From 0ec35a408800a9f1a858d983d3c1ab3024682c26 Mon Sep 17 00:00:00 2001 From: Filippo Squillace Date: Tue, 14 Mar 2017 23:43:52 +0000 Subject: [PATCH 1/8] Issue #174: Add namespace module and check for user namespace --- bin/junest | 43 ++++++++++++++++++-------- lib/core/common.sh | 7 +++++ lib/core/namespace.sh | 47 +++++++++++++++++++++++++++++ tests/unit-tests/test-cli.sh | 43 +++++++++++++++++++++++--- tests/unit-tests/test-common.sh | 10 +++++++ tests/unit-tests/test-namespace.sh | 48 ++++++++++++++++++++++++++++++ 6 files changed, 181 insertions(+), 17 deletions(-) create mode 100644 lib/core/namespace.sh create mode 100755 tests/unit-tests/test-namespace.sh diff --git a/bin/junest b/bin/junest index 836c19b..5e2e6bf 100755 --- a/bin/junest +++ b/bin/junest @@ -7,10 +7,13 @@ JUNEST_BASE="$(readlink -f $(dirname $(readlink -f "$0"))/..)" source "${JUNEST_BASE}/lib/utils/utils.sh" source "${JUNEST_BASE}/lib/core/common.sh" + source "${JUNEST_BASE}/lib/core/build.sh" -source "${JUNEST_BASE}/lib/core/setup.sh" -source "${JUNEST_BASE}/lib/core/proot.sh" source "${JUNEST_BASE}/lib/core/chroot.sh" +source "${JUNEST_BASE}/lib/core/namespace.sh" +source "${JUNEST_BASE}/lib/core/proot.sh" +source "${JUNEST_BASE}/lib/core/setup.sh" + ################################### ### General functions ### @@ -29,8 +32,11 @@ usage() { echo echo -e "Access options:" echo -e "-f, --fakeroot Run $NAME with fakeroot privileges" - echo -e "-r, --root Run $NAME with root privileges" - echo -e "-p, --proot-args Proot arguments (use $CMD -p \"--help\" to check out the proot options)" + echo -e "-r, --root Run $NAME with root privileges via jchroot" + echo -e "-p, --backend-args Arguments for backend program (PRoot or jchroot)" + echo -e " ($CMD -p \"--help\" to check out the PRoot options" + echo -e " $CMD -u -p \"--help\" to check out the jchroot options)" + echo -e "-u, --user-namespace Use Linux User Namespace instead of PRoot" echo echo -e "Building options:" echo -e "-b, --build-image Build a $NAME image (must run in ArchLinux)" @@ -109,7 +115,7 @@ check_cli(){ then die "You must access to $NAME with either fakeroot or root permissions" fi - if $OPT_PROOT_ARGS || $OPT_ARCH + if $OPT_BACKEND_ARGS || $OPT_ARCH then if $OPT_BUILD_IMAGE || $OPT_DELETE || $OPT_HELP || \ $OPT_ROOT || $OPT_VERSION || $OPT_DISABLE_VALIDATION || $OPT_CHECK @@ -135,8 +141,9 @@ function parse_arguments(){ IMAGE_FILE="" OPT_FAKEROOT=false OPT_ROOT=false - OPT_PROOT_ARGS=false - PROOT_ARGS="" + OPT_USER_NAMESPACE=false + OPT_BACKEND_ARGS=false + BACKEND_ARGS="" OPT_ARCH=false ARCH_ARG="" OPT_BUILD_IMAGE=false @@ -153,7 +160,8 @@ function parse_arguments(){ -i|--setup-from-file) OPT_SETUP_FROM_FILE=true ; shift ; IMAGE_FILE=$1 ; shift ;; -f|--fakeroot) OPT_FAKEROOT=true ; shift ;; -r|--root) OPT_ROOT=true ; shift ;; - -p|--proot-args) OPT_PROOT_ARGS=true ; shift ; PROOT_ARGS=$1; shift ;; + -u|--user-namespace) OPT_USER_NAMESPACE=true ; shift ;; + -p|--backend-args) OPT_BACKEND_ARGS=true ; shift ; BACKEND_ARGS=$1; shift ;; -a|--arch) OPT_ARCH=true ; shift ; ARCH_ARG=$1; shift ;; -b|--build-image) OPT_BUILD_IMAGE=true ; shift ;; -n|--disable-validation) OPT_DISABLE_VALIDATION=true ; shift ;; @@ -205,13 +213,22 @@ function execute_operation(){ [ -z "${ARCH_ARG}" ] || \ die "The option --arch cannot be specified since JuNest has already been downloaded in $JUNEST_HOME" - if $OPT_FAKEROOT; then - run_env_as_fakeroot "${PROOT_ARGS}" "${ARGS[@]}" - elif $OPT_ROOT; then - run_env_as_root "${ARGS[@]}" + if $OPT_USER_NAMESPACE; then + if $OPT_FAKEROOT; then + run_env_as_fakeroot_with_namespace "${BACKEND_ARGS}" "${ARGS[@]}" + else + run_env_as_user_with_namespace "${BACKEND_ARGS}" "${ARGS[@]}" + fi else - run_env_as_user "${PROOT_ARGS}" "${ARGS[@]}" + if $OPT_FAKEROOT; then + run_env_as_fakeroot "${BACKEND_ARGS}" "${ARGS[@]}" + elif $OPT_ROOT; then + run_env_as_root "${ARGS[@]}" + else + run_env_as_user "${BACKEND_ARGS}" "${ARGS[@]}" + fi fi + } function cli() { diff --git a/lib/core/common.sh b/lib/core/common.sh index d317933..1ceb092 100644 --- a/lib/core/common.sh +++ b/lib/core/common.sh @@ -19,6 +19,7 @@ ARCHITECTURE_MISMATCH=104 ROOT_ACCESS_ERROR=105 NESTED_ENVIRONMENT=106 VARIABLE_NOT_SET=107 +NO_CONFIG_FOUND=108 if [ "$JUNEST_ENV" == "1" ] then @@ -89,6 +90,8 @@ RM=rm MKDIR=mkdir GETENT=getent CP=cp +# Used for checking user namespace in config.gz file +ZGREP=zgrep LD_EXEC="$LD_LIB --library-path ${JUNEST_HOME}/usr/lib:${JUNEST_HOME}/lib" @@ -120,6 +123,10 @@ function mkdir_cmd(){ $MKDIR $@ || $LD_EXEC ${JUNEST_HOME}/usr/bin/$MKDIR $@ } +function zgrep_cmd(){ + $ZGREP $@ || $LD_EXEC ${JUNEST_HOME}/usr/bin/$ZGREP $@ +} + function proot_cmd(){ local proot_args="$1" shift diff --git a/lib/core/namespace.sh b/lib/core/namespace.sh new file mode 100644 index 0000000..ee26d6c --- /dev/null +++ b/lib/core/namespace.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# +# This module contains all namespace functionalities for JuNest. +# +# http://man7.org/linux/man-pages/man7/namespaces.7.html +# http://man7.org/linux/man-pages/man2/unshare.2.html +# +# Dependencies: +# - lib/utils/utils.sh +# - lib/core/common.sh +# +# vim: ft=sh + +CONFIG_PROC_FILE="/proc/config.gz" +CONFIG_BOOT_FILE="/boot/config-$($UNAME -r)" + +function _is_user_namespace_enabled() { + local config_file="" + if [[ -e $CONFIG_PROC_FILE ]] + then + config_file=$CONFIG_PROC_FILE + elif [[ -e $CONFIG_BOOT_FILE ]] + then + config_file=$CONFIG_BOOT_FILE + else + return $NOT_EXISTING_FILE + fi + + if ! zgrep_cmd "CONFIG_USER_NS=y" $config_file + then + return $NO_CONFIG_FOUND + fi +} + +function run_env_as_user_with_namespace() { + set +e + _is_user_namespace_enabled + case $? in + $NOT_EXISTING_FILE) warn "Could not understand if user namespace is enabled. No config.gz file found. Proceeding anyway..." ;; + $NO_CONFIG_FOUND) warn "User namespace is not enabled or Kernel too old. Proceeding anyway..." ;; + esac + set -e +} + +function run_env_as_fakeroot_with_namespace() { + die_on_status 1 "Not implemented yet" +} diff --git a/tests/unit-tests/test-cli.sh b/tests/unit-tests/test-cli.sh index db23eea..efe55b5 100755 --- a/tests/unit-tests/test-cli.sh +++ b/tests/unit-tests/test-cli.sh @@ -44,17 +44,27 @@ function setup_env(){ echo "setup_env($1)" } function run_env_as_fakeroot(){ - local proot_args="$1" + local backend_args="$1" shift - echo "run_env_as_fakeroot($proot_args,$@)" + echo "run_env_as_fakeroot($backend_args,$@)" } function run_env_as_root(){ echo "run_env_as_root $@" } function run_env_as_user(){ - local proot_args="$1" + local backend_args="$1" shift - echo "run_env_as_user($proot_args,$@)" + echo "run_env_as_user($backend_args,$@)" +} +function run_env_as_fakeroot_with_namespace(){ + local backend_args="$1" + shift + echo "run_env_as_fakeroot_with_namespace($backend_args,$@)" +} +function run_env_as_user_with_namespace(){ + local backend_args="$1" + shift + echo "run_env_as_user_with_namespace($backend_args,$@)" } function test_help(){ @@ -164,6 +174,31 @@ function test_run_env_as_root(){ assertEquals "run_env_as_root command" "$(cat $STDOUTF)" } +function test_run_env_as_fakeroot_with_namespace(){ + assertCommandSuccess cli -u -f + assertEquals "run_env_as_fakeroot_with_namespace(,)" "$(cat $STDOUTF)" + assertCommandSuccess cli --user-namespace --fakeroot + assertEquals "run_env_as_fakeroot_with_namespace(,)" "$(cat $STDOUTF)" + + assertCommandSuccess cli -u -f -p "-b arg" + assertEquals "run_env_as_fakeroot_with_namespace(-b arg,)" "$(cat $STDOUTF)" + assertCommandSuccess cli -u -f -p "-b arg" -- command -kv + assertEquals "run_env_as_fakeroot_with_namespace(-b arg,command -kv)" "$(cat $STDOUTF)" + assertCommandSuccess cli -u -f command --as + assertEquals "run_env_as_fakeroot_with_namespace(,command --as)" "$(cat $STDOUTF)" +} +function test_run_env_as_user_with_namespace(){ + assertCommandSuccess cli -u + assertEquals "run_env_as_user_with_namespace(,)" "$(cat $STDOUTF)" + + assertCommandSuccess cli -u -p "-b arg" + assertEquals "run_env_as_user_with_namespace(-b arg,)" "$(cat $STDOUTF)" + assertCommandSuccess cli -u -p "-b arg" -- command -ll + assertEquals "run_env_as_user_with_namespace(-b arg,command -ll)" "$(cat $STDOUTF)" + assertCommandSuccess cli -u command -ls + assertEquals "run_env_as_user_with_namespace(,command -ls)" "$(cat $STDOUTF)" +} + function test_check_cli(){ assertCommandFail cli -b -h assertCommandFail cli -b -c diff --git a/tests/unit-tests/test-common.sh b/tests/unit-tests/test-common.sh index ad871f5..3cbed72 100755 --- a/tests/unit-tests/test-common.sh +++ b/tests/unit-tests/test-common.sh @@ -100,6 +100,16 @@ function test_mkdir(){ MKDIR=false LD_EXEC=false assertCommandFail mkdir_cmd -p new_dir/new_dir } +function test_zgrep(){ + ZGREP=echo assertCommandSuccess zgrep_cmd new_file + assertEquals "new_file" "$(cat $STDOUTF)" + + ZGREP=false assertCommandSuccess zgrep_cmd new_file + assertEquals "ld_exec ${JUNEST_HOME}/usr/bin/false new_file" "$(cat $STDOUTF)" + + ZGREP=false LD_EXEC=false assertCommandFail zgrep_cmd new_file +} + function test_chroot(){ CHROOT=echo assertCommandSuccess chroot_cmd root assertEquals "root" "$(cat $STDOUTF)" diff --git a/tests/unit-tests/test-namespace.sh b/tests/unit-tests/test-namespace.sh new file mode 100755 index 0000000..deb90d2 --- /dev/null +++ b/tests/unit-tests/test-namespace.sh @@ -0,0 +1,48 @@ +#!/bin/bash + +JUNEST_ROOT=$(readlink -f $(dirname $0)/../..) + +source "$JUNEST_ROOT/tests/utils/utils.sh" + +source "$JUNEST_ROOT/lib/utils/utils.sh" +source "$JUNEST_ROOT/lib/core/common.sh" +source "$JUNEST_ROOT/lib/core/namespace.sh" + +# Disable the exiterr +set +e + +function oneTimeSetUp(){ + setUpUnitTests +} + +function setUp(){ + cwdSetUp +} + +function tearDown(){ + cwdTearDown +} + +function test_is_user_namespace_enabled_no_config_file(){ + CONFIG_PROC_FILE="blah" + CONFIG_BOOT_FILE="blah" + assertCommandFailOnStatus $NOT_EXISTING_FILE _is_user_namespace_enabled +} + +function test_is_user_namespace_enabled_no_config(){ + touch config + gzip config + CONFIG_PROC_FILE="config.gz" + CONFIG_BOOT_FILE="blah" + assertCommandFailOnStatus $NO_CONFIG_FOUND _is_user_namespace_enabled +} + +function test_is_user_namespace_enabled_with_config(){ + echo "CONFIG_USER_NS=y" > config + gzip config + CONFIG_PROC_FILE="config.gz" + CONFIG_BOOT_FILE="blah" + assertCommandSuccess _is_user_namespace_enabled +} + +source $JUNEST_ROOT/tests/utils/shunit2 From 426b708d2d39df8e5597e752993ab9e0c0e158c7 Mon Sep 17 00:00:00 2001 From: Filippo Squillace Date: Wed, 15 Mar 2017 22:22:12 +0000 Subject: [PATCH 2/8] Issue #174: Rename common functions and define skeleton for namespace functions --- lib/core/common.sh | 31 ++++++++++++++++----------- lib/core/namespace.sh | 38 +++++++++++++++++++++++++++++---- lib/core/proot.sh | 18 ++++++++-------- tests/unit-tests/test-common.sh | 26 +++++++++++++++------- 4 files changed, 79 insertions(+), 34 deletions(-) diff --git a/lib/core/common.sh b/lib/core/common.sh index 1ceb092..87b5812 100644 --- a/lib/core/common.sh +++ b/lib/core/common.sh @@ -79,8 +79,8 @@ SH=("/bin/sh" "--login") # List of executables that are run in the host OS: PROOT="${JUNEST_HOME}/opt/proot/proot-${ARCH}" -CHROOT=${JUNEST_BASE}/bin/jchroot -CLASSIC_CHROOT="chroot" +JCHROOT=${JUNEST_BASE}/bin/jchroot +CLASSIC_CHROOT=chroot WGET="wget --no-check-certificate" CURL="curl -L -J -O -k" TAR=tar @@ -92,6 +92,7 @@ GETENT=getent CP=cp # Used for checking user namespace in config.gz file ZGREP=zgrep +UNSHARE=unshare LD_EXEC="$LD_LIB --library-path ${JUNEST_HOME}/usr/lib:${JUNEST_HOME}/lib" @@ -127,6 +128,10 @@ function zgrep_cmd(){ $ZGREP $@ || $LD_EXEC ${JUNEST_HOME}/usr/bin/$ZGREP $@ } +function unshare_cmd(){ + $UNSHARE $@ || $LD_EXEC ${JUNEST_HOME}/usr/bin/$UNSHARE $@ +} + function proot_cmd(){ local proot_args="$1" shift @@ -146,7 +151,7 @@ function download_cmd(){ } function chroot_cmd(){ - $CHROOT "$@" || $CLASSIC_CHROOT "$@" || $LD_EXEC ${JUNEST_HOME}/usr/bin/chroot "$@" + $JCHROOT "$@" || $CLASSIC_CHROOT "$@" || $LD_EXEC ${JUNEST_HOME}/usr/bin/$CLASSIC_CHROOT "$@" } ############## COMMON FUNCTIONS ############### @@ -167,7 +172,7 @@ function chroot_cmd(){ # Output: # None ####################################### -function _provide_common_bindings(){ +function provide_common_bindings(){ RESULT="" local re='(.*):.*' for bind in "/dev" "/sys" "/proc" "/tmp" "$HOME" @@ -198,7 +203,7 @@ function _provide_common_bindings(){ # Output: # None ####################################### -function _copy_passwd_and_group(){ +function copy_passwd_and_group(){ # Enumeration of users/groups is disabled/limited depending on how nsswitch.conf # is configured. # Try to at least get the current user via `getent passwd $USER` since it uses @@ -207,27 +212,27 @@ function _copy_passwd_and_group(){ ! getent_cmd passwd ${USER} >> ${JUNEST_HOME}/etc/passwd then warn "getent command failed or does not exist. Binding directly from /etc/passwd." - _copy_file /etc/passwd ${JUNEST_HOME}/etc/passwd + copy_file /etc/passwd ${JUNEST_HOME}/etc/passwd fi if ! getent_cmd group > ${JUNEST_HOME}/etc/group then warn "getent command failed or does not exist. Binding directly from /etc/group." - _copy_file /etc/group ${JUNEST_HOME}/etc/group + copy_file /etc/group ${JUNEST_HOME}/etc/group fi return 0 } -function _copy_file() { +function copy_file() { local file="${1}" [[ -r "$file" ]] && cp_cmd "$file" "${JUNEST_HOME}/$file" return 0 } -function _copy_common_files() { - _copy_file /etc/host.conf - _copy_file /etc/hosts - _copy_file /etc/nsswitch.conf - _copy_file /etc/resolv.conf +function copy_common_files() { + copy_file /etc/host.conf + copy_file /etc/hosts + copy_file /etc/nsswitch.conf + copy_file /etc/resolv.conf return 0 } diff --git a/lib/core/namespace.sh b/lib/core/namespace.sh index ee26d6c..dddf521 100644 --- a/lib/core/namespace.sh +++ b/lib/core/namespace.sh @@ -32,16 +32,46 @@ function _is_user_namespace_enabled() { fi } -function run_env_as_user_with_namespace() { +function _check_user_namespace() { set +e _is_user_namespace_enabled case $? in $NOT_EXISTING_FILE) warn "Could not understand if user namespace is enabled. No config.gz file found. Proceeding anyway..." ;; - $NO_CONFIG_FOUND) warn "User namespace is not enabled or Kernel too old. Proceeding anyway..." ;; + $NO_CONFIG_FOUND) warn "User namespace is not enabled or Kernel too old (<3.8). Proceeding anyway..." ;; esac set -e } -function run_env_as_fakeroot_with_namespace() { - die_on_status 1 "Not implemented yet" +function run_env_as_user_with_namespace() { + local backend_args="$1" + shift + _check_user_namespace + + copy_common_files + copy_file /etc/hosts.equiv + copy_file /etc/netgroup + copy_file /etc/networks + # No need for localtime as it is setup during the image build + #copy_file /etc/localtime + copy_passwd_and_group + + provide_common_bindings + local bindings=${RESULT} + unset RESULT + + unshare_cmd --mount --user --map-root-user $JCHROOT $bindings $backend_args "$JUNEST_HOME" +} + +function run_env_as_fakeroot_with_namespace() { + local backend_args="$1" + shift + _check_user_namespace + + copy_common_files + + provide_common_bindings + local bindings=${RESULT} + unset RESULT + + JUNEST_ENV=1 unshare_cmd --mount --user --map-root-user $JCHROOT $bindings $backend_args "$JUNEST_HOME" } diff --git a/lib/core/proot.sh b/lib/core/proot.sh index 39d901c..3460f18 100644 --- a/lib/core/proot.sh +++ b/lib/core/proot.sh @@ -59,9 +59,9 @@ function run_env_as_fakeroot(){ (( EUID == 0 )) && \ die_on_status $ROOT_ACCESS_ERROR "You cannot access with root privileges. Use --root option instead." - _copy_common_files + copy_common_files - _provide_common_bindings + provide_common_bindings local bindings=${RESULT} unset RESULT @@ -93,15 +93,15 @@ function run_env_as_user(){ # This function excludes /etc/mtab file so that # it will not give conflicts with the related # symlink in the Arch Linux image. - _copy_common_files - _copy_file /etc/hosts.equiv - _copy_file /etc/netgroup - _copy_file /etc/networks + copy_common_files + copy_file /etc/hosts.equiv + copy_file /etc/netgroup + copy_file /etc/networks # No need for localtime as it is setup during the image build - #_copy_file /etc/localtime - _copy_passwd_and_group + #copy_file /etc/localtime + copy_passwd_and_group - _provide_common_bindings + provide_common_bindings local bindings=${RESULT} unset RESULT diff --git a/tests/unit-tests/test-common.sh b/tests/unit-tests/test-common.sh index 3cbed72..33beefa 100755 --- a/tests/unit-tests/test-common.sh +++ b/tests/unit-tests/test-common.sh @@ -110,17 +110,27 @@ function test_zgrep(){ ZGREP=false LD_EXEC=false assertCommandFail zgrep_cmd new_file } +function test_unshare(){ + UNSHARE=echo assertCommandSuccess unshare_cmd new_program + assertEquals "new_program" "$(cat $STDOUTF)" + + UNSHARE=false assertCommandSuccess unshare_cmd new_program + assertEquals "ld_exec ${JUNEST_HOME}/usr/bin/false new_program" "$(cat $STDOUTF)" + + UNSHARE=false LD_EXEC=false assertCommandFail unshare_cmd new_program +} + function test_chroot(){ - CHROOT=echo assertCommandSuccess chroot_cmd root + JCHROOT=echo assertCommandSuccess chroot_cmd root assertEquals "root" "$(cat $STDOUTF)" - CHROOT=false CLASSIC_CHROOT=echo assertCommandSuccess chroot_cmd root + JCHROOT=false CLASSIC_CHROOT=echo assertCommandSuccess chroot_cmd root assertEquals "root" "$(cat $STDOUTF)" - CHROOT=false CLASSIC_CHROOT=false assertCommandSuccess chroot_cmd root - assertEquals "ld_exec $JUNEST_HOME/usr/bin/chroot root" "$(cat $STDOUTF)" + JCHROOT=false CLASSIC_CHROOT=false assertCommandSuccess chroot_cmd root + assertEquals "ld_exec $JUNEST_HOME/usr/bin/false root" "$(cat $STDOUTF)" - CHROOT=false CLASSIC_CHROOT=false LD_EXEC=false assertCommandFail chroot_cmd root + JCHROOT=false CLASSIC_CHROOT=false LD_EXEC=false assertCommandFail chroot_cmd root } function test_proot_cmd_compat(){ @@ -153,7 +163,7 @@ function test_copy_passwd_and_group(){ getent_cmd_mock() { echo $@ } - GETENT=getent_cmd_mock assertCommandSuccess _copy_passwd_and_group + GETENT=getent_cmd_mock assertCommandSuccess copy_passwd_and_group assertEquals "$(echo -e "passwd\npasswd $USER")" "$(cat $JUNEST_HOME/etc/passwd)" assertEquals "group" "$(cat $JUNEST_HOME/etc/group)" } @@ -162,12 +172,12 @@ function test_copy_passwd_and_group_fallback(){ cp_cmd_mock() { echo $@ } - CP=cp_cmd_mock GETENT=false LD_EXEC=false assertCommandSuccess _copy_passwd_and_group + CP=cp_cmd_mock GETENT=false LD_EXEC=false assertCommandSuccess copy_passwd_and_group assertEquals "$(echo -e "/etc/passwd $JUNEST_HOME//etc/passwd\n/etc/group $JUNEST_HOME//etc/group")" "$(cat $STDOUTF)" } function test_copy_passwd_and_group_failure(){ - CP=false GETENT=false LD_EXEC=false assertCommandFailOnStatus 1 _copy_passwd_and_group + CP=false GETENT=false LD_EXEC=false assertCommandFailOnStatus 1 copy_passwd_and_group } function test_nested_env(){ From 0f7fd33c53e7fd20b07ae37be1fb3b26c152d2c3 Mon Sep 17 00:00:00 2001 From: Filippo Squillace Date: Fri, 17 Mar 2017 17:49:00 +0000 Subject: [PATCH 3/8] Issue #174: Add new program GRoot and change version option for JuNest! This replaces jchroot because GRoot can be also used in a user namespace environment. --- README.md | 4 +- bin/groot | 180 ++++++++++++++++++ bin/jchroot | 98 ---------- bin/junest | 20 +- lib/core/build.sh | 4 +- lib/core/common.sh | 9 +- lib/core/namespace.sh | 14 +- lib/utils/utils.sh | 14 ++ tests/unit-tests/test-common.sh | 20 +- tests/unit-tests/test-groot.sh | 137 +++++++++++++ .../{test-cli.sh => test-junest.sh} | 115 +++++------ tests/unit-tests/test-utils.sh | 22 +++ tests/utils/utils.sh | 5 +- 13 files changed, 458 insertions(+), 184 deletions(-) create mode 100755 bin/groot delete mode 100755 bin/jchroot create mode 100755 tests/unit-tests/test-groot.sh rename tests/unit-tests/{test-cli.sh => test-junest.sh} (66%) diff --git a/README.md b/README.md index 98f0c9e..e747c4e 100644 --- a/README.md +++ b/README.md @@ -169,14 +169,14 @@ Internals There are two main chroot jail used in JuNest. The main one is [proot](https://wiki.archlinux.org/index.php/Proot) which allows unprivileged users to execute programs inside a sandbox and -jchroot, a small and portable version of +GRoot, a small and portable version of [arch-chroot](https://wiki.archlinux.org/index.php/Chroot) which is an enhanced chroot for privileged users that mounts the primary directories (i.e. /proc, /sys, /dev and /run) before executing any programs inside the sandbox. ## Automatic fallback to classic chroot ## -If jchroot fails for some reasons in the host system (i.e. it is not able to +If GRoot fails for some reasons in the host system (i.e. it is not able to mount one of the directories), JuNest automatically tries to fallback to the classic chroot. diff --git a/bin/groot b/bin/groot new file mode 100755 index 0000000..1d6e26b --- /dev/null +++ b/bin/groot @@ -0,0 +1,180 @@ +#!/bin/bash +# +# This script is the simplified and portable version of arch-chroot +# (https://wiki.archlinux.org/index.php/Change_root#Using_arch-chroot) +# + +set -e + +# JUNEST_BASE can be overridden for testing purposes. +# There is no need for doing it for normal usage. +JUNEST_BASE="${JUNEST_BASE:-$(readlink -f $(dirname $(readlink -f "$0"))/..)}" +NAME='GRoot' +CMD='groot' +DESCRIPTION="I am $NAME!" +CHROOTCMD=${CHROOTCMD:-chroot} +SHELL="/bin/sh" +MOUNT=mount +UMOUNT=umount +MOUNTPOINT=mountpoint +MKDIR=mkdir +TOUCH=touch + +NOT_EXISTING_FILE=103 +NOT_ABSOLUTE_PATH=111 +NO_ROOT_PRIVILEGES=110 + +source "${JUNEST_BASE}/lib/utils/utils.sh" + + +################################ MAIN FUNCTIONS ########################### + +function chroot_add_mount() { + $MOUNT "$@" && CHROOT_ACTIVE_MOUNTS=("${@: -1}" "${CHROOT_ACTIVE_MOUNTS[@]}") +} + +function chroot_teardown() { + $UMOUNT "${CHROOT_ACTIVE_MOUNTS[@]}" + unset CHROOT_ACTIVE_MOUNTS +} + +function chroot_maybe_add_mount() { + local cond=$1 + shift + if eval "$cond"; then + chroot_add_mount "$@" + return + fi + return 1 +} + +function chroot_setup() { + CHROOT_ACTIVE_MOUNTS=() + check_and_trap 'chroot_teardown' EXIT + + if ! chroot_maybe_add_mount "! $MOUNTPOINT -q '$CHROOTDIR'" --bind "$CHROOTDIR" "$CHROOTDIR" + then + warn "Failed mount of directories. $CHROOTDIR is already a mountpoint. Skipping it..." + return 0 + fi + + local re='(.*):(.*)' + for binds in ${BINDINGS[@]} + do + local host_path="" + local guest_path="" + if [[ $binds =~ $re ]] + then + local host_path="${BASH_REMATCH[1]}" + local guest_path="${BASH_REMATCH[2]}" + else + local host_path="$binds" + local guest_path="$binds" + fi + + create_node "${host_path}" "${CHROOTDIR}${guest_path}" + chroot_add_mount --rbind "${host_path}" "${CHROOTDIR}${guest_path}" + done +} + +function create_node() { + local src="$1" + local dst="$2" + if [[ ! -e $src ]] + then + die_on_status $NOT_EXISTING_FILE "${src} does not exist." + elif [[ $src != /* ]] + then + die_on_status $NOT_ABSOLUTE_PATH "${src} is not an absolute path." + elif [[ -f $src ]] + then + $TOUCH "$dst" + elif [[ -d $src ]] + then + $MKDIR -p "$dst" + fi +} + +function usage() { + cat < [command]] + +Options: + -b, --bind + Make the content of accessible in the guest rootfs. + + This option makes any file or directory of the host rootfs + accessible in the confined environment just as if it were part of + the guest rootfs. By default the host path is bound to the same + path in the guest rootfs but users can specify any other location + with the syntax: -b :. This option can + be invoked multiple times and the paths specified must be absolutes. + + -h, --help Print this help message + + -V, --version Show the $NAME version + +If 'command' is unspecified, $CMD will launch $SHELL. + +EOF +} + +version() { + echo -e "$NAME $(cat $JUNEST_BASE/VERSION)" +} + +function parse_arguments() { + OPT_BIND=false + BINDINGS=() + OPT_HELP=false + OPT_VERSION=false + for opt in "$@" + do + case "$1" in + -b|--bind) OPT_BIND=true ; shift ; BINDINGS+=("$1") ; shift ;; + -h|--help) OPT_HELP=true ; shift ;; + -V|--version) OPT_VERSION=true ; shift ;; + -*) die "Invalid option $1" ;; + *) break ;; + esac + done + + if [[ ! -z $1 ]] + then + CHROOTDIR="$1" + shift + fi + ARGS=() + for arg in "$@" + do + ARGS+=("$arg") + done +} + +function is_user_root() { + (( EUID == 0 )) +} + +function execute_operation() { + $OPT_HELP && usage && return + $OPT_VERSION && version && return + + is_user_root || die_on_status $NO_ROOT_PRIVILEGES 'This script must be run with root privileges' + + [[ -d $CHROOTDIR ]] || die_on_status $NOT_EXISTING_FILE "Can't create chroot on non-directory $CHROOTDIR" + + chroot_setup "$CHROOTDIR" || die "Failed to setup chroot $CHROOTDIR" + + $CHROOTCMD "$CHROOTDIR" "${ARGS[@]}" +} + + +function main() { + parse_arguments "$@" + execute_operation +} + +main "$@" diff --git a/bin/jchroot b/bin/jchroot deleted file mode 100755 index fe43661..0000000 --- a/bin/jchroot +++ /dev/null @@ -1,98 +0,0 @@ -#!/bin/bash -# -# This script is the simplified and portable version of arch-chroot -# (https://wiki.archlinux.org/index.php/Change_root#Using_arch-chroot) -# - -set -e - -JUNEST_BASE="$(readlink -f $(dirname $(readlink -f "$0"))/..)" - -source "${JUNEST_BASE}/lib/utils/utils.sh" - -################################ MAIN FUNCTIONS ########################### - -chroot_add_mount() { - mount "$@" && CHROOT_ACTIVE_MOUNTS=("$2" "${CHROOT_ACTIVE_MOUNTS[@]}") -} - -chroot_maybe_add_mount() { - local cond=$1; shift - if eval "$cond"; then - chroot_add_mount "$@" - fi -} - -chroot_setup() { - CHROOT_ACTIVE_MOUNTS=() - [[ $(trap -p EXIT) ]] && die '(BUG): attempting to overwrite existing EXIT trap' - trap 'chroot_teardown' EXIT - - chroot_maybe_add_mount "! mountpoint -q '$1'" "$1" "$1" --bind && - chroot_add_mount proc "$1/proc" -t proc -o nosuid,noexec,nodev && - chroot_add_mount sys "$1/sys" -t sysfs -o nosuid,noexec,nodev,ro && - chroot_add_mount udev "$1/dev" -t devtmpfs -o mode=0755,nosuid && - chroot_add_mount devpts "$1/dev/pts" -t devpts -o mode=0620,gid=5,nosuid,noexec && - chroot_add_mount shm "$1/dev/shm" -t tmpfs -o mode=1777,nosuid,nodev && - chroot_add_mount run "$1/run" -t tmpfs -o nosuid,nodev,mode=0755 && - chroot_add_mount tmp "$1/tmp" -t tmpfs -o mode=1777,atime,nodev,nosuid && - mkdir -p "$1/$HOME" && - chroot_add_mount $HOME "$1/$HOME" --bind - - mkdir -p "$1/run/lock" -} - -chroot_teardown() { - umount "${CHROOT_ACTIVE_MOUNTS[@]}" - unset CHROOT_ACTIVE_MOUNTS -} - -usage() { - cat < Arguments for backend program (PRoot or jchroot)" + echo -e "-r, --root Run $NAME with root privileges via GRoot" + echo -e "-p, --backend-args Arguments for backend program (PRoot or GRoot)" echo -e " ($CMD -p \"--help\" to check out the PRoot options" - echo -e " $CMD -u -p \"--help\" to check out the jchroot options)" + echo -e " $CMD -u -p \"--help\" to check out the GRoot options)" echo -e "-u, --user-namespace Use Linux User Namespace instead of PRoot" echo echo -e "Building options:" @@ -47,7 +51,7 @@ usage() { echo echo -e "General options:" echo -e "-h, --help Show this help message" - echo -e "-v, --version Show the $NAME version" + echo -e "-V, --version Show the $NAME version" } version() { @@ -169,7 +173,7 @@ function parse_arguments(){ -s|--skip-root-tests) OPT_SKIP_ROOT_TEST=true ; shift ;; -d|--delete) OPT_DELETE=true ; shift ;; -h|--help) OPT_HELP=true ; shift ;; - -v|--version) OPT_VERSION=true ; shift ;; + -V|--version) OPT_VERSION=true ; shift ;; --) shift ; break ;; -*) die "Invalid option $1" ;; *) break ;; @@ -231,11 +235,11 @@ function execute_operation(){ } -function cli() { +function main() { parse_arguments "$@" check_cli execute_operation } -cli "$@" +main "$@" # vim: set ts=4 sw=4 noet: diff --git a/lib/core/build.sh b/lib/core/build.sh index 017ccad..fd87d86 100644 --- a/lib/core/build.sh +++ b/lib/core/build.sh @@ -69,11 +69,11 @@ function build_image_env(){ # sed command is required for locale-gen sudo ln -sf /usr/share/zoneinfo/posix/UTC ${maindir}/root/etc/localtime sudo bash -c "echo 'en_US.UTF-8 UTF-8' >> ${maindir}/root/etc/locale.gen" - sudo ${maindir}/root/opt/junest/bin/jchroot ${maindir}/root locale-gen + sudo ${maindir}/root/opt/junest/bin/groot ${maindir}/root locale-gen sudo bash -c "echo LANG=\"en_US.UTF-8\" >> ${maindir}/root/etc/locale.conf" info "Setting up the pacman keyring (this might take a while!)..." - sudo ${maindir}/root/opt/junest/bin/jchroot ${maindir}/root bash -c \ + sudo ${maindir}/root/opt/junest/bin/groot ${maindir}/root bash -c \ "pacman-key --init; pacman-key --populate archlinux; [ -e /etc/pacman.d/gnupg/S.gpg-agent ] && gpg-connect-agent -S /etc/pacman.d/gnupg/S.gpg-agent killagent /bye" sudo rm ${maindir}/root/var/cache/pacman/pkg/* diff --git a/lib/core/common.sh b/lib/core/common.sh index 87b5812..d7e050c 100644 --- a/lib/core/common.sh +++ b/lib/core/common.sh @@ -7,8 +7,6 @@ # # vim: ft=sh -set -e - NAME='JuNest' CMD='junest' DESCRIPTION='The Arch Linux based distro that runs upon any Linux distros without root access' @@ -79,7 +77,7 @@ SH=("/bin/sh" "--login") # List of executables that are run in the host OS: PROOT="${JUNEST_HOME}/opt/proot/proot-${ARCH}" -JCHROOT=${JUNEST_BASE}/bin/jchroot +GROOT=${JUNEST_BASE}/bin/groot CLASSIC_CHROOT=chroot WGET="wget --no-check-certificate" CURL="curl -L -J -O -k" @@ -125,7 +123,8 @@ function mkdir_cmd(){ } function zgrep_cmd(){ - $ZGREP $@ || $LD_EXEC ${JUNEST_HOME}/usr/bin/$ZGREP $@ + # No need for LD_EXEC as zgrep is a POSIX shell script + $ZGREP $@ || ${JUNEST_HOME}/usr/bin/$ZGREP $@ } function unshare_cmd(){ @@ -151,7 +150,7 @@ function download_cmd(){ } function chroot_cmd(){ - $JCHROOT "$@" || $CLASSIC_CHROOT "$@" || $LD_EXEC ${JUNEST_HOME}/usr/bin/$CLASSIC_CHROOT "$@" + $GROOT "$@" || $CLASSIC_CHROOT "$@" || $LD_EXEC ${JUNEST_HOME}/usr/bin/$CLASSIC_CHROOT "$@" } ############## COMMON FUNCTIONS ############### diff --git a/lib/core/namespace.sh b/lib/core/namespace.sh index dddf521..7c3da65 100644 --- a/lib/core/namespace.sh +++ b/lib/core/namespace.sh @@ -59,7 +59,7 @@ function run_env_as_user_with_namespace() { local bindings=${RESULT} unset RESULT - unshare_cmd --mount --user --map-root-user $JCHROOT $bindings $backend_args "$JUNEST_HOME" + unshare_cmd --mount --user --map-root-user $GROOT $bindings $backend_args "$JUNEST_HOME" } function run_env_as_fakeroot_with_namespace() { @@ -69,9 +69,19 @@ function run_env_as_fakeroot_with_namespace() { copy_common_files + #mkdir -p "$chrootdir/$HOME" + #mkdir -p "$chrootdir/run/lock" + #chroot_add_mount --rbind /proc "$chrootdir/proc/" + #chroot_add_mount --rbind /dev "$chrootdir/dev/" + #chroot_add_mount --rbind /sys "$chrootdir/sys/" + #chroot_add_mount --rbind /tmp "$chrootdir/tmp/" + ## alternately create a new tmp istead of binding it: + ##chroot_add_mount -t tmpfs tmp "$chrootdir/tmp/" + #chroot_add_mount --rbind $HOME "$chrootdir/$HOME" provide_common_bindings local bindings=${RESULT} unset RESULT - JUNEST_ENV=1 unshare_cmd --mount --user --map-root-user $JCHROOT $bindings $backend_args "$JUNEST_HOME" + JUNEST_ENV=1 unshare_cmd --mount --user --map-root-user $GROOT $bindings $backend_args "$JUNEST_HOME" + } diff --git a/lib/utils/utils.sh b/lib/utils/utils.sh index 840fbe9..00e2cb6 100644 --- a/lib/utils/utils.sh +++ b/lib/utils/utils.sh @@ -170,6 +170,20 @@ function ask(){ [ "$res" == "Y" ] } +function check_and_trap() { + local sigs="${@:2:${#@}}" + local traps="$(trap -p $sigs)" + [[ $traps ]] && die "Attempting to overwrite existing $sigs trap: $traps" + trap $@ +} + +function check_and_force_trap() { + local sigs="${@:2:${#@}}" + local traps="$(trap -p $sigs)" + [[ $traps ]] && warn "Attempting to overwrite existing $sigs trap: $traps" + trap $@ +} + function insert_quotes_on_spaces(){ # It inserts quotes between arguments. # Useful to preserve quotes on command diff --git a/tests/unit-tests/test-common.sh b/tests/unit-tests/test-common.sh index 33beefa..a95c32a 100755 --- a/tests/unit-tests/test-common.sh +++ b/tests/unit-tests/test-common.sh @@ -104,10 +104,16 @@ function test_zgrep(){ ZGREP=echo assertCommandSuccess zgrep_cmd new_file assertEquals "new_file" "$(cat $STDOUTF)" - ZGREP=false assertCommandSuccess zgrep_cmd new_file - assertEquals "ld_exec ${JUNEST_HOME}/usr/bin/false new_file" "$(cat $STDOUTF)" + mkdir -p ${JUNEST_HOME}/usr/bin + touch ${JUNEST_HOME}/usr/bin/false + chmod +x ${JUNEST_HOME}/usr/bin/false - ZGREP=false LD_EXEC=false assertCommandFail zgrep_cmd new_file + echo -e "#!/bin/bash\necho zgrep" > ${JUNEST_HOME}/usr/bin/false + ZGREP=false assertCommandSuccess zgrep_cmd new_file + assertEquals "zgrep" "$(cat $STDOUTF)" + + echo -e "#!/bin/bash\nexit 1" > ${JUNEST_HOME}/usr/bin/false + ZGREP=false assertCommandFail zgrep_cmd new_file } function test_unshare(){ @@ -121,16 +127,16 @@ function test_unshare(){ } function test_chroot(){ - JCHROOT=echo assertCommandSuccess chroot_cmd root + GROOT=echo assertCommandSuccess chroot_cmd root assertEquals "root" "$(cat $STDOUTF)" - JCHROOT=false CLASSIC_CHROOT=echo assertCommandSuccess chroot_cmd root + GROOT=false CLASSIC_CHROOT=echo assertCommandSuccess chroot_cmd root assertEquals "root" "$(cat $STDOUTF)" - JCHROOT=false CLASSIC_CHROOT=false assertCommandSuccess chroot_cmd root + GROOT=false CLASSIC_CHROOT=false assertCommandSuccess chroot_cmd root assertEquals "ld_exec $JUNEST_HOME/usr/bin/false root" "$(cat $STDOUTF)" - JCHROOT=false CLASSIC_CHROOT=false LD_EXEC=false assertCommandFail chroot_cmd root + GROOT=false CLASSIC_CHROOT=false LD_EXEC=false assertCommandFail chroot_cmd root } function test_proot_cmd_compat(){ diff --git a/tests/unit-tests/test-groot.sh b/tests/unit-tests/test-groot.sh new file mode 100755 index 0000000..02f05ec --- /dev/null +++ b/tests/unit-tests/test-groot.sh @@ -0,0 +1,137 @@ +#!/bin/bash +source "$(dirname $0)/../utils/utils.sh" + +JUNEST_BASE="$(readlink -f $(dirname $(readlink -f "$0"))/../..)" + +# Disable the exiterr +set +e + +function oneTimeSetUp(){ + setUpUnitTests +} + +function setUp(){ + # Attempt to source the files under test to revert variable overrides + source $JUNEST_BASE/bin/groot -h &> /dev/null + set +e + + cwdSetUp + mkdir -p chrootdir + + init_mocks +} + +function tearDown(){ + cwdTearDown +} + +## Mock functions ## +function init_mocks() { + function usage(){ + echo "usage" + } + function is_user_root() { + return 0 + } + function chroot() { + echo "chroot($@)" + } + function mountpoint() { + echo "mountpoint($@)" + # As default suppose the mountpoint does not exist + return 1 + } + function mount() { + echo "mount($@)" + } + function umount() { + echo "umount($@)" + } + function check_and_trap() { + echo "check_and_trap($@)" + } +} + +function test_help(){ + assertCommandSuccess main -h + assertEquals "usage" "$(cat $STDOUTF)" + assertCommandSuccess main --help + assertEquals "usage" "$(cat $STDOUTF)" +} +function test_version(){ + assertCommandSuccess main -V + assertEquals "$NAME $(cat $JUNEST_BASE/VERSION)" "$(cat $STDOUTF)" + assertCommandSuccess main --version + assertEquals "$NAME $(cat $JUNEST_BASE/VERSION)" "$(cat $STDOUTF)" +} +function test_groot_no_root(){ + is_user_root() { + return 1 + } + assertCommandFailOnStatus $NO_ROOT_PRIVILEGES main +} +function test_groot_no_directory(){ + assertCommandFailOnStatus $NOT_EXISTING_FILE main no-directory +} +function test_groot_mountpoint_exist(){ + mountpoint_mock() { + echo "mountpoint($@)" + } + MOUNTPOINT=mountpoint_mock + assertCommandSuccess main chrootdir + assertEquals "$(echo -e "check_and_trap(chroot_teardown EXIT)\nmountpoint(-q chrootdir)\nchroot(chrootdir)")" "$(cat $STDOUTF)" +} +function test_groot_mountpoint_does_not_exist(){ + assertCommandSuccess main chrootdir + assertEquals "$(echo -e "check_and_trap(chroot_teardown EXIT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nchroot(chrootdir)")" "$(cat $STDOUTF)" +} +function test_groot_with_bind(){ + assertCommandSuccess main -b /tmp chrootdir + [[ -d chrootdir/tmp ]] + assertEquals 0 $? + assertEquals "$(echo -e "check_and_trap(chroot_teardown EXIT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(--rbind /tmp chrootdir/tmp)\nchroot(chrootdir)")" "$(cat $STDOUTF)" +} +function test_groot_with_bind_file(){ + touch file_src + assertCommandSuccess main -b ${PWD}/file_src:/file_src chrootdir + [[ -f chrootdir/file_src ]] + assertEquals 0 $? + assertEquals "$(echo -e "check_and_trap(chroot_teardown EXIT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(--rbind ${PWD}/file_src chrootdir/file_src)\nchroot(chrootdir)")" "$(cat $STDOUTF)" +} +function test_groot_with_bind_not_existing_node(){ + assertCommandFailOnStatus $NOT_EXISTING_FILE main -b ${PWD}/file_src:/file_src chrootdir + assertEquals "$(echo -e "check_and_trap(chroot_teardown EXIT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)")" "$(cat $STDOUTF)" +} +function test_groot_with_bind_not_absolute_path_node(){ + touch file_src + assertCommandFailOnStatus $NOT_ABSOLUTE_PATH main -b file_src:/file_src chrootdir + assertEquals "$(echo -e "check_and_trap(chroot_teardown EXIT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)")" "$(cat $STDOUTF)" +} +function test_groot_with_bind_guest_host(){ + assertCommandSuccess main -b /tmp:/home/tmp chrootdir + [[ -d chrootdir/home/tmp ]] + assertEquals 0 $? + assertEquals "$(echo -e "check_and_trap(chroot_teardown EXIT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(--rbind /tmp chrootdir/home/tmp)\nchroot(chrootdir)")" "$(cat $STDOUTF)" +} +function test_groot_with_multiple_bind(){ + assertCommandSuccess main -b /tmp:/home/tmp -b /dev chrootdir + [[ -d chrootdir/home/tmp ]] + assertEquals 0 $? + [[ -d chrootdir/dev ]] + assertEquals 0 $? + assertEquals "$(echo -e "check_and_trap(chroot_teardown EXIT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(--rbind /tmp chrootdir/home/tmp)\nmount(--rbind /dev chrootdir/dev)\nchroot(chrootdir)")" "$(cat $STDOUTF)" +} +function test_groot_with_command(){ + assertCommandSuccess main chrootdir ls -la -h + assertEquals "$(echo -e "check_and_trap(chroot_teardown EXIT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nchroot(chrootdir ls -la -h)")" "$(cat $STDOUTF)" +} +function test_groot_with_bind_and_command(){ + assertCommandSuccess main -b /tmp:/home/tmp -b /dev chrootdir ls -la -h + [[ -d chrootdir/home/tmp ]] + assertEquals 0 $? + [[ -d chrootdir/dev ]] + assertEquals 0 $? + assertEquals "$(echo -e "check_and_trap(chroot_teardown EXIT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(--rbind /tmp chrootdir/home/tmp)\nmount(--rbind /dev chrootdir/dev)\nchroot(chrootdir ls -la -h)")" "$(cat $STDOUTF)" +} + +source $(dirname $0)/../utils/shunit2 diff --git a/tests/unit-tests/test-cli.sh b/tests/unit-tests/test-junest.sh similarity index 66% rename from tests/unit-tests/test-cli.sh rename to tests/unit-tests/test-junest.sh index efe55b5..c9f2d4a 100755 --- a/tests/unit-tests/test-cli.sh +++ b/tests/unit-tests/test-junest.sh @@ -1,7 +1,8 @@ #!/bin/bash source "$(dirname $0)/../utils/utils.sh" -source $(dirname $0)/../../bin/junest -h &> /dev/null +JUNEST_BASE="$(dirname $0)/../.." +source $JUNEST_BASE/bin/junest -h &> /dev/null # Disable the exiterr set +e @@ -68,149 +69,149 @@ function run_env_as_user_with_namespace(){ } function test_help(){ - assertCommandSuccess cli -h + assertCommandSuccess main -h assertEquals "usage" "$(cat $STDOUTF)" - assertCommandSuccess cli --help + assertCommandSuccess main --help assertEquals "usage" "$(cat $STDOUTF)" } function test_version(){ - assertCommandSuccess cli -v + assertCommandSuccess main -V assertEquals "version" "$(cat $STDOUTF)" - assertCommandSuccess cli --version + assertCommandSuccess main --version assertEquals "version" "$(cat $STDOUTF)" } function test_build_image_env(){ - assertCommandSuccess cli -b + assertCommandSuccess main -b assertEquals "build_image_env(false,false)" "$(cat $STDOUTF)" - assertCommandSuccess cli --build-image + assertCommandSuccess main --build-image assertEquals "build_image_env(false,false)" "$(cat $STDOUTF)" - assertCommandSuccess cli -b -s + assertCommandSuccess main -b -s assertEquals "build_image_env(false,true)" "$(cat $STDOUTF)" - assertCommandSuccess cli -b -n + assertCommandSuccess main -b -n assertEquals "build_image_env(true,false)" "$(cat $STDOUTF)" - assertCommandSuccess cli -b -n -s + assertCommandSuccess main -b -n -s assertEquals "build_image_env(true,true)" "$(cat $STDOUTF)" - assertCommandSuccess cli --build-image --disable-validation --skip-root-tests + assertCommandSuccess main --build-image --disable-validation --skip-root-tests assertEquals "build_image_env(true,true)" "$(cat $STDOUTF)" } function test_check_env(){ - assertCommandSuccess cli -c myscript + assertCommandSuccess main -c myscript assertEquals "check_env(${JUNEST_HOME},myscript,false)" "$(cat $STDOUTF)" - assertCommandSuccess cli --check myscript + assertCommandSuccess main --check myscript assertEquals "check_env(${JUNEST_HOME},myscript,false)" "$(cat $STDOUTF)" - assertCommandSuccess cli -c myscript -s + assertCommandSuccess main -c myscript -s assertEquals "check_env(${JUNEST_HOME},myscript,true)" "$(cat $STDOUTF)" - assertCommandSuccess cli --check myscript --skip-root-tests + assertCommandSuccess main --check myscript --skip-root-tests assertEquals "check_env(${JUNEST_HOME},myscript,true)" "$(cat $STDOUTF)" } function test_delete_env(){ - assertCommandSuccess cli -d + assertCommandSuccess main -d assertEquals "delete_env" "$(cat $STDOUTF)" - assertCommandSuccess cli --delete + assertCommandSuccess main --delete assertEquals "delete_env" "$(cat $STDOUTF)" } function test_setup_env_from_file(){ is_env_installed(){ return 1 } - assertCommandSuccess cli -i myimage + assertCommandSuccess main -i myimage assertEquals "$(echo -e "setup_env_from_file(myimage)\nrun_env_as_user(,)")" "$(cat $STDOUTF)" - assertCommandSuccess cli --setup-from-file myimage + assertCommandSuccess main --setup-from-file myimage assertEquals "$(echo -e "setup_env_from_file(myimage)\nrun_env_as_user(,)")" "$(cat $STDOUTF)" is_env_installed(){ return 0 } - assertCommandFail cli -i myimage + assertCommandFail main -i myimage } function test_setup_env(){ is_env_installed(){ return 1 } - assertCommandSuccess cli -a arm + assertCommandSuccess main -a arm assertEquals "$(echo -e "setup_env(arm)\nrun_env_as_user(,)")" "$(cat $STDOUTF)" - assertCommandSuccess cli --arch arm + assertCommandSuccess main --arch arm assertEquals "$(echo -e "setup_env(arm)\nrun_env_as_user(,)")" "$(cat $STDOUTF)" - assertCommandSuccess cli + assertCommandSuccess main assertEquals "$(echo -e "setup_env()\nrun_env_as_user(,)")" "$(cat $STDOUTF)" is_env_installed(){ return 0 } - assertCommandFail cli -a arm + assertCommandFail main -a arm } function test_run_env_as_fakeroot(){ - assertCommandSuccess cli -f + assertCommandSuccess main -f assertEquals "run_env_as_fakeroot(,)" "$(cat $STDOUTF)" - assertCommandSuccess cli --fakeroot + assertCommandSuccess main --fakeroot assertEquals "run_env_as_fakeroot(,)" "$(cat $STDOUTF)" - assertCommandSuccess cli -f -p "-b arg" + assertCommandSuccess main -f -p "-b arg" assertEquals "run_env_as_fakeroot(-b arg,)" "$(cat $STDOUTF)" - assertCommandSuccess cli -f -p "-b arg" -- command -kv + assertCommandSuccess main -f -p "-b arg" -- command -kv assertEquals "run_env_as_fakeroot(-b arg,command -kv)" "$(cat $STDOUTF)" - assertCommandSuccess cli -f command --as + assertCommandSuccess main -f command --as assertEquals "run_env_as_fakeroot(,command --as)" "$(cat $STDOUTF)" - assertCommandFail cli -a "myarch" -f command --as + assertCommandFail main -a "myarch" -f command --as } function test_run_env_as_user(){ - assertCommandSuccess cli + assertCommandSuccess main assertEquals "run_env_as_user(,)" "$(cat $STDOUTF)" - assertCommandSuccess cli -p "-b arg" + assertCommandSuccess main -p "-b arg" assertEquals "run_env_as_user(-b arg,)" "$(cat $STDOUTF)" - assertCommandSuccess cli -p "-b arg" -- command -ll + assertCommandSuccess main -p "-b arg" -- command -ll assertEquals "run_env_as_user(-b arg,command -ll)" "$(cat $STDOUTF)" - assertCommandSuccess cli command -ls + assertCommandSuccess main command -ls assertEquals "run_env_as_user(,command -ls)" "$(cat $STDOUTF)" - assertCommandFail cli -a "myarch" -- command -ls + assertCommandFail main -a "myarch" -- command -ls } function test_run_env_as_root(){ - assertCommandSuccess cli -r + assertCommandSuccess main -r assertEquals "run_env_as_root " "$(cat $STDOUTF)" - assertCommandSuccess cli -r command + assertCommandSuccess main -r command assertEquals "run_env_as_root command" "$(cat $STDOUTF)" } function test_run_env_as_fakeroot_with_namespace(){ - assertCommandSuccess cli -u -f + assertCommandSuccess main -u -f assertEquals "run_env_as_fakeroot_with_namespace(,)" "$(cat $STDOUTF)" - assertCommandSuccess cli --user-namespace --fakeroot + assertCommandSuccess main --user-namespace --fakeroot assertEquals "run_env_as_fakeroot_with_namespace(,)" "$(cat $STDOUTF)" - assertCommandSuccess cli -u -f -p "-b arg" + assertCommandSuccess main -u -f -p "-b arg" assertEquals "run_env_as_fakeroot_with_namespace(-b arg,)" "$(cat $STDOUTF)" - assertCommandSuccess cli -u -f -p "-b arg" -- command -kv + assertCommandSuccess main -u -f -p "-b arg" -- command -kv assertEquals "run_env_as_fakeroot_with_namespace(-b arg,command -kv)" "$(cat $STDOUTF)" - assertCommandSuccess cli -u -f command --as + assertCommandSuccess main -u -f command --as assertEquals "run_env_as_fakeroot_with_namespace(,command --as)" "$(cat $STDOUTF)" } function test_run_env_as_user_with_namespace(){ - assertCommandSuccess cli -u + assertCommandSuccess main -u assertEquals "run_env_as_user_with_namespace(,)" "$(cat $STDOUTF)" - assertCommandSuccess cli -u -p "-b arg" + assertCommandSuccess main -u -p "-b arg" assertEquals "run_env_as_user_with_namespace(-b arg,)" "$(cat $STDOUTF)" - assertCommandSuccess cli -u -p "-b arg" -- command -ll + assertCommandSuccess main -u -p "-b arg" -- command -ll assertEquals "run_env_as_user_with_namespace(-b arg,command -ll)" "$(cat $STDOUTF)" - assertCommandSuccess cli -u command -ls + assertCommandSuccess main -u command -ls assertEquals "run_env_as_user_with_namespace(,command -ls)" "$(cat $STDOUTF)" } function test_check_cli(){ - assertCommandFail cli -b -h - assertCommandFail cli -b -c - assertCommandFail cli -d -s - assertCommandFail cli -n -v - assertCommandFail cli -d -r - assertCommandFail cli -h -f - assertCommandFail cli -v -i fsd - assertCommandFail cli -f -r - assertCommandFail cli -p args -v - assertCommandFail cli -a arch -v - assertCommandFail cli -d args + assertCommandFail main -b -h + assertCommandFail main -b -c + assertCommandFail main -d -s + assertCommandFail main -n -v + assertCommandFail main -d -r + assertCommandFail main -h -f + assertCommandFail main -v -i fsd + assertCommandFail main -f -r + assertCommandFail main -p args -v + assertCommandFail main -a arch -v + assertCommandFail main -d args } source $(dirname $0)/../utils/shunit2 diff --git a/tests/unit-tests/test-utils.sh b/tests/unit-tests/test-utils.sh index 8a5a127..c0b1ebf 100755 --- a/tests/unit-tests/test-utils.sh +++ b/tests/unit-tests/test-utils.sh @@ -79,6 +79,28 @@ function test_ask_wrong_default_answer() { assertEquals 33 $? } +function test_check_and_trap_fail() { + trap echo EXIT + trap ls QUIT + assertCommandFailOnStatus 1 check_and_trap 'pwd' EXIT QUIT +} + +function test_check_and_trap() { + trap - EXIT QUIT + assertCommandSuccess check_and_trap 'echo' EXIT QUIT +} + +function test_check_and_force_trap_fail() { + trap echo EXIT + trap ls QUIT + assertCommandSuccess check_and_force_trap 'echo' EXIT QUIT +} + +function test_check_and_force_trap() { + trap - EXIT QUIT + assertCommandSuccess check_and_force_trap 'echo' EXIT QUIT +} + function test_insert_quotes_on_spaces(){ assertCommandSuccess insert_quotes_on_spaces this is "a test" assertEquals "this is \"a test\"" "$(cat $STDOUTF)" diff --git a/tests/utils/utils.sh b/tests/utils/utils.sh index b96000c..ed7cb8c 100644 --- a/tests/utils/utils.sh +++ b/tests/utils/utils.sh @@ -1,3 +1,4 @@ +OLD_CWD=${PWD} function cwdSetUp(){ ORIGIN_CWD=$(TMPDIR=/tmp mktemp -d -t junest-cwd.XXXXXXXXXX) cd $ORIGIN_CWD @@ -5,6 +6,7 @@ function cwdSetUp(){ function cwdTearDown(){ rm -rf $ORIGIN_CWD + cd $OLD_CWD } function junestSetUp(){ @@ -12,15 +14,12 @@ function junestSetUp(){ mkdir -p ${JUNEST_HOME}/etc/junest echo "JUNEST_ARCH=x86_64" > ${JUNEST_HOME}/etc/junest/info mkdir -p ${JUNEST_HOME}/etc/ca-certificates - trap - QUIT EXIT ABRT KILL TERM INT - trap "rm -rf ${JUNEST_HOME}" EXIT QUIT ABRT KILL TERM INT } function junestTearDown(){ # the CA directories are read only and can be deleted only by changing the mod [ -d ${JUNEST_HOME}/etc/ca-certificates ] && chmod -R +w ${JUNEST_HOME}/etc/ca-certificates rm -rf $JUNEST_HOME - trap - QUIT EXIT ABRT KILL TERM INT unset JUNEST_HOME } From 8e5531a27ac74b4997b297de7e955f37d7ef3925 Mon Sep 17 00:00:00 2001 From: Filippo Squillace Date: Mon, 3 Apr 2017 22:26:19 +0100 Subject: [PATCH 4/8] Issue #174: Add unit test for namespace --- lib/core/build.sh | 1 + lib/core/namespace.sh | 62 +++++++++++++++---- lib/core/proot.sh | 24 +++++--- tests/unit-tests/test-namespace.sh | 97 +++++++++++++++++++++++++++++- tests/unit-tests/test-proot.sh | 38 ++++++------ 5 files changed, 181 insertions(+), 41 deletions(-) diff --git a/lib/core/build.sh b/lib/core/build.sh index fd87d86..1945185 100644 --- a/lib/core/build.sh +++ b/lib/core/build.sh @@ -137,4 +137,5 @@ function check_env(){ info "Removing the previous packages..." JUNEST_HOME=${testdir} ${cmd} -f pacman --noconfirm -Rsn tcptraceroute tree iftop + JUNEST_HOME=${testdir} ${cmd} -u -- ls -la } diff --git a/lib/core/namespace.sh b/lib/core/namespace.sh index 7c3da65..6c44bbb 100644 --- a/lib/core/namespace.sh +++ b/lib/core/namespace.sh @@ -26,7 +26,7 @@ function _is_user_namespace_enabled() { return $NOT_EXISTING_FILE fi - if ! zgrep_cmd "CONFIG_USER_NS=y" $config_file + if ! zgrep_cmd -q "CONFIG_USER_NS=y" $config_file then return $NO_CONFIG_FOUND fi @@ -42,6 +42,35 @@ function _check_user_namespace() { set -e } +function _run_env_with_namespace(){ + local backend_args="$1" + shift + + if [[ "$1" != "" ]] + then + JUNEST_ENV=1 unshare_cmd --mount --user --map-root-user $GROOT $bindings $backend_args "$JUNEST_HOME" "${SH[@]}" "-c" "$(insert_quotes_on_spaces "${@}")" + else + JUNEST_ENV=1 unshare_cmd --mount --user --map-root-user $GROOT $bindings $backend_args "$JUNEST_HOME" "${SH[@]}" + fi +} + + +####################################### +# Run JuNest as normal user via user namespace. +# +# Globals: +# JUNEST_HOME (RO) : The JuNest home directory. +# GROOT (RO) : The groot program. +# SH (RO) : Contains the default command to run in JuNest. +# Arguments: +# backend_args ($1) : The arguments to pass to proot +# cmd ($2-?) : The command to run inside JuNest environment. +# Default command is defined by SH variable. +# Returns: +# Depends on the unshare command outcome. +# Output: +# - : The command output. +####################################### function run_env_as_user_with_namespace() { local backend_args="$1" shift @@ -59,9 +88,26 @@ function run_env_as_user_with_namespace() { local bindings=${RESULT} unset RESULT - unshare_cmd --mount --user --map-root-user $GROOT $bindings $backend_args "$JUNEST_HOME" + # TODO make sure to run the environment as normal user + _run_env_with_namespace "$backend_args" "$@" } +####################################### +# Run JuNest as fakeroot via user namespace. +# +# Globals: +# JUNEST_HOME (RO) : The JuNest home directory. +# GROOT (RO) : The groot program. +# SH (RO) : Contains the default command to run in JuNest. +# Arguments: +# backend_args ($1) : The arguments to pass to proot +# cmd ($2-?) : The command to run inside JuNest environment. +# Default command is defined by SH variable. +# Returns: +# Depends on the unshare command outcome. +# Output: +# - : The command output. +####################################### function run_env_as_fakeroot_with_namespace() { local backend_args="$1" shift @@ -69,19 +115,9 @@ function run_env_as_fakeroot_with_namespace() { copy_common_files - #mkdir -p "$chrootdir/$HOME" - #mkdir -p "$chrootdir/run/lock" - #chroot_add_mount --rbind /proc "$chrootdir/proc/" - #chroot_add_mount --rbind /dev "$chrootdir/dev/" - #chroot_add_mount --rbind /sys "$chrootdir/sys/" - #chroot_add_mount --rbind /tmp "$chrootdir/tmp/" - ## alternately create a new tmp istead of binding it: - ##chroot_add_mount -t tmpfs tmp "$chrootdir/tmp/" - #chroot_add_mount --rbind $HOME "$chrootdir/$HOME" provide_common_bindings local bindings=${RESULT} unset RESULT - JUNEST_ENV=1 unshare_cmd --mount --user --map-root-user $GROOT $bindings $backend_args "$JUNEST_HOME" - + _run_env_with_namespace "$backend_args" "$@" } diff --git a/lib/core/proot.sh b/lib/core/proot.sh index 3460f18..edad2c3 100644 --- a/lib/core/proot.sh +++ b/lib/core/proot.sh @@ -44,20 +44,23 @@ function _run_env_with_qemu(){ # Run JuNest as fakeroot. # # Globals: -# JUNEST_HOME (RO) : The JuNest home directory. -# EUID (RO) : The user ID. -# SH (RO) : Contains the default command to run in JuNest. +# JUNEST_HOME (RO) : The JuNest home directory. +# EUID (RO) : The user ID. +# SH (RO) : Contains the default command to run in JuNest. # Arguments: -# cmd ($@?) : The command to run inside JuNest environment. +# backend_args ($1) : The arguments to pass to proot +# cmd ($2-?) : The command to run inside JuNest environment. # Default command is defined by SH variable. # Returns: -# $ROOT_ACCESS_ERROR : If the user is the real root. +# $ROOT_ACCESS_ERROR : If the user is the real root. # Output: -# - : The command output. +# - : The command output. ####################################### function run_env_as_fakeroot(){ (( EUID == 0 )) && \ die_on_status $ROOT_ACCESS_ERROR "You cannot access with root privileges. Use --root option instead." + local backend_args="$1" + shift copy_common_files @@ -67,7 +70,7 @@ function run_env_as_fakeroot(){ # An alternative is via -S option: #_run_env_with_qemu "-S ${JUNEST_HOME} $1" "${@:2}" - _run_env_with_qemu "-0 ${bindings} -r ${JUNEST_HOME} $1" "${@:2}" + _run_env_with_qemu "-0 ${bindings} -r ${JUNEST_HOME} $backend_args" "$@" } ####################################### @@ -78,7 +81,8 @@ function run_env_as_fakeroot(){ # EUID (RO) : The user ID. # SH (RO) : Contains the default command to run in JuNest. # Arguments: -# cmd ($@?) : The command to run inside JuNest environment. +# backend_args ($1) : The arguments to pass to proot +# cmd ($2-?) : The command to run inside JuNest environment. # Default command is defined by SH variable. # Returns: # $ROOT_ACCESS_ERROR : If the user is the real root. @@ -88,6 +92,8 @@ function run_env_as_fakeroot(){ function run_env_as_user(){ (( EUID == 0 )) && \ die_on_status $ROOT_ACCESS_ERROR "You cannot access with root privileges. Use --root option instead." + local backend_args="$1" + shift # Files to bind are visible in `proot --help`. # This function excludes /etc/mtab file so that @@ -105,5 +111,5 @@ function run_env_as_user(){ local bindings=${RESULT} unset RESULT - _run_env_with_qemu "${bindings} -r ${JUNEST_HOME} $1" "${@:2}" + _run_env_with_qemu "${bindings} -r ${JUNEST_HOME} $backend_args" "$@" } diff --git a/tests/unit-tests/test-namespace.sh b/tests/unit-tests/test-namespace.sh index deb90d2..b1e52d5 100755 --- a/tests/unit-tests/test-namespace.sh +++ b/tests/unit-tests/test-namespace.sh @@ -5,8 +5,6 @@ JUNEST_ROOT=$(readlink -f $(dirname $0)/../..) source "$JUNEST_ROOT/tests/utils/utils.sh" source "$JUNEST_ROOT/lib/utils/utils.sh" -source "$JUNEST_ROOT/lib/core/common.sh" -source "$JUNEST_ROOT/lib/core/namespace.sh" # Disable the exiterr set +e @@ -15,14 +13,49 @@ function oneTimeSetUp(){ setUpUnitTests } +## Mock functions ## +function init_mocks() { + function unshare_cmd(){ + echo "unshare $@" + } +} + function setUp(){ cwdSetUp + junestSetUp + + # Attempt to source the files under test to revert variable + # overrides (i.e. SH variable) + source "$JUNEST_ROOT/lib/core/common.sh" + source "$JUNEST_ROOT/lib/core/namespace.sh" + set +e + + init_mocks } function tearDown(){ + junestTearDown cwdTearDown } +function _test_copy_common_files() { + [[ -e /etc/hosts ]] && assertEquals "$(cat /etc/hosts)" "$(cat ${JUNEST_HOME}/etc/hosts)" + [[ -e /etc/host.conf ]] && assertEquals "$(cat /etc/host.conf)" "$(cat ${JUNEST_HOME}/etc/host.conf)" + [[ -e /etc/nsswitch.conf ]] && assertEquals "$(cat /etc/nsswitch.conf)" "$(cat ${JUNEST_HOME}/etc/nsswitch.conf)" + [[ -e /etc/resolv.conf ]] && assertEquals "$(cat /etc/resolv.conf)" "$(cat ${JUNEST_HOME}/etc/resolv.conf)" +} + +function _test_copy_remaining_files() { + [[ -e /etc/hosts.equiv ]] && assertEquals "$(cat /etc/hosts.equiv)" "$(cat ${JUNEST_HOME}/etc/hosts.equiv)" + [[ -e /etc/netgroup ]] && assertEquals "$(cat /etc/netgroup)" "$(cat ${JUNEST_HOME}/etc/netgroup)" + [[ -e /etc/networks ]] && assertEquals "$(cat /etc/networks)" "$(cat ${JUNEST_HOME}/etc/networks)" + + [[ -e ${JUNEST_HOME}/etc/passwd ]] + assertEquals 0 $? + [[ -e ${JUNEST_HOME}/etc/group ]] + assertEquals 0 $? +} + function test_is_user_namespace_enabled_no_config_file(){ CONFIG_PROC_FILE="blah" CONFIG_BOOT_FILE="blah" @@ -45,4 +78,64 @@ function test_is_user_namespace_enabled_with_config(){ assertCommandSuccess _is_user_namespace_enabled } +function test_run_env_as_user_with_namespace() { + assertCommandSuccess run_env_as_user_with_namespace "" "" + assertEquals "unshare --mount --user --map-root-user $GROOT -b $HOME -b /tmp -b /proc -b /sys -b /dev $JUNEST_HOME /bin/sh --login" "$(cat $STDOUTF)" + + _test_copy_common_files + _test_copy_remaining_files +} + +function test_run_env_as_user_with_namespace_with_bindings() { + assertCommandSuccess run_env_as_user_with_namespace "-b /usr -b /lib:/tmp/lib" "" + assertEquals "unshare --mount --user --map-root-user $GROOT -b $HOME -b /tmp -b /proc -b /sys -b /dev -b /usr -b /lib:/tmp/lib $JUNEST_HOME /bin/sh --login" "$(cat $STDOUTF)" + + _test_copy_common_files + _test_copy_remaining_files +} + +function test_run_env_as_user_with_namespace_with_command() { + assertCommandSuccess run_env_as_user_with_namespace "" "ls -la" + assertEquals "unshare --mount --user --map-root-user $GROOT -b $HOME -b /tmp -b /proc -b /sys -b /dev $JUNEST_HOME /bin/sh --login -c \"ls -la\"" "$(cat $STDOUTF)" + + _test_copy_common_files + _test_copy_remaining_files +} + +function test_run_env_as_user_with_namespace_with_bindings_and_command() { + assertCommandSuccess run_env_as_user_with_namespace "-b /usr -b /lib:/tmp/lib" "ls -la" + assertEquals "unshare --mount --user --map-root-user $GROOT -b $HOME -b /tmp -b /proc -b /sys -b /dev -b /usr -b /lib:/tmp/lib $JUNEST_HOME /bin/sh --login -c \"ls -la\"" "$(cat $STDOUTF)" + + _test_copy_common_files + _test_copy_remaining_files +} + +function test_run_env_as_fakeroot_with_namespace() { + assertCommandSuccess run_env_as_fakeroot_with_namespace "" "" + assertEquals "unshare --mount --user --map-root-user $GROOT -b $HOME -b /tmp -b /proc -b /sys -b /dev $JUNEST_HOME /bin/sh --login" "$(cat $STDOUTF)" + + _test_copy_common_files +} + +function test_run_env_as_fakeroot_with_namespace_with_bindings() { + assertCommandSuccess run_env_as_fakeroot_with_namespace "-b /usr -b /lib:/tmp/lib" "" + assertEquals "unshare --mount --user --map-root-user $GROOT -b $HOME -b /tmp -b /proc -b /sys -b /dev -b /usr -b /lib:/tmp/lib $JUNEST_HOME /bin/sh --login" "$(cat $STDOUTF)" + + _test_copy_common_files +} + +function test_run_env_as_fakeroot_with_namespace_with_command() { + assertCommandSuccess run_env_as_fakeroot_with_namespace "" "ls -la" + assertEquals "unshare --mount --user --map-root-user $GROOT -b $HOME -b /tmp -b /proc -b /sys -b /dev $JUNEST_HOME /bin/sh --login -c \"ls -la\"" "$(cat $STDOUTF)" + + _test_copy_common_files +} + +function test_run_env_as_fakeroot_with_namespace_with_bindings_and_command() { + assertCommandSuccess run_env_as_fakeroot_with_namespace "-b /usr -b /lib:/tmp/lib" "ls -la" + assertEquals "unshare --mount --user --map-root-user $GROOT -b $HOME -b /tmp -b /proc -b /sys -b /dev -b /usr -b /lib:/tmp/lib $JUNEST_HOME /bin/sh --login -c \"ls -la\"" "$(cat $STDOUTF)" + + _test_copy_common_files +} + source $JUNEST_ROOT/tests/utils/shunit2 diff --git a/tests/unit-tests/test-proot.sh b/tests/unit-tests/test-proot.sh index fdc83b2..6640035 100755 --- a/tests/unit-tests/test-proot.sh +++ b/tests/unit-tests/test-proot.sh @@ -29,6 +29,24 @@ function tearDown(){ cwdTearDown } +function _test_copy_common_files() { + [[ -e /etc/hosts ]] && assertEquals "$(cat /etc/hosts)" "$(cat ${JUNEST_HOME}/etc/hosts)" + [[ -e /etc/host.conf ]] && assertEquals "$(cat /etc/host.conf)" "$(cat ${JUNEST_HOME}/etc/host.conf)" + [[ -e /etc/nsswitch.conf ]] && assertEquals "$(cat /etc/nsswitch.conf)" "$(cat ${JUNEST_HOME}/etc/nsswitch.conf)" + [[ -e /etc/resolv.conf ]] && assertEquals "$(cat /etc/resolv.conf)" "$(cat ${JUNEST_HOME}/etc/resolv.conf)" +} + +function _test_copy_remaining_files() { + [[ -e /etc/hosts.equiv ]] && assertEquals "$(cat /etc/hosts.equiv)" "$(cat ${JUNEST_HOME}/etc/hosts.equiv)" + [[ -e /etc/netgroup ]] && assertEquals "$(cat /etc/netgroup)" "$(cat ${JUNEST_HOME}/etc/netgroup)" + [[ -e /etc/networks ]] && assertEquals "$(cat /etc/networks)" "$(cat ${JUNEST_HOME}/etc/networks)" + + [[ -e ${JUNEST_HOME}/etc/passwd ]] + assertEquals 0 $? + [[ -e ${JUNEST_HOME}/etc/group ]] + assertEquals 0 $? +} + function test_run_env_as_user(){ _run_env_with_qemu() { echo $@ @@ -40,19 +58,8 @@ function test_run_env_as_user(){ assertCommandSuccess run_env_as_user "-k 3.10" assertEquals "-b $HOME -b /tmp -b /proc -b /sys -b /dev -r ${JUNEST_HOME} -k 3.10" "$(cat $STDOUTF)" - [[ -e /etc/hosts ]] && assertEquals "$(cat /etc/hosts)" "$(cat ${JUNEST_HOME}/etc/hosts)" - [[ -e /etc/host.conf ]] && assertEquals "$(cat /etc/host.conf)" "$(cat ${JUNEST_HOME}/etc/host.conf)" - [[ -e /etc/nsswitch.conf ]] && assertEquals "$(cat /etc/nsswitch.conf)" "$(cat ${JUNEST_HOME}/etc/nsswitch.conf)" - [[ -e /etc/resolv.conf ]] && assertEquals "$(cat /etc/resolv.conf)" "$(cat ${JUNEST_HOME}/etc/resolv.conf)" - - [[ -e /etc/hosts.equiv ]] && assertEquals "$(cat /etc/hosts.equiv)" "$(cat ${JUNEST_HOME}/etc/hosts.equiv)" - [[ -e /etc/netgroup ]] && assertEquals "$(cat /etc/netgroup)" "$(cat ${JUNEST_HOME}/etc/netgroup)" - - [[ -e /etc/passwd ]] - assertEquals 0 $? - [[ -e /etc/group ]] - assertEquals 0 $? - + _test_copy_common_files + _test_copy_remaining_files } function test_run_env_as_fakeroot(){ @@ -66,10 +73,7 @@ function test_run_env_as_fakeroot(){ assertCommandSuccess run_env_as_fakeroot "-k 3.10" assertEquals "-0 -b ${HOME} -b /tmp -b /proc -b /sys -b /dev -r ${JUNEST_HOME} -k 3.10" "$(cat $STDOUTF)" - [[ -e /etc/hosts ]] && assertEquals "$(cat /etc/hosts)" "$(cat ${JUNEST_HOME}/etc/hosts)" - [[ -e /etc/host.conf ]] && assertEquals "$(cat /etc/host.conf)" "$(cat ${JUNEST_HOME}/etc/host.conf)" - [[ -e /etc/nsswitch.conf ]] && assertEquals "$(cat /etc/nsswitch.conf)" "$(cat ${JUNEST_HOME}/etc/nsswitch.conf)" - [[ -e /etc/resolv.conf ]] && assertEquals "$(cat /etc/resolv.conf)" "$(cat ${JUNEST_HOME}/etc/resolv.conf)" + _test_copy_common_files } function test_run_env_with_quotes(){ From fcb4a36f3090a75ee5856fb9b3335d31390bc679 Mon Sep 17 00:00:00 2001 From: Filippo Squillace Date: Fri, 7 Apr 2017 22:38:47 +0100 Subject: [PATCH 5/8] Issue #174: Add option -n in groot and umount directories in order --- bin/groot | 30 ++++++++++++++++++++---------- lib/core/namespace.sh | 7 +++++-- tests/unit-tests/test-groot.sh | 24 ++++++++++++++++++++++++ tests/unit-tests/test-namespace.sh | 16 ++++++++-------- 4 files changed, 57 insertions(+), 20 deletions(-) diff --git a/bin/groot b/bin/groot index 1d6e26b..97ccfef 100755 --- a/bin/groot +++ b/bin/groot @@ -19,6 +19,11 @@ UMOUNT=umount MOUNTPOINT=mountpoint MKDIR=mkdir TOUCH=touch +CUT=cut +SORT=sort +UNIQ=uniq +GREP=grep +MOUNTS_FILE=/proc/self/mounts NOT_EXISTING_FILE=103 NOT_ABSOLUTE_PATH=111 @@ -29,28 +34,29 @@ source "${JUNEST_BASE}/lib/utils/utils.sh" ################################ MAIN FUNCTIONS ########################### -function chroot_add_mount() { - $MOUNT "$@" && CHROOT_ACTIVE_MOUNTS=("${@: -1}" "${CHROOT_ACTIVE_MOUNTS[@]}") -} - function chroot_teardown() { - $UMOUNT "${CHROOT_ACTIVE_MOUNTS[@]}" - unset CHROOT_ACTIVE_MOUNTS + # Remove all mounts starting from the most nested ones. + # Suffix the CHROOTDIR with / to avoid umounting directories not belonging + # to CHROOTDIR. + for mp in $($GREP "${CHROOTDIR%/}/" $MOUNTS_FILE | $CUT -f2 -d' ' | $SORT -r | $UNIQ) + do + $UMOUNT $mp + done + $UMOUNT ${CHROOTDIR%/} } function chroot_maybe_add_mount() { local cond=$1 shift if eval "$cond"; then - chroot_add_mount "$@" + $MOUNT "$@" return fi return 1 } function chroot_setup() { - CHROOT_ACTIVE_MOUNTS=() - check_and_trap 'chroot_teardown' EXIT + $OPT_NO_UMOUNT || check_and_trap 'chroot_teardown' EXIT if ! chroot_maybe_add_mount "! $MOUNTPOINT -q '$CHROOTDIR'" --bind "$CHROOTDIR" "$CHROOTDIR" then @@ -73,7 +79,7 @@ function chroot_setup() { fi create_node "${host_path}" "${CHROOTDIR}${guest_path}" - chroot_add_mount --rbind "${host_path}" "${CHROOTDIR}${guest_path}" + $MOUNT --rbind "${host_path}" "${CHROOTDIR}${guest_path}" done } @@ -112,6 +118,8 @@ Options: path in the guest rootfs but users can specify any other location with the syntax: -b :. This option can be invoked multiple times and the paths specified must be absolutes. + -n, --no-umount + Do not umount after chroot session finished. -h, --help Print this help message @@ -129,12 +137,14 @@ version() { function parse_arguments() { OPT_BIND=false BINDINGS=() + OPT_NO_UMOUNT=false OPT_HELP=false OPT_VERSION=false for opt in "$@" do case "$1" in -b|--bind) OPT_BIND=true ; shift ; BINDINGS+=("$1") ; shift ;; + -n|--no-umount) OPT_NO_UMOUNT=true ; shift ;; -h|--help) OPT_HELP=true ; shift ;; -V|--version) OPT_VERSION=true ; shift ;; -*) die "Invalid option $1" ;; diff --git a/lib/core/namespace.sh b/lib/core/namespace.sh index 6c44bbb..8854c43 100644 --- a/lib/core/namespace.sh +++ b/lib/core/namespace.sh @@ -46,11 +46,14 @@ function _run_env_with_namespace(){ local backend_args="$1" shift + # Use option -n in groot because umount do not work sometimes. + # As soon as the process terminates, the namespace + # will terminate too with its own mounted directories. if [[ "$1" != "" ]] then - JUNEST_ENV=1 unshare_cmd --mount --user --map-root-user $GROOT $bindings $backend_args "$JUNEST_HOME" "${SH[@]}" "-c" "$(insert_quotes_on_spaces "${@}")" + JUNEST_ENV=1 unshare_cmd --mount --user --map-root-user $GROOT -n $bindings $backend_args "$JUNEST_HOME" "${SH[@]}" "-c" "$(insert_quotes_on_spaces "${@}")" else - JUNEST_ENV=1 unshare_cmd --mount --user --map-root-user $GROOT $bindings $backend_args "$JUNEST_HOME" "${SH[@]}" + JUNEST_ENV=1 unshare_cmd --mount --user --map-root-user $GROOT -n $bindings $backend_args "$JUNEST_HOME" "${SH[@]}" fi } diff --git a/tests/unit-tests/test-groot.sh b/tests/unit-tests/test-groot.sh index 02f05ec..472e027 100755 --- a/tests/unit-tests/test-groot.sh +++ b/tests/unit-tests/test-groot.sh @@ -133,5 +133,29 @@ function test_groot_with_bind_and_command(){ assertEquals 0 $? assertEquals "$(echo -e "check_and_trap(chroot_teardown EXIT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(--rbind /tmp chrootdir/home/tmp)\nmount(--rbind /dev chrootdir/dev)\nchroot(chrootdir ls -la -h)")" "$(cat $STDOUTF)" } +function test_groot_with_bind_no_umount(){ + assertCommandSuccess main -n chrootdir + assertEquals "$(echo -e "mountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nchroot(chrootdir)")" "$(cat $STDOUTF)" +} +function test_groot_with_chroot_teardown(){ + echo -e "1 /home/mychroot/dev\n1 /home/mychroot/proc/fs1\n1 /home/mychroot\n1 /home/mychroot-no/dev\n1 /home/mychroot/dev/shm\n1 /home/mychroot/proc\n" > ./mounts + MOUNTS_FILE=./mounts + CHROOTDIR=/home/mychroot assertCommandSuccess chroot_teardown + assertEquals "$(echo -e "umount(/home/mychroot/proc/fs1) +umount(/home/mychroot/proc) +umount(/home/mychroot/dev/shm) +umount(/home/mychroot/dev) +umount(/home/mychroot)")" "$(cat $STDOUTF)" +} +function test_groot_with_chroot_teardown_with_trailing_slash(){ + echo -e "1 /home/mychroot/dev\n1 /home/mychroot/proc/fs1\n1 /home/mychroot\n1 /home/mychroot-no/dev\n1 /home/mychroot/dev/shm\n1 /home/mychroot/proc\n" > ./mounts + MOUNTS_FILE=./mounts + CHROOTDIR=/home/mychroot assertCommandSuccess chroot_teardown + assertEquals "$(echo -e "umount(/home/mychroot/proc/fs1) +umount(/home/mychroot/proc) +umount(/home/mychroot/dev/shm) +umount(/home/mychroot/dev) +umount(/home/mychroot)")" "$(cat $STDOUTF)" +} source $(dirname $0)/../utils/shunit2 diff --git a/tests/unit-tests/test-namespace.sh b/tests/unit-tests/test-namespace.sh index b1e52d5..e09db0a 100755 --- a/tests/unit-tests/test-namespace.sh +++ b/tests/unit-tests/test-namespace.sh @@ -80,7 +80,7 @@ function test_is_user_namespace_enabled_with_config(){ function test_run_env_as_user_with_namespace() { assertCommandSuccess run_env_as_user_with_namespace "" "" - assertEquals "unshare --mount --user --map-root-user $GROOT -b $HOME -b /tmp -b /proc -b /sys -b /dev $JUNEST_HOME /bin/sh --login" "$(cat $STDOUTF)" + assertEquals "unshare --mount --user --map-root-user $GROOT -n -b $HOME -b /tmp -b /proc -b /sys -b /dev $JUNEST_HOME /bin/sh --login" "$(cat $STDOUTF)" _test_copy_common_files _test_copy_remaining_files @@ -88,7 +88,7 @@ function test_run_env_as_user_with_namespace() { function test_run_env_as_user_with_namespace_with_bindings() { assertCommandSuccess run_env_as_user_with_namespace "-b /usr -b /lib:/tmp/lib" "" - assertEquals "unshare --mount --user --map-root-user $GROOT -b $HOME -b /tmp -b /proc -b /sys -b /dev -b /usr -b /lib:/tmp/lib $JUNEST_HOME /bin/sh --login" "$(cat $STDOUTF)" + assertEquals "unshare --mount --user --map-root-user $GROOT -n -b $HOME -b /tmp -b /proc -b /sys -b /dev -b /usr -b /lib:/tmp/lib $JUNEST_HOME /bin/sh --login" "$(cat $STDOUTF)" _test_copy_common_files _test_copy_remaining_files @@ -96,7 +96,7 @@ function test_run_env_as_user_with_namespace_with_bindings() { function test_run_env_as_user_with_namespace_with_command() { assertCommandSuccess run_env_as_user_with_namespace "" "ls -la" - assertEquals "unshare --mount --user --map-root-user $GROOT -b $HOME -b /tmp -b /proc -b /sys -b /dev $JUNEST_HOME /bin/sh --login -c \"ls -la\"" "$(cat $STDOUTF)" + assertEquals "unshare --mount --user --map-root-user $GROOT -n -b $HOME -b /tmp -b /proc -b /sys -b /dev $JUNEST_HOME /bin/sh --login -c \"ls -la\"" "$(cat $STDOUTF)" _test_copy_common_files _test_copy_remaining_files @@ -104,7 +104,7 @@ function test_run_env_as_user_with_namespace_with_command() { function test_run_env_as_user_with_namespace_with_bindings_and_command() { assertCommandSuccess run_env_as_user_with_namespace "-b /usr -b /lib:/tmp/lib" "ls -la" - assertEquals "unshare --mount --user --map-root-user $GROOT -b $HOME -b /tmp -b /proc -b /sys -b /dev -b /usr -b /lib:/tmp/lib $JUNEST_HOME /bin/sh --login -c \"ls -la\"" "$(cat $STDOUTF)" + assertEquals "unshare --mount --user --map-root-user $GROOT -n -b $HOME -b /tmp -b /proc -b /sys -b /dev -b /usr -b /lib:/tmp/lib $JUNEST_HOME /bin/sh --login -c \"ls -la\"" "$(cat $STDOUTF)" _test_copy_common_files _test_copy_remaining_files @@ -112,28 +112,28 @@ function test_run_env_as_user_with_namespace_with_bindings_and_command() { function test_run_env_as_fakeroot_with_namespace() { assertCommandSuccess run_env_as_fakeroot_with_namespace "" "" - assertEquals "unshare --mount --user --map-root-user $GROOT -b $HOME -b /tmp -b /proc -b /sys -b /dev $JUNEST_HOME /bin/sh --login" "$(cat $STDOUTF)" + assertEquals "unshare --mount --user --map-root-user $GROOT -n -b $HOME -b /tmp -b /proc -b /sys -b /dev $JUNEST_HOME /bin/sh --login" "$(cat $STDOUTF)" _test_copy_common_files } function test_run_env_as_fakeroot_with_namespace_with_bindings() { assertCommandSuccess run_env_as_fakeroot_with_namespace "-b /usr -b /lib:/tmp/lib" "" - assertEquals "unshare --mount --user --map-root-user $GROOT -b $HOME -b /tmp -b /proc -b /sys -b /dev -b /usr -b /lib:/tmp/lib $JUNEST_HOME /bin/sh --login" "$(cat $STDOUTF)" + assertEquals "unshare --mount --user --map-root-user $GROOT -n -b $HOME -b /tmp -b /proc -b /sys -b /dev -b /usr -b /lib:/tmp/lib $JUNEST_HOME /bin/sh --login" "$(cat $STDOUTF)" _test_copy_common_files } function test_run_env_as_fakeroot_with_namespace_with_command() { assertCommandSuccess run_env_as_fakeroot_with_namespace "" "ls -la" - assertEquals "unshare --mount --user --map-root-user $GROOT -b $HOME -b /tmp -b /proc -b /sys -b /dev $JUNEST_HOME /bin/sh --login -c \"ls -la\"" "$(cat $STDOUTF)" + assertEquals "unshare --mount --user --map-root-user $GROOT -n -b $HOME -b /tmp -b /proc -b /sys -b /dev $JUNEST_HOME /bin/sh --login -c \"ls -la\"" "$(cat $STDOUTF)" _test_copy_common_files } function test_run_env_as_fakeroot_with_namespace_with_bindings_and_command() { assertCommandSuccess run_env_as_fakeroot_with_namespace "-b /usr -b /lib:/tmp/lib" "ls -la" - assertEquals "unshare --mount --user --map-root-user $GROOT -b $HOME -b /tmp -b /proc -b /sys -b /dev -b /usr -b /lib:/tmp/lib $JUNEST_HOME /bin/sh --login -c \"ls -la\"" "$(cat $STDOUTF)" + assertEquals "unshare --mount --user --map-root-user $GROOT -n -b $HOME -b /tmp -b /proc -b /sys -b /dev -b /usr -b /lib:/tmp/lib $JUNEST_HOME /bin/sh --login -c \"ls -la\"" "$(cat $STDOUTF)" _test_copy_common_files } From b817aa8445ace8c5e58cdff88e7f0e0a275a8e15 Mon Sep 17 00:00:00 2001 From: Filippo Squillace Date: Sun, 9 Apr 2017 17:34:43 +0100 Subject: [PATCH 6/8] Issue #174: Add -g option for Groot and integ tests for user namespace --- .travis.yml | 11 ++- README.md | 77 +++++++++++++++++--- bin/groot | 2 +- bin/junest | 77 +++++++------------- lib/checks/check.sh | 62 ++++++++++++++++ lib/core/build.sh | 46 +----------- lib/core/chroot.sh | 87 +++++++++++++++++------ lib/core/common.sh | 110 +++++++++++++++++++++-------- lib/core/namespace.sh | 48 +++---------- lib/core/proot.sh | 5 +- tests/unit-tests/test-chroot.sh | 45 ++++++++---- tests/unit-tests/test-common.sh | 46 ++++++++---- tests/unit-tests/test-groot.sh | 20 +++--- tests/unit-tests/test-junest.sh | 86 ++++++++-------------- tests/unit-tests/test-namespace.sh | 44 +++--------- 15 files changed, 433 insertions(+), 333 deletions(-) create mode 100755 lib/checks/check.sh diff --git a/.travis.yml b/.travis.yml index a1b6078..4e4536a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,9 +17,14 @@ install: script: - bash --version - - bash ./tests/unit-tests/unit-tests.sh - bash ./tests/checkstyle/checkstyle.sh - - junest --check ./bin/junest + - bash ./tests/unit-tests/unit-tests.sh + + # Multiple tests against different execution modes: + - junest -f -- ${PWD}/lib/checks/check.sh + - junest -u -- ${PWD}/lib/checks/check.sh --skip-aur-tests + - sudo -E ${PWD}/bin/junest -g -- ${PWD}/lib/checks/check.sh --run-root-tests - yes | junest --delete - - JUNEST_HOME=~/.junest-arm junest --check ./bin/junest --skip-root-tests + + - JUNEST_HOME=~/.junest-arm junest -f -- ./lib/checks/check.sh - yes | JUNEST_HOME=~/.junest-arm junest --delete diff --git a/README.md b/README.md index e747c4e..492e284 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ The Arch Linux based distro that runs upon any Linux distros without root access |Project Status|Donation|Communication| |:------------:|:------:|:-----------:| -| [![Build status](https://api.travis-ci.org/fsquillace/junest.png?branch=master)](https://travis-ci.org/fsquillace/junest) [![OpenHub](https://www.openhub.net/p/junest/widgets/project_thin_badge.gif)](https://www.openhub.net/p/junest) | [![PayPal](https://img.shields.io/badge/PayPal-Donate%20a%20beer-blue.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=8LEHQKBCYTACY) [![Gratipay](https://img.shields.io/badge/Gratipay-Donate%20to%20JuNest-green.svg)](https://gratipay.com/junest/) | [![Join the gitter chat at https://gitter.im/fsquillace/junest](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/fsquillace/junest?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Join the IRC chat at https://webchat.freenode.net/?channels=junest](https://img.shields.io/badge/IRC-JuNest-yellow.svg)](https://webchat.freenode.net/?channels=junest) [![Join the group at https://groups.google.com/d/forum/junest](https://img.shields.io/badge/Google Groups-JuNest-red.svg)](https://groups.google.com/d/forum/junest) [![RSS](https://img.shields.io/badge/RSS-News-orange.svg)](http://fsquillace.github.io/junest-site/feed.xml) | +| [![Build status](https://api.travis-ci.org/fsquillace/junest.png?branch=master)](https://travis-ci.org/fsquillace/junest) [![OpenHub](https://www.openhub.net/p/junest/widgets/project_thin_badge.gif)](https://www.openhub.net/p/junest) | [![PayPal](https://img.shields.io/badge/PayPal-Donate%20a%20beer-blue.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=8LEHQKBCYTACY) [![Gratipay](https://img.shields.io/badge/Gratipay-Donate%20to%20JuNest-green.svg)](https://gratipay.com/junest/) | [![Join the gitter chat at https://gitter.im/fsquillace/junest](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/fsquillace/junest?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Join the IRC chat at https://webchat.freenode.net/?channels=junest](https://img.shields.io/badge/IRC-JuNest-yellow.svg)](https://webchat.freenode.net/?channels=junest) [![Join the group at https://groups.google.com/d/forum/junest](https://img.shields.io/badge/GoogleGroups-JuNest-red.svg)](https://groups.google.com/d/forum/junest) [![RSS](https://img.shields.io/badge/RSS-News-orange.svg)](http://fsquillace.github.io/junest-site/feed.xml) | **Table of Contents** - [Description](#description) @@ -48,19 +48,62 @@ JuNest follows the [Arch Linux philosophy](https://wiki.archlinux.org/index.php/ Quickstart ========== -There are three different ways you can run JuNest: + +Backend programs +---------------- +There are three different ways you can run JuNest depending on the backend program you decide to use. + +### PRoot based ### +[Proot](https://wiki.archlinux.org/index.php/Proot) represents the default +program used for accessing to the JuNest environments. +The main reason to choose Proot as default backend program is because +it represents a portable solution that works well in most of GNU/Linux distros available. +One of the major drawbacks is the fact that Proot is not officially +supported anymore, therefore, Proot bugs may no longer be fixed. + +In order to run JuNest via Proot: - As normal user - Allow to make basic operations: ```junest``` - As fakeroot - Allow to install/remove packages: ```junest -f``` -- As root - Allow to have fully root privileges inside JuNest environment (you need to be root for executing this): ```junest -r``` +### Linux namespaces based ### +The [Linux namespaces](http://man7.org/linux/man-pages/man7/namespaces.7.html) +represents the next generation backend program for JuNest. +The major drawback about the namespace is portability, as certain requirements +need to be satisfied: 1) Only starting from Linux 3.8, unprivileged processes can +create the required user and mount namespaces. +2) Moreover, the Linux kernel distro must have the user namespace enabled. +Hopefully, in the future the major GNU/Linux distros will start enabling such feature by default. +For instance, Ubuntu (version 14.04+) already has such feature enabled. +In order to run JuNest via Linux namespaces: + +- As fakeroot - Allow to install/remove packages: ```junest -u``` + +### Chroot based ### +This solution suits only for privileged users. JuNest provides the possibility +to run the environment via `chroot` program. +In particular, it uses a special program called `GRoot`, an enhanced `chroot` +wrapper that allows to bind mount directories specified by the user, such as +/proc, /sys, /dev, /tmp and $HOME, before +executing any programs inside the JuNest sandbox. In case the mounting will not +work, JuNest is even providing the possibility to run the environment directly via +the pure `chroot` command. + +In order to run JuNest via `chroot` solutions: + +- As root via `GRoot` - Allow to have fully root privileges inside JuNest environment (you need to be root for executing this): ```junest -g``` + +- As root via `chroot` - Allow to have fully root privileges inside JuNest environment (you need to be root for executing this): ```junest -r``` + +After running JuNest +-------------------- If the JuNest image has not been downloaded yet, the script will download -the image and will place it to the default directory ~/.junest. -You can change the default directory by changing the environment variable *JUNEST\_HOME*. +the image from the repository and will place it to the default directory `~/.junest`. +You can change the default directory by changing the environment variable `JUNEST_HOME`. -If you are new on Archlinux and you are not familiar with *pacman* package manager +If you are new on Arch Linux and you are not familiar with `pacman` package manager visit the [pacman rosetta page](https://wiki.archlinux.org/index.php/Pacman_Rosetta). Installation @@ -90,7 +133,7 @@ Just clone the JuNest repo somewhere (for example in ~/.local/share/junest): ### Installation using AUR (Arch Linux only) ### If you are using an Arch Linux system you can, alternatively, install JuNest from the [AUR repository](https://aur.archlinux.org/): - yogurt -S junest-git + yaourt -S junest-git export PATH=/opt/junest/bin:$PATH ## Method two ## @@ -98,7 +141,7 @@ Alternatively, another installation method would be to directly download the JuN ARCH= mkdir ~/.junest - curl https://dl.dropboxusercontent.com/u/42449030/junest/junest-${ARCH}.tar.gz | tar -xz -C ~/.junest + curl https://s3-eu-west-1.amazonaws.com/junest-repo/junest-${ARCH}.tar.gz | tar -xz -C ~/.junest export PATH=~/.junest/opt/junest/bin:$PATH Advanced usage @@ -141,12 +184,12 @@ To bind a host directory to a guest location, you can use proot arguments: junest -p "-b /mnt/mydata:/home/user/mydata" -Check out the proot options with: +This will works with PRoot, Namespace and GRoot backend programs. +Check out the backend program options by passing `--help` option: - junest -p "--help" + junest [-u|-g] -p "--help" ## Systemd integration ## - Although JuNest has not been designed to be a complete container, it is even possible to virtualize the process tree thanks to the [systemd container](https://wiki.archlinux.org/index.php/Systemd-nspawn). The JuNest containter allows to run services inside the container that can be @@ -345,6 +388,18 @@ Troubleshooting > since JuNest will try to preserve the JuNest environment by assigning ownership > of the files to the real user. +## Not enabled User namespace or kernel too old ## + +> **Q**: Why do I get warning when I run JuNest via Linux namespaces? + + $> junest -u + User namespace is not enabled or Kernel too old (<3.8). Proceeding anyway... + +> **A**: This means that JuNest detected that the host OS either +> does not have a newer Linux version or the user namespace is not enabled. +> JuNest does not stop the execution of the program but it attempts to run it +> anyway. Try to use Proot as backend program in case is not possible to invoke namespaces. + More documentation ================== There are additional tutorials in the diff --git a/bin/groot b/bin/groot index 97ccfef..4033705 100755 --- a/bin/groot +++ b/bin/groot @@ -56,7 +56,7 @@ function chroot_maybe_add_mount() { } function chroot_setup() { - $OPT_NO_UMOUNT || check_and_trap 'chroot_teardown' EXIT + $OPT_NO_UMOUNT || check_and_trap 'chroot_teardown' QUIT EXIT ABRT KILL TERM INT if ! chroot_maybe_add_mount "! $MOUNTPOINT -q '$CHROOTDIR'" --bind "$CHROOTDIR" "$CHROOTDIR" then diff --git a/bin/junest b/bin/junest index 9d2a527..a2b96e5 100755 --- a/bin/junest +++ b/bin/junest @@ -36,18 +36,17 @@ usage() { echo echo -e "Access options:" echo -e "-f, --fakeroot Run $NAME with fakeroot privileges" - echo -e "-r, --root Run $NAME with root privileges via GRoot" + echo -e "-g, --groot Run $NAME with root privileges via GRoot" + echo -e "-r, --root Run $NAME with root privileges via classic chroot" + echo -e "-u, --namespace Use Linux Namespace (with GRoot) instead of PRoot" echo -e "-p, --backend-args Arguments for backend program (PRoot or GRoot)" echo -e " ($CMD -p \"--help\" to check out the PRoot options" - echo -e " $CMD -u -p \"--help\" to check out the GRoot options)" - echo -e "-u, --user-namespace Use Linux User Namespace instead of PRoot" + echo -e " $CMD -g -p \"--help\" to check out the GRoot options" + echo -e " $CMD -r -p \"--help\" to check out the chroot options)" echo echo -e "Building options:" echo -e "-b, --build-image Build a $NAME image (must run in ArchLinux)" echo -e "-n, --disable-validation Disable the $NAME image validation" - echo -e "-c, --check <${CMD}_script> Validate the env located in ${JUNEST_HOME}" - echo -e " using ${CMD}_script. This will alterate the environment" - echo -e "-s, --skip-root-tests Skip the root tests during the validation process" echo echo -e "General options:" echo -e "-h, --help Show this help message" @@ -60,33 +59,17 @@ version() { check_cli(){ if $OPT_BUILD_IMAGE - then - if $OPT_DELETE || $OPT_HELP || $OPT_VERSION || $OPT_SETUP_FROM_FILE || \ - $OPT_FAKEROOT || $OPT_ROOT || $OPT_CHECK - then - die "The build image option must be used exclusively" - fi - fi - if $OPT_SKIP_ROOT_TEST then if $OPT_DELETE || $OPT_HELP || $OPT_VERSION || $OPT_SETUP_FROM_FILE || \ $OPT_FAKEROOT || $OPT_ROOT then - die "The skip root tests option must be used with either build image or check options" - fi - fi - if $OPT_CHECK - then - if $OPT_DELETE || $OPT_HELP || $OPT_VERSION || $OPT_SETUP_FROM_FILE || \ - $OPT_FAKEROOT || $OPT_ROOT || $OPT_BUILD_IMAGE - then - die "The validation image option must be used exclusively" + die "The build image option must be used exclusively" fi fi if $OPT_DISABLE_VALIDATION then if $OPT_DELETE || $OPT_HELP || $OPT_VERSION || $OPT_SETUP_FROM_FILE || \ - $OPT_FAKEROOT || $OPT_ROOT || $OPT_CHECK + $OPT_FAKEROOT || $OPT_ROOT then die "The disable validation option must be used with the build image option only" fi @@ -94,7 +77,7 @@ check_cli(){ if $OPT_DELETE then if $OPT_BUILD_IMAGE || $OPT_HELP || $OPT_VERSION || $OPT_SETUP_FROM_FILE || \ - $OPT_FAKEROOT || $OPT_ROOT || $OPT_DISABLE_VALIDATION || $OPT_CHECK + $OPT_FAKEROOT || $OPT_ROOT || $OPT_DISABLE_VALIDATION then die "The $NAME delete option must be used exclusively" fi @@ -102,7 +85,7 @@ check_cli(){ if $OPT_HELP then if $OPT_BUILD_IMAGE || $OPT_DELETE || $OPT_VERSION || $OPT_SETUP_FROM_FILE || \ - $OPT_FAKEROOT || $OPT_ROOT || $OPT_DISABLE_VALIDATION || $OPT_CHECK + $OPT_FAKEROOT || $OPT_ROOT || $OPT_DISABLE_VALIDATION then die "The $NAME help option must be used exclusively" fi @@ -110,7 +93,7 @@ check_cli(){ if $OPT_VERSION then if $OPT_BUILD_IMAGE || $OPT_DELETE || $OPT_HELP || $OPT_SETUP_FROM_FILE || \ - $OPT_FAKEROOT || $OPT_ROOT || $OPT_DISABLE_VALIDATION || $OPT_CHECK + $OPT_FAKEROOT || $OPT_ROOT || $OPT_DISABLE_VALIDATION then die "The $NAME version option must be used exclusively" fi @@ -122,7 +105,7 @@ check_cli(){ if $OPT_BACKEND_ARGS || $OPT_ARCH then if $OPT_BUILD_IMAGE || $OPT_DELETE || $OPT_HELP || \ - $OPT_ROOT || $OPT_VERSION || $OPT_DISABLE_VALIDATION || $OPT_CHECK + $OPT_ROOT || $OPT_VERSION || $OPT_DISABLE_VALIDATION then die "Invalid syntax: Proot and arch args are not allowed with the other options" fi @@ -130,7 +113,7 @@ check_cli(){ if [ "$ARGS" != "" ] then if $OPT_BUILD_IMAGE || $OPT_DELETE || $OPT_HELP || $OPT_SETUP_FROM_FILE || \ - $OPT_VERSION || $OPT_DISABLE_VALIDATION || $OPT_CHECK + $OPT_VERSION || $OPT_DISABLE_VALIDATION then die "No arguments are needed. For the CLI syntax run: $CMD --help" fi @@ -145,6 +128,7 @@ function parse_arguments(){ IMAGE_FILE="" OPT_FAKEROOT=false OPT_ROOT=false + OPT_GROOT=false OPT_USER_NAMESPACE=false OPT_BACKEND_ARGS=false BACKEND_ARGS="" @@ -152,9 +136,7 @@ function parse_arguments(){ ARCH_ARG="" OPT_BUILD_IMAGE=false OPT_DISABLE_VALIDATION=false - OPT_CHECK=false CHECK_ARG="" - OPT_SKIP_ROOT_TEST=false OPT_DELETE=false OPT_HELP=false OPT_VERSION=false @@ -164,13 +146,12 @@ function parse_arguments(){ -i|--setup-from-file) OPT_SETUP_FROM_FILE=true ; shift ; IMAGE_FILE=$1 ; shift ;; -f|--fakeroot) OPT_FAKEROOT=true ; shift ;; -r|--root) OPT_ROOT=true ; shift ;; - -u|--user-namespace) OPT_USER_NAMESPACE=true ; shift ;; + -g|--groot) OPT_GROOT=true ; shift ;; + -u|--namespace) OPT_USER_NAMESPACE=true ; shift ;; -p|--backend-args) OPT_BACKEND_ARGS=true ; shift ; BACKEND_ARGS=$1; shift ;; -a|--arch) OPT_ARCH=true ; shift ; ARCH_ARG=$1; shift ;; -b|--build-image) OPT_BUILD_IMAGE=true ; shift ;; -n|--disable-validation) OPT_DISABLE_VALIDATION=true ; shift ;; - -c|--check) OPT_CHECK=true ; shift ; CHECK_ARG=$1; shift ;; - -s|--skip-root-tests) OPT_SKIP_ROOT_TEST=true ; shift ;; -d|--delete) OPT_DELETE=true ; shift ;; -h|--help) OPT_HELP=true ; shift ;; -V|--version) OPT_VERSION=true ; shift ;; @@ -192,14 +173,11 @@ function execute_operation(){ $OPT_VERSION && version && return if $OPT_BUILD_IMAGE; then - build_image_env $OPT_DISABLE_VALIDATION $OPT_SKIP_ROOT_TEST + build_image_env $OPT_DISABLE_VALIDATION return elif $OPT_DELETE; then delete_env return - elif $OPT_CHECK; then - check_env "${JUNEST_HOME}" "${CHECK_ARG}" $OPT_SKIP_ROOT_TEST - return fi if ! is_env_installed @@ -217,22 +195,21 @@ function execute_operation(){ [ -z "${ARCH_ARG}" ] || \ die "The option --arch cannot be specified since JuNest has already been downloaded in $JUNEST_HOME" + local run_env if $OPT_USER_NAMESPACE; then - if $OPT_FAKEROOT; then - run_env_as_fakeroot_with_namespace "${BACKEND_ARGS}" "${ARGS[@]}" - else - run_env_as_user_with_namespace "${BACKEND_ARGS}" "${ARGS[@]}" - fi + run_env=run_env_with_namespace + elif $OPT_FAKEROOT; then + run_env=run_env_as_fakeroot + elif $OPT_ROOT; then + run_env=run_env_as_chroot + elif $OPT_GROOT; then + run_env=run_env_as_groot else - if $OPT_FAKEROOT; then - run_env_as_fakeroot "${BACKEND_ARGS}" "${ARGS[@]}" - elif $OPT_ROOT; then - run_env_as_root "${ARGS[@]}" - else - run_env_as_user "${BACKEND_ARGS}" "${ARGS[@]}" - fi + run_env=run_env_as_user fi + $run_env "${BACKEND_ARGS}" "${ARGS[@]}" + } function main() { diff --git a/lib/checks/check.sh b/lib/checks/check.sh new file mode 100755 index 0000000..4a0cd73 --- /dev/null +++ b/lib/checks/check.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash +# +# This modules is used for: +# - Running checks against the building JuNest image +# - Integration tests on JuNest script against different execution modes (i.e. -f, -u, -r modes) +# +# Dependencies: +# - None +# +# vim: ft=sh + +set -eu + +OPT_RUN_ROOT_TESTS=${1:-false} +RUN_ROOT_TESTS=false +[[ ${OPT_RUN_ROOT_TESTS} == "--run-root-tests" ]] && RUN_ROOT_TESTS=true + +OPT_SKIP_AUR_TESTS=${1:-false} +SKIP_AUR_TESTS=false +[[ ${OPT_SKIP_AUR_TESTS} == "--skip-aur-tests" ]] && SKIP_AUR_TESTS=true + +JUNEST_HOME=${JUNEST_HOME:-$HOME/.junest} + +# JUNEST_BASE can be overridden for testing purposes. +# There is no need for doing it for normal usage. +JUNEST_BASE="${JUNEST_BASE:-$(readlink -f $(dirname $(readlink -f "$0"))/../..)}" + +source "${JUNEST_BASE}/lib/utils/utils.sh" +source "${JUNEST_BASE}/lib/core/common.sh" + +info "Validating JuNest located in ${JUNEST_HOME}..." + +info "Initial JuNest setup..." +echo "Server = ${DEFAULT_MIRROR}" >> /etc/pacman.d/mirrorlist +pacman --noconfirm -Syy +pacman --noconfirm -S base-devel + +info "Checking essential executables work..." +pacman -Qi pacman 1> /dev/null +yogurt -V 1> /dev/null +/opt/proot/proot-$ARCH --help 1> /dev/null + +repo_package1=tree +echo "Checking ${repo_package1} package from official repo..." +pacman --noconfirm -S ${repo_package1} +tree -L 1 +pacman --noconfirm -Rsn ${repo_package1} + +repo_package2=iftop +info "Checking ${repo_package2} package from official repo..." +pacman --noconfirm -S ${repo_package2} +$RUN_ROOT_TESTS && iftop -t -s 5 +pacman --noconfirm -Rsn ${repo_package2} + +if ! $SKIP_AUR_TESTS +then + aur_package=tcptraceroute + info "Checking ${aur_package} package from AUR repo..." + yogurt -A --noconfirm -S ${aur_package} + $RUN_ROOT_TESTS && tcptraceroute localhost + pacman --noconfirm -Rsn ${aur_package} +fi diff --git a/lib/core/build.sh b/lib/core/build.sh index 1945185..4a00f22 100644 --- a/lib/core/build.sh +++ b/lib/core/build.sh @@ -40,7 +40,6 @@ function build_image_env(){ _check_package git local disable_validation=$1 - local skip_root_tests=$2 local maindir=$(TMPDIR=$JUNEST_TEMPDIR mktemp -d -t ${CMD}.XXXXXXXXXX) sudo mkdir -p ${maindir}/root @@ -88,7 +87,8 @@ function build_image_env(){ then mkdir -p ${maindir}/root_test $TAR -zxpf ${imagefile} -C "${maindir}/root_test" - check_env "${maindir}/root_test" "${maindir}/root_test/opt/${CMD}/bin/${CMD}" $skip_root_tests + JUNEST_HOME="${maindir}/root_test" ${maindir}/root_test/opt/${CMD}/bin/${CMD} -f ./lib/checks/check.sh + JUNEST_HOME="${maindir}/root_test" sudo -E ${maindir}/root_test/opt/${CMD}/bin/${CMD} -g ./lib/checks/check.sh --run-root-tests fi sudo cp ${maindir}/output/${imagefile} ${ORIGIN_WD} @@ -97,45 +97,3 @@ function build_image_env(){ trap - QUIT EXIT ABRT KILL TERM INT sudo rm -fr "$maindir" } - -function check_env(){ - local testdir=$1 - local cmd=$2 - local skip_root_tests=$3 - info "Validating ${NAME} located in ${testdir} using the ${cmd} script..." - echo "Server = ${DEFAULT_MIRROR}" >> ${testdir}/etc/pacman.d/mirrorlist - JUNEST_HOME=${testdir} ${cmd} -f pacman --noconfirm -Syy - - # Check most basic executables work - $skip_root_tests || JUNEST_HOME=${testdir} sudo -E ${cmd} -r pacman -Qi pacman 1> /dev/null - JUNEST_HOME=${testdir} ${cmd} -- pacman -Qi pacman 1> /dev/null - JUNEST_HOME=${testdir} ${cmd} -f -- pacman -Qi pacman 1> /dev/null - $skip_root_tests || JUNEST_HOME=${testdir} sudo -E ${cmd} -r yogurt -V 1> /dev/null - JUNEST_HOME=${testdir} ${cmd} -- yogurt -V 1> /dev/null - JUNEST_HOME=${testdir} ${cmd} -f -- yogurt -V 1> /dev/null - $skip_root_tests || JUNEST_HOME=${testdir} sudo -E ${cmd} -r /opt/proot/proot-$ARCH --help 1> /dev/null - JUNEST_HOME=${testdir} ${cmd} -- /opt/proot/proot-$ARCH --help 1> /dev/null - JUNEST_HOME=${testdir} ${cmd} -f -- /opt/proot/proot-$ARCH --help 1> /dev/null - - local repo_package=tree - info "Installing ${repo_package} package from official repo using proot..." - JUNEST_HOME=${testdir} ${cmd} -f pacman --noconfirm -S ${repo_package} - JUNEST_HOME=${testdir} ${cmd} tree - JUNEST_HOME=${testdir} ${cmd} -f tree - - local repo_package=iftop - info "Installing ${repo_package} package from official repo using root..." - JUNEST_HOME=${testdir} ${cmd} -f pacman --noconfirm -S ${repo_package} - $skip_root_tests || JUNEST_HOME=${testdir} sudo -E ${cmd} -r iftop -t -s 5 - - JUNEST_HOME=${testdir} ${cmd} -f pacman --noconfirm -S base-devel - local aur_package=tcptraceroute - info "Installing ${aur_package} package from AUR repo using proot..." - JUNEST_HOME=${testdir} ${cmd} -f -- yogurt -A --noconfirm -S ${aur_package} - $skip_root_tests || JUNEST_HOME=${testdir} sudo -E ${cmd} -r tcptraceroute localhost - - info "Removing the previous packages..." - JUNEST_HOME=${testdir} ${cmd} -f pacman --noconfirm -Rsn tcptraceroute tree iftop - - JUNEST_HOME=${testdir} ${cmd} -u -- ls -la -} diff --git a/lib/core/chroot.sh b/lib/core/chroot.sh index ff8465d..9a104aa 100644 --- a/lib/core/chroot.sh +++ b/lib/core/chroot.sh @@ -8,27 +8,12 @@ # # vim: ft=sh -####################################### -# Run JuNest as real root. -# -# Globals: -# JUNEST_HOME (RO) : The JuNest home directory. -# UID (RO) : The user ID. -# SUDO_USER (RO) : The sudo user ID. -# SUDO_GID (RO) : The sudo group ID. -# SH (RO) : Contains the default command to run in JuNest. -# Arguments: -# cmd ($@?) : The command to run inside JuNest environment. -# Default command is defined by SH variable. -# Returns: -# $ARCHITECTURE_MISMATCH : If host and JuNest architecture are different. -# Output: -# - : The command output. -####################################### -function run_env_as_root(){ - source ${JUNEST_HOME}/etc/junest/info - [ "$JUNEST_ARCH" != "$ARCH" ] && \ - die_on_status $ARCHITECTURE_MISMATCH "The host system architecture is not correct: $ARCH != $JUNEST_ARCH" +function _run_env_as_xroot(){ + local cmd=$1 + local backend_args="$2" + shift 2 + + check_same_arch local uid=$UID # SUDO_USER is more reliable compared to SUDO_UID @@ -41,5 +26,63 @@ function run_env_as_root(){ trap - QUIT EXIT ABRT KILL TERM INT trap "[ -z $uid ] || chown_cmd -R ${uid} ${JUNEST_HOME};" EXIT QUIT ABRT KILL TERM INT - JUNEST_ENV=1 chroot_cmd "$JUNEST_HOME" "${SH[@]}" "-c" "${main_cmd}" + copy_common_files + + check_nested_env + + JUNEST_ENV=1 $cmd $backend_args "$JUNEST_HOME" "${SH[@]}" "-c" "${main_cmd}" +} + +####################################### +# Run JuNest as real root via GRoot command. +# +# Globals: +# JUNEST_HOME (RO) : The JuNest home directory. +# UID (RO) : The user ID. +# SUDO_USER (RO) : The sudo user ID. +# SUDO_GID (RO) : The sudo group ID. +# SH (RO) : Contains the default command to run in JuNest. +# Arguments: +# backend_args ($1) : The arguments to pass to proot +# cmd ($2-?) : The command to run inside JuNest environment. +# Default command is defined by SH variable. +# Returns: +# $ARCHITECTURE_MISMATCH : If host and JuNest architecture are different. +# Output: +# - : The command output. +####################################### +function run_env_as_groot(){ + local backend_args="$1" + shift + + provide_common_bindings + local bindings=${RESULT} + unset RESULT + + _run_env_as_xroot "$GROOT $bindings" "$backend_args" "$@" +} + +####################################### +# Run JuNest as real root via chroot command. +# +# Globals: +# JUNEST_HOME (RO) : The JuNest home directory. +# UID (RO) : The user ID. +# SUDO_USER (RO) : The sudo user ID. +# SUDO_GID (RO) : The sudo group ID. +# SH (RO) : Contains the default command to run in JuNest. +# Arguments: +# backend_args ($1) : The arguments to pass to proot +# cmd ($2-?) : The command to run inside JuNest environment. +# Default command is defined by SH variable. +# Returns: +# $ARCHITECTURE_MISMATCH : If host and JuNest architecture are different. +# Output: +# - : The command output. +####################################### +function run_env_as_chroot(){ + local backend_args="$1" + shift + + _run_env_as_xroot chroot_cmd "$backend_args" "$@" } diff --git a/lib/core/common.sh b/lib/core/common.sh index d7e050c..17b0d5b 100644 --- a/lib/core/common.sh +++ b/lib/core/common.sh @@ -19,20 +19,9 @@ NESTED_ENVIRONMENT=106 VARIABLE_NOT_SET=107 NO_CONFIG_FOUND=108 -if [ "$JUNEST_ENV" == "1" ] -then - die_on_status $NESTED_ENVIRONMENT "Error: Nested ${NAME} environments are not allowed" -elif [ ! -z $JUNEST_ENV ] && [ "$JUNEST_ENV" != "0" ] -then - die_on_status $VARIABLE_NOT_SET "The variable JUNEST_ENV is not properly set" -fi - -[ -z ${JUNEST_HOME} ] && JUNEST_HOME=~/.${CMD} -[ -z ${JUNEST_BASE} ] && JUNEST_BASE=${JUNEST_HOME}/opt/junest -if [ -z ${JUNEST_TEMPDIR} ] || [ ! -d ${JUNEST_TEMPDIR} ] -then - JUNEST_TEMPDIR=/tmp -fi +JUNEST_HOME=${JUNEST_HOME:-~/.${CMD}} +JUNEST_BASE=${JUNEST_BASE:-${JUNEST_HOME}/opt/junest} +JUNEST_TEMPDIR=${JUNEST_TEMPDIR:-/tmp} # The update of the variable PATH ensures that the executables are # found on different locations @@ -99,36 +88,55 @@ LD_EXEC="$LD_LIB --library-path ${JUNEST_HOME}/usr/lib:${JUNEST_HOME}/lib" # image. function ln_cmd(){ - $LN $@ || $LD_EXEC ${JUNEST_HOME}/usr/bin/$LN $@ + $LN "$@" || $LD_EXEC ${JUNEST_HOME}/usr/bin/$LN "$@" } function getent_cmd(){ - $GETENT $@ || $LD_EXEC ${JUNEST_HOME}/usr/bin/$GETENT $@ + $GETENT "$@" || $LD_EXEC ${JUNEST_HOME}/usr/bin/$GETENT "$@" } function cp_cmd(){ - $CP $@ || $LD_EXEC ${JUNEST_HOME}/usr/bin/$CP $@ + $CP "$@" || $LD_EXEC ${JUNEST_HOME}/usr/bin/$CP "$@" } function rm_cmd(){ - $RM $@ || $LD_EXEC ${JUNEST_HOME}/usr/bin/$RM $@ + $RM "$@" || $LD_EXEC ${JUNEST_HOME}/usr/bin/$RM "$@" } function chown_cmd(){ - $CHOWN $@ || $LD_EXEC ${JUNEST_HOME}/usr/bin/$CHOWN $@ + $CHOWN "$@" || $LD_EXEC ${JUNEST_HOME}/usr/bin/$CHOWN "$@" } function mkdir_cmd(){ - $MKDIR $@ || $LD_EXEC ${JUNEST_HOME}/usr/bin/$MKDIR $@ + $MKDIR "$@" || $LD_EXEC ${JUNEST_HOME}/usr/bin/$MKDIR "$@" } function zgrep_cmd(){ # No need for LD_EXEC as zgrep is a POSIX shell script - $ZGREP $@ || ${JUNEST_HOME}/usr/bin/$ZGREP $@ + $ZGREP "$@" || ${JUNEST_HOME}/usr/bin/$ZGREP "$@" +} + +function download_cmd(){ + $WGET "$@" || $CURL "$@" +} + +function chroot_cmd(){ + $CLASSIC_CHROOT "$@" || $LD_EXEC ${JUNEST_HOME}/usr/bin/$CLASSIC_CHROOT "$@" } function unshare_cmd(){ - $UNSHARE $@ || $LD_EXEC ${JUNEST_HOME}/usr/bin/$UNSHARE $@ + # Most of the distros do not have the `unshare` command updated + # with --user option available. + # Hence, give priority to the `unshare` executable in JuNest image. + if $LD_EXEC ${JUNEST_HOME}/usr/bin/$UNSHARE --user "${SH[@]}" "-c" ":" + then + $LD_EXEC ${JUNEST_HOME}/usr/bin/$UNSHARE "${@}" + elif $UNSHARE --user "${SH[@]}" "-c" ":" + then + $UNSHARE "$@" + else + die "Error: Something went wrong with unshare command. Exiting" + fi } function proot_cmd(){ @@ -141,20 +149,60 @@ function proot_cmd(){ then PROOT_NO_SECCOMP=1 ${PROOT} ${proot_args} "${@}" else - die "Error: Check if the ${CMD} arguments are correct and if the kernel is too old use the option ${CMD} -p \"-k 3.10\"" + die "Error: Something went wrong with proot command. Exiting" fi } -function download_cmd(){ - $WGET $@ || $CURL $@ -} - -function chroot_cmd(){ - $GROOT "$@" || $CLASSIC_CHROOT "$@" || $LD_EXEC ${JUNEST_HOME}/usr/bin/$CLASSIC_CHROOT "$@" -} - ############## COMMON FUNCTIONS ############### +####################################### +# Check if the executable is being running inside a JuNest environment. +# +# Globals: +# JUNEST_ENV (RO) : The boolean junest env check +# NESTED_ENVIRONMENT (RO) : The nest env exception +# VARIABLE_NOT_SET (RO) : The var not set exception +# NAME (RO) : The JuNest name +# Arguments: +# None +# Returns: +# VARIABLE_NOT_SET : If no JUNEST_ENV is not properly set +# NESTED_ENVIRONMENT : If the script is executed inside JuNest env +# Output: +# None +####################################### +function check_nested_env() { + if [[ $JUNEST_ENV == "1" ]] + then + die_on_status $NESTED_ENVIRONMENT "Error: Nested ${NAME} environments are not allowed" + elif [[ ! -z $JUNEST_ENV ]] && [[ $JUNEST_ENV != "0" ]] + then + die_on_status $VARIABLE_NOT_SET "The variable JUNEST_ENV is not properly set" + fi +} + +####################################### +# Check if the architecture between Host OS and Guest OS is the same. +# +# Globals: +# JUNEST_HOME (RO) : The JuNest home path. +# ARCHITECTURE_MISMATCH (RO) : The arch mismatch exception +# ARCH (RO) : The host OS arch +# JUNEST_ARCH (RO) : The JuNest arch +# Arguments: +# None +# Returns: +# ARCHITECTURE_MISMATCH : If arch between host and guest is not the same +# Output: +# None +####################################### +function check_same_arch() { + source ${JUNEST_HOME}/etc/junest/info + [ "$JUNEST_ARCH" != "$ARCH" ] && \ + die_on_status $ARCHITECTURE_MISMATCH "The host system architecture is not correct: $ARCH != $JUNEST_ARCH" + return 0 +} + ####################################### # Provide the proot common binding options for both normal user and fakeroot. # The list of bindings can be found in `proot --help`. This function excludes diff --git a/lib/core/namespace.sh b/lib/core/namespace.sh index 8854c43..9f4596f 100644 --- a/lib/core/namespace.sh +++ b/lib/core/namespace.sh @@ -46,6 +46,12 @@ function _run_env_with_namespace(){ local backend_args="$1" shift + check_nested_env + + provide_common_bindings + local bindings=${RESULT} + unset RESULT + # Use option -n in groot because umount do not work sometimes. # As soon as the process terminates, the namespace # will terminate too with its own mounted directories. @@ -59,7 +65,7 @@ function _run_env_with_namespace(){ ####################################### -# Run JuNest as normal user via user namespace. +# Run JuNest as fakeroot user via user namespace. # # Globals: # JUNEST_HOME (RO) : The JuNest home directory. @@ -70,15 +76,18 @@ function _run_env_with_namespace(){ # cmd ($2-?) : The command to run inside JuNest environment. # Default command is defined by SH variable. # Returns: +# $ARCHITECTURE_MISMATCH : If host and JuNest architecture are different. # Depends on the unshare command outcome. # Output: # - : The command output. ####################################### -function run_env_as_user_with_namespace() { +function run_env_with_namespace() { local backend_args="$1" shift _check_user_namespace + check_same_arch + copy_common_files copy_file /etc/hosts.equiv copy_file /etc/netgroup @@ -87,40 +96,5 @@ function run_env_as_user_with_namespace() { #copy_file /etc/localtime copy_passwd_and_group - provide_common_bindings - local bindings=${RESULT} - unset RESULT - - # TODO make sure to run the environment as normal user - _run_env_with_namespace "$backend_args" "$@" -} - -####################################### -# Run JuNest as fakeroot via user namespace. -# -# Globals: -# JUNEST_HOME (RO) : The JuNest home directory. -# GROOT (RO) : The groot program. -# SH (RO) : Contains the default command to run in JuNest. -# Arguments: -# backend_args ($1) : The arguments to pass to proot -# cmd ($2-?) : The command to run inside JuNest environment. -# Default command is defined by SH variable. -# Returns: -# Depends on the unshare command outcome. -# Output: -# - : The command output. -####################################### -function run_env_as_fakeroot_with_namespace() { - local backend_args="$1" - shift - _check_user_namespace - - copy_common_files - - provide_common_bindings - local bindings=${RESULT} - unset RESULT - _run_env_with_namespace "$backend_args" "$@" } diff --git a/lib/core/proot.sh b/lib/core/proot.sh index edad2c3..f678d9d 100644 --- a/lib/core/proot.sh +++ b/lib/core/proot.sh @@ -12,6 +12,7 @@ function _run_env_with_proot(){ local proot_args="$1" shift + check_nested_env if [ "$1" != "" ] then JUNEST_ENV=1 proot_cmd "${proot_args}" "${SH[@]}" "-c" "$(insert_quotes_on_spaces "${@}")" @@ -58,7 +59,7 @@ function _run_env_with_qemu(){ ####################################### function run_env_as_fakeroot(){ (( EUID == 0 )) && \ - die_on_status $ROOT_ACCESS_ERROR "You cannot access with root privileges. Use --root option instead." + die_on_status $ROOT_ACCESS_ERROR "You cannot access with root privileges. Use --groot option instead." local backend_args="$1" shift @@ -91,7 +92,7 @@ function run_env_as_fakeroot(){ ####################################### function run_env_as_user(){ (( EUID == 0 )) && \ - die_on_status $ROOT_ACCESS_ERROR "You cannot access with root privileges. Use --root option instead." + die_on_status $ROOT_ACCESS_ERROR "You cannot access with root privileges. Use --groot option instead." local backend_args="$1" shift diff --git a/tests/unit-tests/test-chroot.sh b/tests/unit-tests/test-chroot.sh index 04b22a7..aa491e5 100755 --- a/tests/unit-tests/test-chroot.sh +++ b/tests/unit-tests/test-chroot.sh @@ -18,6 +18,7 @@ function oneTimeSetUp(){ function setUp(){ cwdSetUp junestSetUp + init_mocks } function tearDown(){ @@ -25,28 +26,42 @@ function tearDown(){ cwdTearDown } -function test_run_env_as_root_different_arch(){ - echo "JUNEST_ARCH=XXX" > ${JUNEST_HOME}/etc/junest/info - assertCommandFailOnStatus 104 run_env_as_root pwd -} - -function _test_run_env_as_root() { +function init_mocks() { chroot_cmd() { [ "$JUNEST_ENV" != "1" ] && return 1 - echo $@ + echo "chroot_cmd $@" } - - assertCommandSuccess run_env_as_root $@ + GROOT=chroot_cmd } -function test_run_env_as_root_cmd(){ - _test_run_env_as_root pwd - assertEquals "$JUNEST_HOME /bin/sh --login -c pwd" "$(cat $STDOUTF)" +function test_run_env_as_groot_cmd(){ + assertCommandSuccess run_env_as_groot "" pwd + assertEquals "chroot_cmd -b $HOME -b /tmp -b /proc -b /sys -b /dev $JUNEST_HOME /bin/sh --login -c pwd" "$(cat $STDOUTF)" } -function test_run_env_as_classic_root_no_cmd(){ - _test_run_env_as_root - assertEquals "$JUNEST_HOME /bin/sh --login -c /bin/sh --login" "$(cat $STDOUTF)" +function test_run_env_as_groot_no_cmd(){ + assertCommandSuccess run_env_as_groot "" + assertEquals "chroot_cmd -b $HOME -b /tmp -b /proc -b /sys -b /dev $JUNEST_HOME /bin/sh --login -c /bin/sh --login" "$(cat $STDOUTF)" +} + +function test_run_env_as_groot_cmd_with_backend_args(){ + assertCommandSuccess run_env_as_groot "-n -b /home/blah" pwd + assertEquals "chroot_cmd -b $HOME -b /tmp -b /proc -b /sys -b /dev -n -b /home/blah $JUNEST_HOME /bin/sh --login -c pwd" "$(cat $STDOUTF)" +} + +function test_run_env_as_chroot_cmd(){ + assertCommandSuccess run_env_as_chroot "" pwd + assertEquals "chroot_cmd $JUNEST_HOME /bin/sh --login -c pwd" "$(cat $STDOUTF)" +} + +function test_run_env_as_chroot_no_cmd(){ + assertCommandSuccess run_env_as_chroot "" + assertEquals "chroot_cmd $JUNEST_HOME /bin/sh --login -c /bin/sh --login" "$(cat $STDOUTF)" +} + +function test_run_env_as_chroot_cmd_with_backend_args(){ + assertCommandSuccess run_env_as_chroot "-n -b /home/blah" pwd + assertEquals "chroot_cmd -n -b /home/blah $JUNEST_HOME /bin/sh --login -c pwd" "$(cat $STDOUTF)" } source $JUNEST_ROOT/tests/utils/shunit2 diff --git a/tests/unit-tests/test-common.sh b/tests/unit-tests/test-common.sh index a95c32a..e9286fa 100755 --- a/tests/unit-tests/test-common.sh +++ b/tests/unit-tests/test-common.sh @@ -20,10 +20,20 @@ function oneTimeTearDown(){ } function setUp(){ - ld_exec() { + ld_exec_mock() { echo "ld_exec $@" } - LD_EXEC=ld_exec + ld_exec_mock_false() { + echo "ld_exec $@" + return 1 + } + LD_EXEC=ld_exec_mock + + unshare_mock() { + echo "unshare $@" + } + UNSHARE=unshare_mock + } function test_ln(){ @@ -117,26 +127,23 @@ function test_zgrep(){ } function test_unshare(){ - UNSHARE=echo assertCommandSuccess unshare_cmd new_program - assertEquals "new_program" "$(cat $STDOUTF)" + assertCommandSuccess unshare_cmd new_program + assertEquals "$(echo -e "ld_exec ${JUNEST_HOME}/usr/bin/$UNSHARE --user /bin/sh --login -c :\nld_exec ${JUNEST_HOME}/usr/bin/$UNSHARE new_program")" "$(cat $STDOUTF)" - UNSHARE=false assertCommandSuccess unshare_cmd new_program - assertEquals "ld_exec ${JUNEST_HOME}/usr/bin/false new_program" "$(cat $STDOUTF)" + LD_EXEC=ld_exec_mock_false assertCommandSuccess unshare_cmd new_program + assertEquals "$(echo -e "ld_exec ${JUNEST_HOME}/usr/bin/unshare_mock --user /bin/sh --login -c :\nunshare --user /bin/sh --login -c :\nunshare new_program")" "$(cat $STDOUTF)" UNSHARE=false LD_EXEC=false assertCommandFail unshare_cmd new_program } function test_chroot(){ - GROOT=echo assertCommandSuccess chroot_cmd root + CLASSIC_CHROOT=echo assertCommandSuccess chroot_cmd root assertEquals "root" "$(cat $STDOUTF)" - GROOT=false CLASSIC_CHROOT=echo assertCommandSuccess chroot_cmd root - assertEquals "root" "$(cat $STDOUTF)" - - GROOT=false CLASSIC_CHROOT=false assertCommandSuccess chroot_cmd root + CLASSIC_CHROOT=false assertCommandSuccess chroot_cmd root assertEquals "ld_exec $JUNEST_HOME/usr/bin/false root" "$(cat $STDOUTF)" - GROOT=false CLASSIC_CHROOT=false LD_EXEC=false assertCommandFail chroot_cmd root + CLASSIC_CHROOT=false LD_EXEC=false assertCommandFail chroot_cmd root } function test_proot_cmd_compat(){ @@ -187,11 +194,22 @@ function test_copy_passwd_and_group_failure(){ } function test_nested_env(){ - JUNEST_ENV=1 assertCommandFailOnStatus 106 bash -c "source $JUNEST_ROOT/lib/utils/utils.sh; source $JUNEST_ROOT/lib/core/common.sh" + JUNEST_ENV=1 assertCommandFailOnStatus 106 check_nested_env } function test_nested_env_not_set_variable(){ - JUNEST_ENV=aaa assertCommandFailOnStatus 107 bash -c "source $JUNEST_ROOT/lib/utils/utils.sh; source $JUNEST_ROOT/lib/core/common.sh" + JUNEST_ENV=aaa assertCommandFailOnStatus 107 check_nested_env } +function test_check_same_arch_not_same(){ + echo "JUNEST_ARCH=XXX" > ${JUNEST_HOME}/etc/junest/info + assertCommandFailOnStatus 104 check_same_arch +} + +function test_check_same_arch(){ + echo "JUNEST_ARCH=$ARCH" > ${JUNEST_HOME}/etc/junest/info + assertCommandSuccess check_same_arch +} + + source $JUNEST_ROOT/tests/utils/shunit2 diff --git a/tests/unit-tests/test-groot.sh b/tests/unit-tests/test-groot.sh index 472e027..c7b5f36 100755 --- a/tests/unit-tests/test-groot.sh +++ b/tests/unit-tests/test-groot.sh @@ -79,39 +79,39 @@ function test_groot_mountpoint_exist(){ } MOUNTPOINT=mountpoint_mock assertCommandSuccess main chrootdir - assertEquals "$(echo -e "check_and_trap(chroot_teardown EXIT)\nmountpoint(-q chrootdir)\nchroot(chrootdir)")" "$(cat $STDOUTF)" + assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nchroot(chrootdir)")" "$(cat $STDOUTF)" } function test_groot_mountpoint_does_not_exist(){ assertCommandSuccess main chrootdir - assertEquals "$(echo -e "check_and_trap(chroot_teardown EXIT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nchroot(chrootdir)")" "$(cat $STDOUTF)" + assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nchroot(chrootdir)")" "$(cat $STDOUTF)" } function test_groot_with_bind(){ assertCommandSuccess main -b /tmp chrootdir [[ -d chrootdir/tmp ]] assertEquals 0 $? - assertEquals "$(echo -e "check_and_trap(chroot_teardown EXIT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(--rbind /tmp chrootdir/tmp)\nchroot(chrootdir)")" "$(cat $STDOUTF)" + assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(--rbind /tmp chrootdir/tmp)\nchroot(chrootdir)")" "$(cat $STDOUTF)" } function test_groot_with_bind_file(){ touch file_src assertCommandSuccess main -b ${PWD}/file_src:/file_src chrootdir [[ -f chrootdir/file_src ]] assertEquals 0 $? - assertEquals "$(echo -e "check_and_trap(chroot_teardown EXIT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(--rbind ${PWD}/file_src chrootdir/file_src)\nchroot(chrootdir)")" "$(cat $STDOUTF)" + assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(--rbind ${PWD}/file_src chrootdir/file_src)\nchroot(chrootdir)")" "$(cat $STDOUTF)" } function test_groot_with_bind_not_existing_node(){ assertCommandFailOnStatus $NOT_EXISTING_FILE main -b ${PWD}/file_src:/file_src chrootdir - assertEquals "$(echo -e "check_and_trap(chroot_teardown EXIT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)")" "$(cat $STDOUTF)" + assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)")" "$(cat $STDOUTF)" } function test_groot_with_bind_not_absolute_path_node(){ touch file_src assertCommandFailOnStatus $NOT_ABSOLUTE_PATH main -b file_src:/file_src chrootdir - assertEquals "$(echo -e "check_and_trap(chroot_teardown EXIT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)")" "$(cat $STDOUTF)" + assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)")" "$(cat $STDOUTF)" } function test_groot_with_bind_guest_host(){ assertCommandSuccess main -b /tmp:/home/tmp chrootdir [[ -d chrootdir/home/tmp ]] assertEquals 0 $? - assertEquals "$(echo -e "check_and_trap(chroot_teardown EXIT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(--rbind /tmp chrootdir/home/tmp)\nchroot(chrootdir)")" "$(cat $STDOUTF)" + assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(--rbind /tmp chrootdir/home/tmp)\nchroot(chrootdir)")" "$(cat $STDOUTF)" } function test_groot_with_multiple_bind(){ assertCommandSuccess main -b /tmp:/home/tmp -b /dev chrootdir @@ -119,11 +119,11 @@ function test_groot_with_multiple_bind(){ assertEquals 0 $? [[ -d chrootdir/dev ]] assertEquals 0 $? - assertEquals "$(echo -e "check_and_trap(chroot_teardown EXIT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(--rbind /tmp chrootdir/home/tmp)\nmount(--rbind /dev chrootdir/dev)\nchroot(chrootdir)")" "$(cat $STDOUTF)" + assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(--rbind /tmp chrootdir/home/tmp)\nmount(--rbind /dev chrootdir/dev)\nchroot(chrootdir)")" "$(cat $STDOUTF)" } function test_groot_with_command(){ assertCommandSuccess main chrootdir ls -la -h - assertEquals "$(echo -e "check_and_trap(chroot_teardown EXIT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nchroot(chrootdir ls -la -h)")" "$(cat $STDOUTF)" + assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nchroot(chrootdir ls -la -h)")" "$(cat $STDOUTF)" } function test_groot_with_bind_and_command(){ assertCommandSuccess main -b /tmp:/home/tmp -b /dev chrootdir ls -la -h @@ -131,7 +131,7 @@ function test_groot_with_bind_and_command(){ assertEquals 0 $? [[ -d chrootdir/dev ]] assertEquals 0 $? - assertEquals "$(echo -e "check_and_trap(chroot_teardown EXIT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(--rbind /tmp chrootdir/home/tmp)\nmount(--rbind /dev chrootdir/dev)\nchroot(chrootdir ls -la -h)")" "$(cat $STDOUTF)" + assertEquals "$(echo -e "check_and_trap(chroot_teardown QUIT EXIT ABRT KILL TERM INT)\nmountpoint(-q chrootdir)\nmount(--bind chrootdir chrootdir)\nmount(--rbind /tmp chrootdir/home/tmp)\nmount(--rbind /dev chrootdir/dev)\nchroot(chrootdir ls -la -h)")" "$(cat $STDOUTF)" } function test_groot_with_bind_no_umount(){ assertCommandSuccess main -n chrootdir diff --git a/tests/unit-tests/test-junest.sh b/tests/unit-tests/test-junest.sh index c9f2d4a..2176cea 100755 --- a/tests/unit-tests/test-junest.sh +++ b/tests/unit-tests/test-junest.sh @@ -26,14 +26,7 @@ function version(){ } function build_image_env(){ local disable_validation=$1 - local skip_root_tests=$2 - echo "build_image_env($disable_validation,$skip_root_tests)" -} -function check_env(){ - local env_home=$1 - local cmd_script=$2 - local skip_root_tests=$3 - echo "check_env($env_home,$cmd_script,$skip_root_tests)" + echo "build_image_env($disable_validation)" } function delete_env(){ echo "delete_env" @@ -49,23 +42,21 @@ function run_env_as_fakeroot(){ shift echo "run_env_as_fakeroot($backend_args,$@)" } -function run_env_as_root(){ - echo "run_env_as_root $@" +function run_env_as_groot(){ + echo "run_env_as_groot $@" +} +function run_env_as_chroot(){ + echo "run_env_as_chroot $@" } function run_env_as_user(){ local backend_args="$1" shift echo "run_env_as_user($backend_args,$@)" } -function run_env_as_fakeroot_with_namespace(){ +function run_env_with_namespace(){ local backend_args="$1" shift - echo "run_env_as_fakeroot_with_namespace($backend_args,$@)" -} -function run_env_as_user_with_namespace(){ - local backend_args="$1" - shift - echo "run_env_as_user_with_namespace($backend_args,$@)" + echo "run_env_with_namespace($backend_args,$@)" } function test_help(){ @@ -82,27 +73,13 @@ function test_version(){ } function test_build_image_env(){ assertCommandSuccess main -b - assertEquals "build_image_env(false,false)" "$(cat $STDOUTF)" + assertEquals "build_image_env(false)" "$(cat $STDOUTF)" assertCommandSuccess main --build-image - assertEquals "build_image_env(false,false)" "$(cat $STDOUTF)" - assertCommandSuccess main -b -s - assertEquals "build_image_env(false,true)" "$(cat $STDOUTF)" + assertEquals "build_image_env(false)" "$(cat $STDOUTF)" assertCommandSuccess main -b -n - assertEquals "build_image_env(true,false)" "$(cat $STDOUTF)" - assertCommandSuccess main -b -n -s - assertEquals "build_image_env(true,true)" "$(cat $STDOUTF)" - assertCommandSuccess main --build-image --disable-validation --skip-root-tests - assertEquals "build_image_env(true,true)" "$(cat $STDOUTF)" -} -function test_check_env(){ - assertCommandSuccess main -c myscript - assertEquals "check_env(${JUNEST_HOME},myscript,false)" "$(cat $STDOUTF)" - assertCommandSuccess main --check myscript - assertEquals "check_env(${JUNEST_HOME},myscript,false)" "$(cat $STDOUTF)" - assertCommandSuccess main -c myscript -s - assertEquals "check_env(${JUNEST_HOME},myscript,true)" "$(cat $STDOUTF)" - assertCommandSuccess main --check myscript --skip-root-tests - assertEquals "check_env(${JUNEST_HOME},myscript,true)" "$(cat $STDOUTF)" + assertEquals "build_image_env(true)" "$(cat $STDOUTF)" + assertCommandSuccess main --build-image --disable-validation + assertEquals "build_image_env(true)" "$(cat $STDOUTF)" } function test_delete_env(){ assertCommandSuccess main -d @@ -168,36 +145,31 @@ function test_run_env_as_user(){ assertCommandFail main -a "myarch" -- command -ls } -function test_run_env_as_root(){ +function test_run_env_as_groot(){ + assertCommandSuccess main -g + assertEquals "run_env_as_groot " "$(cat $STDOUTF)" + assertCommandSuccess main -g command + assertEquals "run_env_as_groot command" "$(cat $STDOUTF)" +} +function test_run_env_as_chroot(){ assertCommandSuccess main -r - assertEquals "run_env_as_root " "$(cat $STDOUTF)" + assertEquals "run_env_as_chroot " "$(cat $STDOUTF)" assertCommandSuccess main -r command - assertEquals "run_env_as_root command" "$(cat $STDOUTF)" + assertEquals "run_env_as_chroot command" "$(cat $STDOUTF)" } -function test_run_env_as_fakeroot_with_namespace(){ +function test_run_env_with_namespace(){ assertCommandSuccess main -u -f - assertEquals "run_env_as_fakeroot_with_namespace(,)" "$(cat $STDOUTF)" - assertCommandSuccess main --user-namespace --fakeroot - assertEquals "run_env_as_fakeroot_with_namespace(,)" "$(cat $STDOUTF)" + assertEquals "run_env_with_namespace(,)" "$(cat $STDOUTF)" + assertCommandSuccess main --namespace --fakeroot + assertEquals "run_env_with_namespace(,)" "$(cat $STDOUTF)" assertCommandSuccess main -u -f -p "-b arg" - assertEquals "run_env_as_fakeroot_with_namespace(-b arg,)" "$(cat $STDOUTF)" + assertEquals "run_env_with_namespace(-b arg,)" "$(cat $STDOUTF)" assertCommandSuccess main -u -f -p "-b arg" -- command -kv - assertEquals "run_env_as_fakeroot_with_namespace(-b arg,command -kv)" "$(cat $STDOUTF)" + assertEquals "run_env_with_namespace(-b arg,command -kv)" "$(cat $STDOUTF)" assertCommandSuccess main -u -f command --as - assertEquals "run_env_as_fakeroot_with_namespace(,command --as)" "$(cat $STDOUTF)" -} -function test_run_env_as_user_with_namespace(){ - assertCommandSuccess main -u - assertEquals "run_env_as_user_with_namespace(,)" "$(cat $STDOUTF)" - - assertCommandSuccess main -u -p "-b arg" - assertEquals "run_env_as_user_with_namespace(-b arg,)" "$(cat $STDOUTF)" - assertCommandSuccess main -u -p "-b arg" -- command -ll - assertEquals "run_env_as_user_with_namespace(-b arg,command -ll)" "$(cat $STDOUTF)" - assertCommandSuccess main -u command -ls - assertEquals "run_env_as_user_with_namespace(,command -ls)" "$(cat $STDOUTF)" + assertEquals "run_env_with_namespace(,command --as)" "$(cat $STDOUTF)" } function test_check_cli(){ diff --git a/tests/unit-tests/test-namespace.sh b/tests/unit-tests/test-namespace.sh index e09db0a..9cc60a7 100755 --- a/tests/unit-tests/test-namespace.sh +++ b/tests/unit-tests/test-namespace.sh @@ -78,64 +78,36 @@ function test_is_user_namespace_enabled_with_config(){ assertCommandSuccess _is_user_namespace_enabled } -function test_run_env_as_user_with_namespace() { - assertCommandSuccess run_env_as_user_with_namespace "" "" +function test_run_env_with_namespace() { + assertCommandSuccess run_env_with_namespace "" "" assertEquals "unshare --mount --user --map-root-user $GROOT -n -b $HOME -b /tmp -b /proc -b /sys -b /dev $JUNEST_HOME /bin/sh --login" "$(cat $STDOUTF)" _test_copy_common_files _test_copy_remaining_files } -function test_run_env_as_user_with_namespace_with_bindings() { - assertCommandSuccess run_env_as_user_with_namespace "-b /usr -b /lib:/tmp/lib" "" +function test_run_env_with_namespace_with_bindings() { + assertCommandSuccess run_env_with_namespace "-b /usr -b /lib:/tmp/lib" "" assertEquals "unshare --mount --user --map-root-user $GROOT -n -b $HOME -b /tmp -b /proc -b /sys -b /dev -b /usr -b /lib:/tmp/lib $JUNEST_HOME /bin/sh --login" "$(cat $STDOUTF)" _test_copy_common_files _test_copy_remaining_files } -function test_run_env_as_user_with_namespace_with_command() { - assertCommandSuccess run_env_as_user_with_namespace "" "ls -la" +function test_run_env_with_namespace_with_command() { + assertCommandSuccess run_env_with_namespace "" "ls -la" assertEquals "unshare --mount --user --map-root-user $GROOT -n -b $HOME -b /tmp -b /proc -b /sys -b /dev $JUNEST_HOME /bin/sh --login -c \"ls -la\"" "$(cat $STDOUTF)" _test_copy_common_files _test_copy_remaining_files } -function test_run_env_as_user_with_namespace_with_bindings_and_command() { - assertCommandSuccess run_env_as_user_with_namespace "-b /usr -b /lib:/tmp/lib" "ls -la" +function test_run_env_with_namespace_with_bindings_and_command() { + assertCommandSuccess run_env_with_namespace "-b /usr -b /lib:/tmp/lib" "ls -la" assertEquals "unshare --mount --user --map-root-user $GROOT -n -b $HOME -b /tmp -b /proc -b /sys -b /dev -b /usr -b /lib:/tmp/lib $JUNEST_HOME /bin/sh --login -c \"ls -la\"" "$(cat $STDOUTF)" _test_copy_common_files _test_copy_remaining_files } -function test_run_env_as_fakeroot_with_namespace() { - assertCommandSuccess run_env_as_fakeroot_with_namespace "" "" - assertEquals "unshare --mount --user --map-root-user $GROOT -n -b $HOME -b /tmp -b /proc -b /sys -b /dev $JUNEST_HOME /bin/sh --login" "$(cat $STDOUTF)" - - _test_copy_common_files -} - -function test_run_env_as_fakeroot_with_namespace_with_bindings() { - assertCommandSuccess run_env_as_fakeroot_with_namespace "-b /usr -b /lib:/tmp/lib" "" - assertEquals "unshare --mount --user --map-root-user $GROOT -n -b $HOME -b /tmp -b /proc -b /sys -b /dev -b /usr -b /lib:/tmp/lib $JUNEST_HOME /bin/sh --login" "$(cat $STDOUTF)" - - _test_copy_common_files -} - -function test_run_env_as_fakeroot_with_namespace_with_command() { - assertCommandSuccess run_env_as_fakeroot_with_namespace "" "ls -la" - assertEquals "unshare --mount --user --map-root-user $GROOT -n -b $HOME -b /tmp -b /proc -b /sys -b /dev $JUNEST_HOME /bin/sh --login -c \"ls -la\"" "$(cat $STDOUTF)" - - _test_copy_common_files -} - -function test_run_env_as_fakeroot_with_namespace_with_bindings_and_command() { - assertCommandSuccess run_env_as_fakeroot_with_namespace "-b /usr -b /lib:/tmp/lib" "ls -la" - assertEquals "unshare --mount --user --map-root-user $GROOT -n -b $HOME -b /tmp -b /proc -b /sys -b /dev -b /usr -b /lib:/tmp/lib $JUNEST_HOME /bin/sh --login -c \"ls -la\"" "$(cat $STDOUTF)" - - _test_copy_common_files -} - source $JUNEST_ROOT/tests/utils/shunit2 From 0060658726a6dd8fd8895d13a9c33c237ad66b1f Mon Sep 17 00:00:00 2001 From: Filippo Squillace Date: Wed, 12 Apr 2017 19:32:30 +0100 Subject: [PATCH 7/8] Issue #174: Add util-linux in JuNest image util-linux contains `unshare` which is needed for the namespace mode. --- README.md | 2 +- lib/core/build.sh | 3 ++- lib/core/common.sh | 7 +++++-- tests/unit-tests/test-common.sh | 4 ++-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 492e284..f123eda 100644 --- a/README.md +++ b/README.md @@ -157,7 +157,7 @@ files in order to make JuNest working properly (such as pacman, yogurt and proot The option **-n** will skip the final validation tests if they are not needed. Remember that the script to build the image must run in an Arch Linux OS with arch-install-scripts, package-query, git and the base-devel packages installed. -To change the build directory just use the *JUNEST_TEMPDIR* (by default /tmp). +To change the build directory just use the `JUNEST_TEMPDIR` (by default /tmp). After creating the image junest-x86\_64.tar.gz you can install it by running: diff --git a/lib/core/build.sh b/lib/core/build.sh index 4a00f22..497e928 100644 --- a/lib/core/build.sh +++ b/lib/core/build.sh @@ -50,7 +50,8 @@ function build_image_env(){ # All the essential executables (ln, mkdir, chown, etc) are in coreutils # yaourt requires sed # localedef (called by locale-gen) requires gzip - sudo pacstrap -G -M -d ${maindir}/root pacman coreutils libunistring archlinux-keyring sed gzip + # unshare command belongs to util-linux + sudo pacstrap -G -M -d ${maindir}/root pacman coreutils libunistring archlinux-keyring sed gzip util-linux sudo bash -c "echo 'Server = $DEFAULT_MIRROR' >> ${maindir}/root/etc/pacman.d/mirrorlist" sudo mkdir -p ${maindir}/root/run/lock diff --git a/lib/core/common.sh b/lib/core/common.sh index 17b0d5b..d2ab487 100644 --- a/lib/core/common.sh +++ b/lib/core/common.sh @@ -128,10 +128,13 @@ function unshare_cmd(){ # Most of the distros do not have the `unshare` command updated # with --user option available. # Hence, give priority to the `unshare` executable in JuNest image. - if $LD_EXEC ${JUNEST_HOME}/usr/bin/$UNSHARE --user "${SH[@]}" "-c" ":" + # Also, unshare provides an environment in which /bin/sh maps to dash shell, + # therefore it ignores all the remaining SH arguments (i.e. --login) as + # they are not supported by dash. + if $LD_EXEC ${JUNEST_HOME}/usr/bin/$UNSHARE --user "${SH[0]}" "-c" ":" then $LD_EXEC ${JUNEST_HOME}/usr/bin/$UNSHARE "${@}" - elif $UNSHARE --user "${SH[@]}" "-c" ":" + elif $UNSHARE --user "${SH[0]}" "-c" ":" then $UNSHARE "$@" else diff --git a/tests/unit-tests/test-common.sh b/tests/unit-tests/test-common.sh index e9286fa..cfa3475 100755 --- a/tests/unit-tests/test-common.sh +++ b/tests/unit-tests/test-common.sh @@ -128,10 +128,10 @@ function test_zgrep(){ function test_unshare(){ assertCommandSuccess unshare_cmd new_program - assertEquals "$(echo -e "ld_exec ${JUNEST_HOME}/usr/bin/$UNSHARE --user /bin/sh --login -c :\nld_exec ${JUNEST_HOME}/usr/bin/$UNSHARE new_program")" "$(cat $STDOUTF)" + assertEquals "$(echo -e "ld_exec ${JUNEST_HOME}/usr/bin/$UNSHARE --user /bin/sh -c :\nld_exec ${JUNEST_HOME}/usr/bin/$UNSHARE new_program")" "$(cat $STDOUTF)" LD_EXEC=ld_exec_mock_false assertCommandSuccess unshare_cmd new_program - assertEquals "$(echo -e "ld_exec ${JUNEST_HOME}/usr/bin/unshare_mock --user /bin/sh --login -c :\nunshare --user /bin/sh --login -c :\nunshare new_program")" "$(cat $STDOUTF)" + assertEquals "$(echo -e "ld_exec ${JUNEST_HOME}/usr/bin/unshare_mock --user /bin/sh -c :\nunshare --user /bin/sh -c :\nunshare new_program")" "$(cat $STDOUTF)" UNSHARE=false LD_EXEC=false assertCommandFail unshare_cmd new_program } From 665c45b7aa40858ec90c7b77d5b39d7858f66ff4 Mon Sep 17 00:00:00 2001 From: Filippo Squillace Date: Tue, 18 Apr 2017 09:02:54 +0100 Subject: [PATCH 8/8] Issue #182: Update doc with comparison table on the execution modes --- README.md | 110 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 65 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index f123eda..96c8b0b 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ The Arch Linux based distro that runs upon any Linux distros without root access - [Description](#description) - [Quickstart](#quickstart) - [Installation](#installation) -- [Dependencies](#dependencies) +- [Usage](#usage) - [Advanced usage](#advanced-usage) - [Internals](#internals) - [Troubleshooting](#troubleshooting) @@ -48,54 +48,14 @@ JuNest follows the [Arch Linux philosophy](https://wiki.archlinux.org/index.php/ Quickstart ========== - -Backend programs ----------------- -There are three different ways you can run JuNest depending on the backend program you decide to use. - -### PRoot based ### -[Proot](https://wiki.archlinux.org/index.php/Proot) represents the default -program used for accessing to the JuNest environments. -The main reason to choose Proot as default backend program is because -it represents a portable solution that works well in most of GNU/Linux distros available. -One of the major drawbacks is the fact that Proot is not officially -supported anymore, therefore, Proot bugs may no longer be fixed. - -In order to run JuNest via Proot: +The basic way to run JuNest is via the [Proot](https://wiki.archlinux.org/index.php/Proot) as the backend program: - As normal user - Allow to make basic operations: ```junest``` - As fakeroot - Allow to install/remove packages: ```junest -f``` -### Linux namespaces based ### -The [Linux namespaces](http://man7.org/linux/man-pages/man7/namespaces.7.html) -represents the next generation backend program for JuNest. -The major drawback about the namespace is portability, as certain requirements -need to be satisfied: 1) Only starting from Linux 3.8, unprivileged processes can -create the required user and mount namespaces. -2) Moreover, the Linux kernel distro must have the user namespace enabled. -Hopefully, in the future the major GNU/Linux distros will start enabling such feature by default. -For instance, Ubuntu (version 14.04+) already has such feature enabled. - -In order to run JuNest via Linux namespaces: - -- As fakeroot - Allow to install/remove packages: ```junest -u``` - -### Chroot based ### -This solution suits only for privileged users. JuNest provides the possibility -to run the environment via `chroot` program. -In particular, it uses a special program called `GRoot`, an enhanced `chroot` -wrapper that allows to bind mount directories specified by the user, such as -/proc, /sys, /dev, /tmp and $HOME, before -executing any programs inside the JuNest sandbox. In case the mounting will not -work, JuNest is even providing the possibility to run the environment directly via -the pure `chroot` command. - -In order to run JuNest via `chroot` solutions: - -- As root via `GRoot` - Allow to have fully root privileges inside JuNest environment (you need to be root for executing this): ```junest -g``` - -- As root via `chroot` - Allow to have fully root privileges inside JuNest environment (you need to be root for executing this): ```junest -r``` +To know more about the JuNest execution modes depending on the backend program +used, see the [Usage](#usage) section below. After running JuNest -------------------- @@ -144,9 +104,69 @@ Alternatively, another installation method would be to directly download the JuN curl https://s3-eu-west-1.amazonaws.com/junest-repo/junest-${ARCH}.tar.gz | tar -xz -C ~/.junest export PATH=~/.junest/opt/junest/bin:$PATH +Usage +===== +There are three different ways you can run JuNest depending on the backend program you decide to use. + +PRoot based +----------- +[Proot](https://wiki.archlinux.org/index.php/Proot) represents the default +program used for accessing to the JuNest environments. +The main reason to choose Proot as default backend program is because +it represents a portable solution that works well in most of GNU/Linux distros available. +One of the major drawbacks is the fact that Proot is not officially +supported anymore, therefore, Proot bugs may no longer be fixed. + +In order to run JuNest via Proot: + +- As normal user - Allow to make basic operations: ```junest``` + +- As fakeroot - Allow to install/remove packages: ```junest -f``` + +Linux namespaces based +---------------------- +The [Linux namespaces](http://man7.org/linux/man-pages/man7/namespaces.7.html) +represents the next generation backend program for JuNest. +The major drawback about the namespace is portability, as certain requirements +need to be satisfied: 1) Only starting from Linux 3.8, unprivileged processes can +create the required user and mount namespaces. +2) Moreover, the Linux kernel distro must have the user namespace enabled. +Hopefully, in the future the major GNU/Linux distros will start enabling such feature by default. +For instance, Ubuntu (version 14.04+) already has such feature enabled. + +In order to run JuNest via Linux namespaces: + +- As fakeroot - Allow to install/remove packages: ```junest -u``` + +Chroot based +------------ +This solution suits only for privileged users. JuNest provides the possibility +to run the environment via `chroot` program. +In particular, it uses a special program called `GRoot`, an enhanced `chroot` +wrapper that allows to bind mount directories specified by the user, such as +/proc, /sys, /dev, /tmp and $HOME, before +executing any programs inside the JuNest sandbox. In case the mounting will not +work, JuNest is even providing the possibility to run the environment directly via +the pure `chroot` command. + +In order to run JuNest via `chroot` solutions: + +- As root via `GRoot` - Allow to have fully root privileges inside JuNest environment (you need to be root for executing this): ```junest -g``` + +- As root via `chroot` - Allow to have fully root privileges inside JuNest environment (you need to be root for executing this): ```junest -r``` + +Execution modes comparison table +---------------- +The following table shows the capabilities that each backend program is able to perform: + +| | QEMU | Root privileges required | Manage Official Packages | Manage AUR Packages | Portability | Support | User modes | +| --- | ---- | ------------------------ | ------------------------ | ------------------- | ----------- | ------- | ---------- | +| **Proot** | YES | NO | YES | YES | YES | Poor | Normal user and `fakeroot` | +| **Linux Namespaces** | NO | NO | YES | NO | Poor | YES | `fakeroot` only | +| **Chroot** | NO | YES | YES | YES | YES | YES | `root` only | + Advanced usage ============== - ## Build image ## You can build a new JuNest image from scratch by running the following command: