Merge pull request #87 from fsquillace/issue_74_chroot

Issue 74 chroot
This commit is contained in:
Filippo Squillace 2015-06-23 08:55:45 +01:00
commit dc8018a4b9
6 changed files with 152 additions and 26 deletions

View file

@ -87,7 +87,7 @@ You can build a new JuNest image from scratch by running the following command:
junest -b [-n]
The script will create a directory containing all the essentials
files in order to make JuNest working properly (such as pacman, yaourt, arch-chroot and proot).
files in order to make JuNest working properly (such as pacman, yaourt 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.
@ -132,18 +132,20 @@ 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
[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##
Since the [arch-chroot](https://wiki.archlinux.org/index.php/Chroot) may not work
on some distros, JuNest automatically tries to fallback to the classic chroot.
If jchroot 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.
##Automatic fallback for all the dependent host OS executables##
JuNest attempt first to run the executables in the host OS located in different
positions (/usr/bin, /bin and /sbin).
positions (/usr/bin, /bin, /usr/sbin and /sbin).
As a fallback it tries to run the same executable if it is available in the JuNest
image.

112
bin/jchroot Executable file
View file

@ -0,0 +1,112 @@
#!/bin/bash
#
# Copyright (c) 2012-2015
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU Library General Public License as published
# by the Free Software Foundation; either version 2, or (at your option)
# any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# This script is the simplified and portable version of arch-chroot
# (https://wiki.archlinux.org/index.php/Change_root#Using_arch-chroot)
set -e
################################ IMPORTS ##################################
source "$(dirname $0)/../lib/util.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 <<EOF
usage: ${0##*/} chroot-dir [command]
-h Print this help message
If 'command' is unspecified, ${0##*/} will launch /bin/sh.
EOF
}
chroot_add_resolv_conf() {
local chrootdir=$1 resolv_conf=$1/etc/resolv.conf
# Handle resolv.conf as a symlink to somewhere else.
if [[ -L $chrootdir/etc/resolv.conf ]]; then
# readlink(1) should always give us *something* since we know at this point
# it's a symlink. For simplicity, ignore the case of nested symlinks.
resolv_conf=$(readlink "$chrootdir/etc/resolv.conf")
if [[ $resolv_conf = /* ]]; then
resolv_conf=$chrootdir$resolv_conf
else
resolv_conf=$chrootdir/etc/$resolv_conf
fi
# ensure file exists to bind mount over
if [[ ! -f $resolv_conf ]]; then
install -Dm644 /dev/null "$resolv_conf" || return 1
fi
fi
chroot_add_mount /etc/resolv.conf "$resolv_conf" --bind
}
if [[ -z $1 || $1 == -h || $1 == --help ]]; then
usage
exit $(( $# ? 0 : 1 ))
fi
(( EUID == 0 )) || die 'This script must be run with root privileges'
chrootdir=$1
shift
[[ -d $chrootdir ]] || die "Can't create chroot on non-directory $chrootdir"
chroot_setup "$chrootdir" || die "failed to setup chroot $chrootdir"
chroot_add_resolv_conf "$chrootdir" || die "failed to setup resolv.conf"
SHELL="/bin/sh" chroot "$chrootdir" "$@"

View file

@ -18,7 +18,9 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
source "$(dirname $0)/../lib/core.sh"
JUNEST_BASE="$(dirname $0)/.."
source "${JUNEST_BASE}/lib/core.sh"
###################################
### General functions ###

View file

@ -46,6 +46,7 @@ then
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
@ -86,14 +87,14 @@ ID="id -u"
# List of executables that are run in the host OS:
PROOT_COMPAT="${JUNEST_HOME}/opt/proot/proot-${ARCH}"
CHROOT=${JUNEST_HOME}/usr/bin/arch-chroot
CLASSIC_CHROOT=${JUNEST_HOME}/usr/bin/chroot
CHROOT=${JUNEST_BASE}/bin/jchroot
CLASSIC_CHROOT="chroot"
WGET="wget --no-check-certificate"
CURL="curl -L -J -O -k"
TAR=tar
CHOWN="chown"
PATH=/usr/bin:/bin:/sbin:$PATH
PATH=/usr/bin:/bin:/usr/sbin:/sbin:$PATH
LD_EXEC="$LD_LIB --library-path ${JUNEST_HOME}/usr/lib:${JUNEST_HOME}/lib"
# The following functions attempt first to run the executable in the host OS.
@ -120,6 +121,10 @@ function download_cmd(){
$WGET $@ || $CURL $@
}
function chroot_cmd(){
$CHROOT "$@" || $CLASSIC_CHROOT "$@" || $LD_EXEC ${JUNEST_HOME}/usr/bin/chroot "$@"
}
################################# MAIN FUNCTIONS ##############################
function is_env_installed(){
@ -182,32 +187,20 @@ function setup_env_from_file(){
builtin cd $ORIGIN_WD
}
function run_env_as_root(){
local uid=$UID
[ -z $SUDO_UID ] || uid=$SUDO_UID:$SUDO_GID
local main_cmd="${SH[@]}"
[ "$1" != "" ] && main_cmd="$(insert_quotes_on_spaces "$@")"
local cmd="mkdir -p ${JUNEST_HOME}/${HOME} && mkdir -p /run/lock && ${main_cmd}"
trap - QUIT EXIT ABRT KILL TERM INT
trap "[ -z $uid ] || chown_cmd -R ${uid} ${JUNEST_HOME}; rm_cmd -r ${JUNEST_HOME}/etc/mtab" EXIT QUIT ABRT KILL TERM INT
[ ! -e ${JUNEST_HOME}/etc/mtab ] && ln_cmd -s /proc/self/mounts ${JUNEST_HOME}/etc/mtab
if ${CHROOT} $JUNEST_HOME ${TRUE} 1> /dev/null
then
JUNEST_ENV=1 ${CHROOT} $JUNEST_HOME "${SH[@]}" "-c" "${cmd}"
local ret=$?
elif ${CLASSIC_CHROOT} $JUNEST_HOME ${TRUE} 1> /dev/null
then
warn "Warning: The executable arch-chroot does not work, falling back to classic chroot"
JUNEST_ENV=1 ${CLASSIC_CHROOT} $JUNEST_HOME "${SH[@]}" "-c" "${cmd}"
local ret=$?
else
die "Error: Chroot does not work"
fi
JUNEST_ENV=1 chroot_cmd "$JUNEST_HOME" "${SH[@]}" "-c" "${main_cmd}"
local ret=$?
# The ownership of the files is assigned to the real user
[ -z $uid ] || chown_cmd -R ${uid} ${JUNEST_HOME}
@ -319,7 +312,7 @@ function build_image_env(){
# The archlinux-keyring and libunistring are due to missing dependencies declaration in ARM archlinux
# All the essential executables (ln, mkdir, chown, etc) are in coreutils
# yaourt requires sed
sudo pacstrap -G -M -d ${maindir}/root pacman arch-install-scripts coreutils binutils libunistring archlinux-keyring sed
sudo pacstrap -G -M -d ${maindir}/root pacman coreutils binutils libunistring archlinux-keyring sed
sudo bash -c "echo 'Server = $DEFAULT_MIRROR' >> ${maindir}/root/etc/pacman.d/mirrorlist"
info "Generating the locales..."
@ -394,7 +387,6 @@ function validate_image(){
JUNEST_HOME=${testdir} sudo -E ${testdir}/opt/${CMD}/bin/${CMD} -r pacman -Qi pacman 1> /dev/null
JUNEST_HOME=${testdir} sudo -E ${testdir}/opt/${CMD}/bin/${CMD} -r yaourt -V 1> /dev/null
JUNEST_HOME=${testdir} sudo -E ${testdir}/opt/${CMD}/bin/${CMD} -r /opt/proot/proot-$ARCH --help 1> /dev/null
JUNEST_HOME=${testdir} sudo -E ${testdir}/opt/${CMD}/bin/${CMD} -r arch-chroot --help 1> /dev/null
local repo_package=sysstat
info "Installing ${repo_package} package from official repo using proot..."

View file

@ -1,5 +1,5 @@
#!/bin/bash
source $(dirname $0)/../bin/* -h &> /dev/null
source $(dirname $0)/../bin/junest -h &> /dev/null
# Disable the exiterr
set +e

View file

@ -16,7 +16,8 @@ function install_mini_env(){
function setUp(){
cd $CURRPWD
JUNEST_HOME=$(TMPDIR=/tmp mktemp -d -t envhome.XXXXXXXXXX)
source "$(dirname $0)/../lib/core.sh"
JUNEST_BASE="$CURRPWD/$(dirname $0)/.."
source "${JUNEST_BASE}/lib/core.sh"
ORIGIN_WD=$(TMPDIR=/tmp mktemp -d -t envowd.XXXXXXXXXX)
cd $ORIGIN_WD
JUNEST_TEMPDIR=$(TMPDIR=/tmp mktemp -d -t envtemp.XXXXXXXXXX)
@ -217,6 +218,23 @@ function test_run_env_as_classic_root(){
assertEquals 0 $?
}
function test_run_env_as_junest_root(){
[ $SKIP_ROOT_TESTS -eq 1 ] && return
install_mini_env
CHROOT="sudo unknowncommand"
CLASSIC_CHROOT="sudo unknowncommand"
LD_EXEC="sudo $LD_EXEC"
CHOWN="sudo $CHOWN"
local output=$(run_env_as_root pwd 2> /dev/null)
assertEquals "/" "$output"
run_env_as_root [ -e /run/lock ] 2> /dev/null
assertEquals 0 $?
run_env_as_root [ -e $HOME ] 2> /dev/null
assertEquals 0 $?
}
function test_run_env_as_user(){
install_mini_env
local output=$(run_env_as_user "-k 3.10" "/usr/bin/mkdir" "-v" "/newdir2" | awk -F: '{print $1}')