From d721e37784d864bad2fa7b5d555316c0105f6f55 Mon Sep 17 00:00:00 2001 From: The Fox in the Shell Date: Sat, 6 Aug 2016 23:59:51 +0200 Subject: [PATCH 1/8] Add user network namespaces (INCOMPLETE) Todo: - add DHCP and NAT setup for IPv4 - add pam_network_namespace to actually make the user enter the namespace --- .etckeeper | 1 + .gitignore | 4 ++ network/interfaces.example | 25 ++++++- network/namespace.sh | 101 ++++++++++++++++++++++++++++ pam.d/common-session-noninteractive | 1 + sysctl.conf | 2 +- 6 files changed, 130 insertions(+), 4 deletions(-) create mode 100755 network/namespace.sh diff --git a/.etckeeper b/.etckeeper index fd17c08..b1def66 100755 --- a/.etckeeper +++ b/.etckeeper @@ -890,6 +890,7 @@ maybe chmod 0755 'network/if-up.d/postfix' maybe chmod 0755 'network/if-up.d/upstart' maybe chmod 0755 'network/interfaces.d' maybe chmod 0644 'network/interfaces.example' +maybe chmod 0755 'network/namespace.sh' maybe chmod 0755 'newt' maybe chmod 0644 'newt/palette.original' maybe chmod 0644 'nova-agent.env' diff --git a/.gitignore b/.gitignore index bdd7d01..c6f0bbe 100644 --- a/.gitignore +++ b/.gitignore @@ -86,3 +86,7 @@ src/* # Sometimes generated by ld when using firejail ld.so.preload + +# Used for server-specific network configuration +# cf network/namespace.sh +default/user_netns diff --git a/network/interfaces.example b/network/interfaces.example index c562750..72d286f 100644 --- a/network/interfaces.example +++ b/network/interfaces.example @@ -16,6 +16,7 @@ iface eth0 inet static netmask 255.255.255.0 gateway 192.0.2.254 + # Assuming that native IPv6 is available: iface eth0 inet6 static address 2001:DB8:f00d:b1a::10ca1 @@ -32,6 +33,24 @@ iface he-ipv6 inet6 v4tunnel endpoint 203.0.113.226 local 192.0.2.42 ttl 255 - # Sad hack - up ip a add dev $IFACE 2001:DB8:f00:b1a::/64 - down ip a del dev $IFACE 2001:DB8:f00:b1a::/64 + + +# The bridge interface for user netns +# The network 172.19.0.0/16 is non-routable, per RFC1918 +# It is used for providing IPv4 networking to users +# The network 2001:DB8:f00:b1a::/64 is reserved for documentation (RFC3849) +# and stands for the server's globally-reachable IPv6 subnet +auto br0 +iface br0 inet static + address 172.19.42.1 + netmask 255.255.255.0 + # disable Spanning Tree Protocol + bridge_stp off + # no delay before a port becomes available + bridge_waitport 0 + # no forwarding delay + bridge_fd 0 + +iface br0 inet6 static + address 2001:DB8:f00:b1a:: + netmask 64 diff --git a/network/namespace.sh b/network/namespace.sh new file mode 100755 index 0000000..3326c0f --- /dev/null +++ b/network/namespace.sh @@ -0,0 +1,101 @@ +#!/bin/sh -e +# WARNING: The environment is under the control of the user, to some extend. +# Read the pam_exec(8) manpage before editing this script. + +# root has no network namespace +if [ "$PAM_USER" == root ]; then + exit 0 +fi + + +# Check the logger manpage for valid priority levels +function log() { + logger --id=$$ --priority auth."$1" "$2" +} + +function die() { + log crit "$@" + exit 1 +} + +# SYNOPSIS: This script constructs and configures a user namespace +# for the user currently logging in. +# +# The namespace contains the following interfaces: +# - lo, the loopback interface +# - veth, a virtual Ethernet interface which is tied to a bridge. +# veth is configured with a dynamic, RFC1918 IPv4 address, +# and with a static IPv6 address. +# +# Moreover, veth's peer is userbr-${PAM_USER}, +# and is attached to the br0 bridge +# (configurable as USER_BR in /etc/default/user_netns) + +USER_BR="br0" +if [ -f "/etc/default/user_netns" ]; then + source /etc/default/user_netns +fi + +# If the user's netns exists, nothing to do +if [ -f "/var/run/netns/user-${PAM_USER}" ]; then + log info "User ${PAM_USER} already has a netns" +fi + + +# Prepare the user's netns +trap 'ip netns delete "user-${PAM_USER}"' QUIT + +if ! ip netns add "user-${PAM_USER}"; then + die "Failed to create netns for ${PAM_USER}" +fi + +if ! ip netns exec "user-${PAM_USER}" \ + ip link set dev lo up; then + die "Failed to create a loopback interface for ${PAM_USER}" +fi + +# TODO: check that deleting the namespace causes the veth pair to be deleted +if ! ip link add "userbr-${PAM_USER}" type veth peer name "userbr-${PAM_USER}-peer"; then + die "Failed to create a veth pair for ${PAM_USER}" +fi + +if ! ip link set "userbr-${PAM_USER}-peer" netns "user-${PAM_USER}"; then + ip link delete dev "userbr-${PAM_USER}" + die "Failed to add the veth peer to netns for ${PAM_USER}" +fi + +# Renaming the interface should happen before any configuration occurs +if ! ip netns exec "user-${PAM_USER}" \ + ip link set dev "userbr-${PAM_USER}-peer" name "veth"; then + die "Failed to rename interface for ${PAM_USER}" +fi + +if ! ip link set "userbr-${PAM_USER}" master "$USER_BR"; then + die "Failed to add the veth for ${PAM_USER} to the bridge" +fi + +if ! ip netns exec "user-${PAM_USER}" \ + ip link set dev "userbr-${PAM_USER}" name "veth"; then + die "Failed to rename interface for ${PAM_USER}" +fi + +if ! ip netns exec "user-${PAM_USER}" \ + dhclient "veth"; then + die "Failed to configure IPv4 for ${PAM_USER}" +fi + +# TODO: Construct the user's IPv6 from userdb info +if ! ip netns exec "user-${PAM_USER}" \ + ip addr add dev "veth" get_user_ip()/64; then + die "Failed to configure IPv6 for ${PAM_USER}" +fi + +# TODO: Construct the server's own IPv6 from userdb info +if ! ip netns exec "user-${PAM_USER}" \ + ip route add dev "veth" default via get_server_ipv6(); then + die "Failed to configure IPv6 for ${PAM_USER}" +fi + +# We reached the end without error: no cleanup on exit +trap - QUIT +exit 0 diff --git a/pam.d/common-session-noninteractive b/pam.d/common-session-noninteractive index e7e76b6..d6e5c7c 100644 --- a/pam.d/common-session-noninteractive +++ b/pam.d/common-session-noninteractive @@ -13,6 +13,7 @@ session required pam_env.so # User restrictions session required pam_namespace.so unmnt_remnt session required pam_limits.so +session required pam_exec.so type=session /etc/network/namespace.sh # Passwd database handling session sufficient pam_sss.so diff --git a/sysctl.conf b/sysctl.conf index da43ab6..b39eb2f 100644 --- a/sysctl.conf +++ b/sysctl.conf @@ -30,7 +30,7 @@ # Uncomment the next line to enable packet forwarding for IPv6 # Enabling this option disables Stateless Address Autoconfiguration # based on Router Advertisements for this host -#net.ipv6.conf.all.forwarding=1 +net.ipv6.conf.all.forwarding=1 ################################################################### From e813d357601a257379b93ee1f0a813e09f214e7f Mon Sep 17 00:00:00 2001 From: The Fox in the Shell Date: Sat, 24 Dec 2016 15:02:35 +0100 Subject: [PATCH 2/8] netns: Correctly exit if the netns already exists --- network/namespace.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/network/namespace.sh b/network/namespace.sh index 3326c0f..28a8517 100755 --- a/network/namespace.sh +++ b/network/namespace.sh @@ -36,9 +36,11 @@ if [ -f "/etc/default/user_netns" ]; then source /etc/default/user_netns fi + # If the user's netns exists, nothing to do if [ -f "/var/run/netns/user-${PAM_USER}" ]; then log info "User ${PAM_USER} already has a netns" + exit 0 fi From 9e3175caba367bde44350ec10072d6ebb0540089 Mon Sep 17 00:00:00 2001 From: The Fox in the Shell Date: Sat, 24 Dec 2016 15:03:16 +0100 Subject: [PATCH 3/8] netns: Resolve UID and ignore system users --- network/namespace.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/network/namespace.sh b/network/namespace.sh index 28a8517..f6297e2 100755 --- a/network/namespace.sh +++ b/network/namespace.sh @@ -7,6 +7,13 @@ if [ "$PAM_USER" == root ]; then exit 0 fi +# System users have no network namespace +# UID ranges defined by the Debian policy manual: +# https://www.debian.org/doc/debian-policy/ch-opersys.html#s9.2.2 +UID=$(getent passwd "$PAM_USER" | cut -d: -f3) +if [ $UID -lt 1000 ] || [ $UID -ge 60000 -a $UID -lt 65536 ]; then + exit 0 +fi # Check the logger manpage for valid priority levels function log() { From c084e0bcdfe2fbc6aa950499154bc9cd151afb28 Mon Sep 17 00:00:00 2001 From: The Fox in the Shell Date: Sat, 24 Dec 2016 15:03:40 +0100 Subject: [PATCH 4/8] netns: Construct user's IPv6 --- network/namespace.sh | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/network/namespace.sh b/network/namespace.sh index f6297e2..32d338a 100755 --- a/network/namespace.sh +++ b/network/namespace.sh @@ -25,6 +25,13 @@ function die() { exit 1 } +# Construct the user's IPv6, as {server_prefix}::{uid} +function get_user_ipv6() { + HEX_UID=$(echo "obase=16; ${UID}" | bc) + IPV6_SUFFIX=$(echo "$HEX_UID" | rev | fold -w4 | paste -sd: | rev) + return "${IPV6_PREFIX}::${IPV6_SUFFIX}" +} + # SYNOPSIS: This script constructs and configures a user namespace # for the user currently logging in. # @@ -43,6 +50,9 @@ if [ -f "/etc/default/user_netns" ]; then source /etc/default/user_netns fi +if [ -z "${IPV6_PREFIX}" ]; then + die "No IPv6 prefix defined in configuration" +fi # If the user's netns exists, nothing to do if [ -f "/var/run/netns/user-${PAM_USER}" ]; then @@ -93,7 +103,6 @@ if ! ip netns exec "user-${PAM_USER}" \ die "Failed to configure IPv4 for ${PAM_USER}" fi -# TODO: Construct the user's IPv6 from userdb info if ! ip netns exec "user-${PAM_USER}" \ ip addr add dev "veth" get_user_ip()/64; then die "Failed to configure IPv6 for ${PAM_USER}" From 82bae63f41eb4521042c8196072f7ff4c5139609 Mon Sep 17 00:00:00 2001 From: The Fox in the Shell Date: Sat, 24 Dec 2016 15:04:07 +0100 Subject: [PATCH 5/8] netns: Make the default bridge br-users --- network/interfaces.example | 6 +++--- network/namespace.sh | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/network/interfaces.example b/network/interfaces.example index 72d286f..0f2aea1 100644 --- a/network/interfaces.example +++ b/network/interfaces.example @@ -40,8 +40,8 @@ iface he-ipv6 inet6 v4tunnel # It is used for providing IPv4 networking to users # The network 2001:DB8:f00:b1a::/64 is reserved for documentation (RFC3849) # and stands for the server's globally-reachable IPv6 subnet -auto br0 -iface br0 inet static +auto br-users +iface br-users inet static address 172.19.42.1 netmask 255.255.255.0 # disable Spanning Tree Protocol @@ -51,6 +51,6 @@ iface br0 inet static # no forwarding delay bridge_fd 0 -iface br0 inet6 static +iface br-users inet6 static address 2001:DB8:f00:b1a:: netmask 64 diff --git a/network/namespace.sh b/network/namespace.sh index 32d338a..f61787c 100755 --- a/network/namespace.sh +++ b/network/namespace.sh @@ -45,7 +45,7 @@ function get_user_ipv6() { # and is attached to the br0 bridge # (configurable as USER_BR in /etc/default/user_netns) -USER_BR="br0" +USER_BR="br-users" if [ -f "/etc/default/user_netns" ]; then source /etc/default/user_netns fi From 8deea967f2ba6ee2f196e7a3add28d42380ede77 Mon Sep 17 00:00:00 2001 From: The Fox in the Shell Date: Sat, 24 Dec 2016 15:05:20 +0100 Subject: [PATCH 6/8] netns: Refactor, introduce an in_ns function --- network/namespace.sh | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/network/namespace.sh b/network/namespace.sh index f61787c..b1d413b 100755 --- a/network/namespace.sh +++ b/network/namespace.sh @@ -32,6 +32,11 @@ function get_user_ipv6() { return "${IPV6_PREFIX}::${IPV6_SUFFIX}" } +# Execute some command in the user's netns +function in_ns() { + return ip netns exec "user-${PAM_USER}" $@ +} + # SYNOPSIS: This script constructs and configures a user namespace # for the user currently logging in. # @@ -68,8 +73,7 @@ if ! ip netns add "user-${PAM_USER}"; then die "Failed to create netns for ${PAM_USER}" fi -if ! ip netns exec "user-${PAM_USER}" \ - ip link set dev lo up; then +if ! in_ns ip link set dev lo up; then die "Failed to create a loopback interface for ${PAM_USER}" fi @@ -84,8 +88,7 @@ if ! ip link set "userbr-${PAM_USER}-peer" netns "user-${PAM_USER}"; then fi # Renaming the interface should happen before any configuration occurs -if ! ip netns exec "user-${PAM_USER}" \ - ip link set dev "userbr-${PAM_USER}-peer" name "veth"; then +if ! in_ns ip link set dev "userbr-${PAM_USER}-peer" name "veth"; then die "Failed to rename interface for ${PAM_USER}" fi @@ -93,18 +96,11 @@ if ! ip link set "userbr-${PAM_USER}" master "$USER_BR"; then die "Failed to add the veth for ${PAM_USER} to the bridge" fi -if ! ip netns exec "user-${PAM_USER}" \ - ip link set dev "userbr-${PAM_USER}" name "veth"; then - die "Failed to rename interface for ${PAM_USER}" -fi - -if ! ip netns exec "user-${PAM_USER}" \ - dhclient "veth"; then +if ! in_ns dhclient "veth"; then die "Failed to configure IPv4 for ${PAM_USER}" fi -if ! ip netns exec "user-${PAM_USER}" \ - ip addr add dev "veth" get_user_ip()/64; then +if ! in_ns ip addr add dev "veth" "get_user_ip()/64"; then die "Failed to configure IPv6 for ${PAM_USER}" fi From f25500fbfbb40d051f4fa1d9073cc497f1f10713 Mon Sep 17 00:00:00 2001 From: The Fox in the Shell Date: Sat, 24 Dec 2016 15:06:32 +0100 Subject: [PATCH 7/8] netns: No need to manually add routes RD/RA should take care of it --- network/namespace.sh | 6 ------ 1 file changed, 6 deletions(-) diff --git a/network/namespace.sh b/network/namespace.sh index b1d413b..05bde31 100755 --- a/network/namespace.sh +++ b/network/namespace.sh @@ -104,12 +104,6 @@ if ! in_ns ip addr add dev "veth" "get_user_ip()/64"; then die "Failed to configure IPv6 for ${PAM_USER}" fi -# TODO: Construct the server's own IPv6 from userdb info -if ! ip netns exec "user-${PAM_USER}" \ - ip route add dev "veth" default via get_server_ipv6(); then - die "Failed to configure IPv6 for ${PAM_USER}" -fi - # We reached the end without error: no cleanup on exit trap - QUIT exit 0 From 671205a239367744ac1933430825bf2aaf0ff53e Mon Sep 17 00:00:00 2001 From: The Fox in the Shell Date: Sat, 24 Dec 2016 15:17:44 +0100 Subject: [PATCH 8/8] netns: Shellcheck fixups - Avoid non-POSIX keywords (function, source) - Use proper quoting - Do not return strings - Avoid [ X -a Y ], as it isn't well-defined --- network/namespace.sh | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/network/namespace.sh b/network/namespace.sh index 05bde31..85facb4 100755 --- a/network/namespace.sh +++ b/network/namespace.sh @@ -3,7 +3,7 @@ # Read the pam_exec(8) manpage before editing this script. # root has no network namespace -if [ "$PAM_USER" == root ]; then +if [ "$PAM_USER" = root ]; then exit 0 fi @@ -11,30 +11,33 @@ fi # UID ranges defined by the Debian policy manual: # https://www.debian.org/doc/debian-policy/ch-opersys.html#s9.2.2 UID=$(getent passwd "$PAM_USER" | cut -d: -f3) -if [ $UID -lt 1000 ] || [ $UID -ge 60000 -a $UID -lt 65536 ]; then +if [ "$UID" -lt 1000 ]; then + exit 0 +fi +if [ "$UID" -ge 60000 ] && [ "$UID" -lt 65536 ]; then exit 0 fi # Check the logger manpage for valid priority levels -function log() { +log() { logger --id=$$ --priority auth."$1" "$2" } -function die() { +die() { log crit "$@" exit 1 } # Construct the user's IPv6, as {server_prefix}::{uid} -function get_user_ipv6() { +get_user_ipv6() { HEX_UID=$(echo "obase=16; ${UID}" | bc) IPV6_SUFFIX=$(echo "$HEX_UID" | rev | fold -w4 | paste -sd: | rev) - return "${IPV6_PREFIX}::${IPV6_SUFFIX}" + echo "${IPV6_PREFIX}::${IPV6_SUFFIX}" } # Execute some command in the user's netns -function in_ns() { - return ip netns exec "user-${PAM_USER}" $@ +in_ns() { + ip netns exec "user-${PAM_USER}" "$@" } # SYNOPSIS: This script constructs and configures a user namespace @@ -52,7 +55,7 @@ function in_ns() { USER_BR="br-users" if [ -f "/etc/default/user_netns" ]; then - source /etc/default/user_netns + . /etc/default/user_netns fi if [ -z "${IPV6_PREFIX}" ]; then @@ -100,7 +103,7 @@ if ! in_ns dhclient "veth"; then die "Failed to configure IPv4 for ${PAM_USER}" fi -if ! in_ns ip addr add dev "veth" "get_user_ip()/64"; then +if ! in_ns ip addr add dev "veth" "$(get_user_ip)/64"; then die "Failed to configure IPv6 for ${PAM_USER}" fi