Skip to content

Commit

Permalink
uvpn: Add initial version
Browse files Browse the repository at this point in the history
This tools allows a user to create a (net,mnt,user)-namespace container
which can be used to run openvpn.

This tools uses sudo to gain privileges for the setup and requires
a line like

    ALL ALL=NOPASSWD: /usr/bin/uvpn start_as_root,/usr/bin/uvpn stop_as_root

in the sudoers file.

The container will be connected to the guest network. For this the
system needs a working vlan.guest0 vlan interface into the guest
network.

The usage for the user might be along this pattern:

    uvpn start
    uvpn exec openvpn ~/.charite-username.ovpn
    echo -e "search charite.de\nnameserver 141.42.1.1\nnameserver 141.14.16.1" | uvpn exec bash -c 'cat > /etc/resolv.conf'
    uvpn exec firefox --new-instance --ProfileManager
    uvpn exec firefox --new-instance -P charite
    uvpn show
    uvpn stop_container

Because the network of the container is separate from the host system,
ip based X11-forward will not work without more setup. So the above example
would only work on the local workstation.
  • Loading branch information
donald committed Nov 2, 2018
1 parent 9f58249 commit 02dce7b
Show file tree
Hide file tree
Showing 2 changed files with 304 additions and 0 deletions.
1 change: 1 addition & 0 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -126,4 +126,5 @@ install_data blink/51-blink.rules "$DESTDIR$udev_rulesdir
install_data clusterd/clusterd.service "$DESTDIR$systemdunitdir/clusterd.service"
install_exec clusterd/clusterd "$DESTDIR$usr_sbindir/clusterd"
install_exec setuid/setuid "$DESTDIR$usr_sbindir/setuid"
install_exec uvpn/uvpn "$DESTDIR$usr_bindir/uvpn"
exit
303 changes: 303 additions & 0 deletions uvpn/uvpn
Original file line number Diff line number Diff line change
@@ -0,0 +1,303 @@
#! /bin/bash

die() {
echo "$@">&2
exit 1
}

die_usage() {
cat <<EOF
usage:
$0 start # start uvpn container
$0 stop # stop uvpn container
$0 exec [cmd...] # execute cmd (default bash) in the container
$0 show [user...] # show processes
$0 start_as_root # sudo callback - internal use
$0 stop_as_root # sudo callback - internal use
EOF
exit 1
}

create_dhclient-script() {
cat >/var/run/uvpn/$USER/dhclient-script << '_EOF_'
#!/bin/bash
ip=/sbin/ip
###
### DHCPv4 Handlers
###
if [ x$new_broadcast_address != x ]; then
new_broadcast_arg="broadcast $new_broadcast_address"
fi
if [ x$old_broadcast_address != x ]; then
old_broadcast_arg="broadcast $old_broadcast_address"
fi
if [ x$new_subnet_mask != x ]; then
new_subnet_arg="netmask $new_subnet_mask"
fi
if [ x$old_subnet_mask != x ]; then
old_subnet_arg="netmask $old_subnet_mask"
fi
if [ x$alias_subnet_mask != x ]; then
alias_subnet_arg="netmask $alias_subnet_mask"
fi
if [ x$new_interface_mtu != x ]; then
mtu_arg="mtu $new_interface_mtu"
fi
if [ x$IF_METRIC != x ]; then
metric_arg="metric $IF_METRIC"
fi
if [ x$reason = xBOUND ] || [ x$reason = xRENEW ] || \
[ x$reason = xREBIND ] || [ x$reason = xREBOOT ]; then
if [ x$old_ip_address != x ] && [ x$alias_ip_address != x ] && \
[ x$alias_ip_address != x$old_ip_address ]; then
# Possible new alias. Remove old alias.
ifconfig $interface:0- inet 0
fi
if [ x$old_ip_address != x ] && [ x$old_ip_address != x$new_ip_address ]; then
# IP address changed. Bringing down the interface will delete all routes,
# and clear the ARP cache.
ifconfig $interface inet 0 down
fi
if [ x$old_ip_address = x ] || [ x$old_ip_address != x$new_ip_address ] || \
[ x$reason = xBOUND ] || [ x$reason = xREBOOT ]; then
ifconfig $interface inet $new_ip_address $new_subnet_arg \
$new_broadcast_arg $mtu_arg
# Add a network route to the computed network address.
for router in $new_routers; do
if [ "x$new_subnet_mask" = "x255.255.255.255" ] ; then
ip route add $router dev $interface
fi
ip route add default via $router dev $interface $metric_arg
done
else
# we haven't changed the address, have we changed other options
# that we wish to update?
if [ x$new_routers != x ] && [ x$new_routers != x$old_routers ] ; then
# if we've changed routers delete the old and add the new.
for router in $old_routers; do
ip route del default via $router
done
for router in $new_routers; do
if [ "x$new_subnet_mask" = "x255.255.255.255" ] ; then
ip route add $router dev $interface
fi
ip route add default via $router dev $interface $metric_arg
done
fi
fi
if [ x$new_ip_address != x$alias_ip_address ] && [ x$alias_ip_address != x ];
then
ifconfig $interface:0- inet 0
ifconfig $interface:0 inet $alias_ip_address $alias_subnet_arg
ip route add $alias_ip_address dev $interface:0
fi
exit 0
fi
if [ x$reason = xEXPIRE ] || [ x$reason = xFAIL ] || [ x$reason = xRELEASE ] \
|| [ x$reason = xSTOP ]; then
if [ x$alias_ip_address != x ]; then
# Turn off alias interface.
ifconfig $interface:0- inet 0
fi
if [ x$old_ip_address != x ]; then
# Shut down interface, which will delete routes and clear arp cache.
ifconfig $interface inet 0 down
fi
if [ x$alias_ip_address != x ]; then
ifconfig $interface:0 inet $alias_ip_address $alias_subnet_arg
ip route add $alias_ip_address dev $interface:0
fi
exit 0
fi
if [ x$reason = xTIMEOUT ]; then
if [ x$alias_ip_address != x ]; then
ifconfig $interface:0- inet 0
fi
ifconfig $interface inet $new_ip_address $new_subnet_arg \
$new_broadcast_arg $mtu_arg
set $new_routers
if ping -q -c 1 $1; then
if [ x$new_ip_address != x$alias_ip_address ] && \
[ x$alias_ip_address != x ]; then
ifconfig $interface:0 inet $alias_ip_address $alias_subnet_arg
ip route add $alias_ip_address dev $interface:0
fi
for router in $new_routers; do
if [ "x$new_subnet_mask" = "x255.255.255.255" ] ; then
ip route add $router dev $interface
fi
ip route add default via $router dev $interface $metric_arg
done
exit 0
fi
ifconfig $interface inet 0 down
exit 1
fi
exit 0
_EOF_
chmod +x /var/run/uvpn/$USER/dhclient-script
}

have_interface() {
local if="$1"
ip link show "$1" >/dev/null 2>&1
}

cmd_start() {
test $# -eq 0 || die_usage

sudo $0 start_as_root || exit 1
create_dhclient-script
cat >/var/run/uvpn/$USER/resolv.conf<<_EOF_
search molgen.mpg.de
nameserver 141.14.16.1
_EOF_
cmd_exec ip link set lo up
cmd_exec ip link set "uvpn.$USER.1" up
cmd_exec mount --bind /var/run/uvpn/$USER/resolv.conf /etc/resolv.conf
cmd_exec dhclient -4 -v \
-lf /var/run/uvpn/$USER/dhclient.leases \
-pf /var/run/uvpn/$USER/dhclient.pid \
-sf /var/run/uvpn/$USER/dhclient-script \
uvpn.$USER.1
exit
}

cmd_start_as_root() {
test $# -eq 0 || die_usage

test $(id -u) -eq 0 || die "must be root"
user="$SUDO_USER"
test -n "$user" || die "must be called via sudo"
uid=$(id -u "$user") 2>/dev/null || die "$user: no such user"
umask 022
have_interface vlan.guest0 || die "vlan.guest0 not available. This system is not prepared to run $0. Please contact helpdesk@molgen.mpg.de"

findmnt "/run/uvpn/$user/ns" >/dev/null 2>&1 && die "container already started"

if ! have_interface "uvpn.$user.0"; then
ip link add "uvpn.$user.0" type veth peer name "uvpn.$user.1"
fi
if ! have_interface br.guest0; then
brctl addbr br.guest0
brctl addif br.guest0 vlan.guest0
fi
brctl addif br.guest0 "uvpn.$user.0"
ip link set "uvpn.$user.0" up
ip link set vlan.guest0 up
ip link set br.guest0 up

mkdir -p "/run/uvpn/$user/ns"
chown "$user" "/run/uvpn/$user"
for ns in user net mnt;do
touch "/run/uvpn/$user/ns/$ns"
done
mount --bind "/run/uvpn/$user/ns" "/run/uvpn/$user/ns"
mount --make-private "/run/uvpn/$user/ns"

pid=$(setuid $uid unshare --mount --user --net bash -c 'sleep 30 > /dev/null&echo $!')
for ns in user net mnt;do
mount --bind /proc/$pid/ns/$ns "/run/uvpn/$user/ns/$ns"
done
echo "0 $uid 1" > /proc/$pid/uid_map
echo "0 $(id -g "$user") 1" > /proc/$pid/gid_map
ip link set "uvpn.$user.1" netns $pid
kill $pid
}

cmd_stop() {
test $# -eq 0 || die_usage
user=$USER
uid=$(id -u "$user")
if findmnt "/run/uvpn/$user/ns/net" > /dev/null; then
inode_net=$(stat --format %i "/run/uvpn/$user/ns/net")
for pid in $(cd /proc;ls -1d [0-9]*|sort -n); do
inode="$(stat -L --format %i /proc/$pid/ns/net 2>/dev/null)" || continue
test $inode == $inode_net || continue
cmd=$(cat /proc/$pid/cmdline|sed 's/\x0/ /g')
echo "kill --> $pid $cmd"
kill -9 $pid
found_processes=1
done
test -n "$found_processes" && sleep 1
fi
sudo $0 stop_as_root
}

cmd_stop_as_root() {
test $# -eq 0 || die_usage
test $(id -u) -eq 0 || die "must be root"
user="$SUDO_USER"
test -n "$user" || die "must be called via sudo"
uid=$(id -u "$user") 2>/dev/null || die "$user: no such user"

for ns in user net mnt;do
while umount "/run/uvpn/$user/ns/$ns" 2>/dev/null; do true; done
done
while umount "/run/uvpn/$user/ns" 2>/dev/null; do true; done
ip link del "uvpn.$user.0" 2>/dev/null
}

cmd_exec() {
findmnt "/run/uvpn/$USER/ns" >/dev/null 2>&1 || die "container not started"
nsenter \
--wd=$(pwd) \
--net="/run/uvpn/$USER/ns/net" \
--user="/run/uvpn/$USER/ns/user" \
--mount="/run/uvpn/$USER/ns/mnt" \
"$@"
}

cmd_show() {
user="$1";shift
test $# -eq 0 || die_usage
test -z "$user" && user=$USER
uid=$(id -u "$user") 2>/dev/null || die "$user: no such user"
findmnt "/run/uvpn/$user/ns/net" >/dev/null 2>/dev/null || { echo "Container not started"; exit; }
inode_net=$(stat --format %i "/run/uvpn/$user/ns/net")
echo "Processes running in this container:"
for pid in $(cd /proc;ls -1d [0-9]*|sort -n); do
inode="$(stat -L --format %i /proc/$pid/ns/net 2>/dev/null)" || continue
test $inode == $inode_net || continue
cmd=$(cat /proc/$pid/cmdline|sed 's/\x0/ /g')
echo " $pid $cmd"
found_processes=1
done
test -n "$found_processes" || echo " (none)"
}

cmd="$1";shift
case "$cmd" in
start)
cmd_start "$@"
;;
start_as_root)
cmd_start_as_root "$@"
;;
stop)
cmd_stop "$@"
;;
stop_as_root)
cmd_stop_as_root "$@"
;;
exec)
cmd_exec "$@"
;;
show)
cmd_show "$@"
;;
*)
die_usage
;;
esac

0 comments on commit 02dce7b

Please sign in to comment.