Skip to content

Instantly share code, notes, and snippets.

@smoser

smoser/README.md

Last active Jun 3, 2020
Embed
What would you like to do?
bash version of lxc-usernsexec

bash version of lxc-usernsexec

I was in need of something like lxc-usernsexec but ran into some issues lxc/#3420 using it (possibly user error). I put together a test case testing what I thought were valid invocations of lxc-usernsexec. That is here as test-usernsexec.

I looked at the implementation and came up with the bash version here ias 'usernsexec'. My version passes the test case.

My version doesn't get tty's for the executed command.

A heavily modified test case was added to lxc through pull requests #3432 and #3428. It is in lxc/src/tests/lxc-test-usernsexec.

#!/bin/sh
fail() { echo "$@" 1>&2; exit 1; }
uid=$(id --user) || fail "failed to get uid"
gid=$(id --group) || fail "failed to get gid"
name=$(id --user --name) || fail "failed to get username"
subuid=$(awk -F: '$1 == n { print $2; exit(0); }' "n=$name" /etc/subuid) &&
[ -n "$subuid" ] || fail "did not find $name in /etc/subuid"
subgid=$(awk -F: '$1 == n { print $2; exit(0); }' "n=$name" /etc/subgid) &&
[ -n "$subgid" ] || fail "did not find $name in /etc/subgid"
test_it() {
local out="" pf="FAIL" fmt="%-8s %s [%d] %s\n" name="$1" r="0"
shift
set -- $USERNSEXEC "$@" -- $TEST_CMD
out=$("$@" 2>&1) && pf=PASS
r=$?
printf "$fmt" "$name" "$pf" "$r" "$*"
if [ "${DEBUG:-0}" = "2" ]; then
[ -n "$out" ] && echo "$out"
elif [ "${DEBUG:-0}" = "1" ]; then
[ $r -eq 0 ] || echo "$out" | sed 's,^, | ,' 1>&2
fi
}
USERNSEXEC=${USERNSEXEC:-lxc-usernsexec}
TEST_CMD=${TEST_CMD:-/bin/true}
mapuid="u:0:$uid:1"
mapgid="g:0:$gid:1"
mapsubuid="u:1:$subuid:1"
mapsubgid="g:1:$subgid:1"
ver=$(dpkg-query --show lxc-utils | awk '{print $2}')
echo "uid=$uid gid=$gid name=$name subuid=$subuid subgid=$subgid ver=$ver"
echo "lxc-utils=$ver kver=$(uname -r)"
echo "USERNSEXEC=$USERNSEXEC TEST_CMD=$TEST_CMD"
echo ----
test_it default
test_it UG.. "-m$mapuid" "-m$mapgid"
# per brauner, this is not expected to work. Must pass group and user.
#test_it U... "-m$mapuid"
#test_it .G.. "-m$mapgid"
test_it UGu. "-m$mapuid" "-m$mapgid" "-m$mapsubuid"
test_it UG.g "-m$mapuid" "-m$mapgid" "-m$mapsubgid"
test_it UGug "-m$mapuid" "-m$mapgid" "-m$mapsubuid" "-m$mapsubgid"
test_it B... "-mb${mapuid#u}"
test_it B.b. "-mb${mapuid#u}" "-mb${mapsubuid#u}"
# this one shows strange behavior. all above, a 'whoami' will
# show root. but this is basically just like UG.. but with different
# ids and it will show nobody.
#test_it ug.. "-mu:0:$subuid:1" "-mg:0:$subuid:1"
#!/bin/bash
TEMP_D=""
KID=""
MYNAME=""
VERBOSITY=${_VERBOSITY:-0}
cleanup() {
[ -d "${TEMP_D}" ] && rm -Rf "${TEMP_D}"
}
error() { echo "$@" 1>&2 ; }
fail() {
[ $# -eq 0 ] || error "$@"
exit 1
}
kidmode() {
local x
read x <&9
[ "$x" = "1" ] || fail "$$ read $x from 3"
debug 2 "$$ moving on"
exec 9<&-
exec "$@"
}
Usage() {
cat <<EOF
Usage: ${0##*/} [ mappings ] <CMD> <<ARGUMENTS>>
Apply mappings and execute CMD with arguments.
options:
-m <uid-maps> uid maps to use.
EOF
}
bad_Usage() { Usage 1>&2; [ $# -eq 0 ] || error "$@"; return 1; }
debug() {
local level=${1}; shift;
[ "${level}" -gt "${VERBOSITY}" ] && return
error "${@}"
}
remap() {
local prog="$1" pid="$2" r="" args="" group=""
shift 2
if [ $# -eq 0 ]; then
debug 1 "no maps for $prog"
return 0
fi
# maps are ns_id:host_id:range
# new[ug]idmap want <uid> <loweruid> <count>
args=( )
for group in "$@"; do
args=( "${args[@]}" ${group//:/ } )
done
debug 2 "$prog $pid ${args[@]}"
$prog $pid "${args[@]}" && return 0
r=$?
error "failed [$r]: $prog $pid ${args[*]}"
return $r
}
check_map() {
# just validate quickly
local t=${1%%:*}
case "$t" in
u|g|b) :;;
*) return 1;;
esac
}
main() {
local short_opts="hm:Nv"
local long_opts="help,no-defaults,verbose"
local getopt_out=""
getopt_out=$(getopt --name "${0##*/}" \
--options "${short_opts}" --long "${long_opts}" -- "$@") &&
eval set -- "${getopt_out}" ||
{ bad_Usage; return; }
local cur="" next="" umaps="" gmaps="" defmaps=true
umaps=( );
gmaps=( )
while [ $# -ne 0 ]; do
cur="$1"; next="$2";
case "$cur" in
-h|--help) Usage ; exit 0;;
-v|--verbose) VERBOSITY=$((${VERBOSITY}+1));;
-N|--no-defaults) defmaps=false;;
-m) check_map "$next" || fail "bad map $next"
case "$next" in
u:*) umaps[${#umaps[@]}]=${next#*:};;
g:*) gmaps[${#umaps[@]}]=${next#*:};;
b:*)
umaps[${#umaps[@]}]=${next#*:}
gmaps[${#gmaps[@]}]=${next#*:}
;;
esac
shift;;
--) shift; break;;
esac
shift;
done
[ $# -ne 0 ] || { bad_Usage "must provide cmd"; return; }
cmd=( "$@" )
TEMP_D=$(mktemp -d "${TMPDIR:-/tmp}/${0##*/}.XXXXXX") ||
fail "failed to make tempdir"
trap cleanup EXIT
if [ "$VERBOSITY" -gt 0 ]; then
local uid="" gid="" name=""
uid=$(id --user) || fail "failed to get uid"
gid=$(id --group) || fail "failed to get gid"
name=$(id --user --name) || fail "failed to get username"
subuid=$(awk -F: '$1 == n { print $2; exit(0); }' "n=$name" /etc/subuid) &&
[ -n "$subuid" ] || fail "did not find $name in /etc/subuid"
subgid=$(awk -F: '$1 == n { print $2; exit(0); }' "n=$name" /etc/subgid) &&
[ -n "$subgid" ] || fail "did not find $name in /etc/subgid"
debug 1 "uid=$uid subuid=$subuid gid=$gid subgid=$subgid"
fi
FIFO="${TEMP_D}/fifo"
mkfifo "$FIFO" || fail "failed mkfifo"
exec 9<>$FIFO || fail "failed opening 9"
_VERBOSITY=$((VERBOSITY-1)) unshare --user "$0" kid "${cmd[@]}" &
KID=$!
[ -d "/proc/$KID" ] || fail "$KID is dead"
if [ "${defmaps}" = "true" ]; then
[ "${#umaps[@]}" -eq 0 ] && umaps=( "0:$UID:1" )
[ "${#gmaps[@]}" -eq 0 ] && gmaps=( "0:$(id -g):1" )
fi
remap newuidmap $KID "${umaps[@]}" ||
fail "failed to remap $KID with ${umaps[@]}"
remap newgidmap $KID "${gmaps[@]}" ||
fail "failed to remap $KID with ${gmaps[@]}"
echo 1 >&9
debug 2 "$$: waiting for $KID"
wait $KID
r=$?
debug 2 "$$: done $r"
return $r
}
if [ "$1" == "kid" ]; then
shift
kidmode "$@"
exit
fi
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment