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..0f2aea1 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 br-users +iface br-users 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 br-users 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..85facb4 --- /dev/null +++ b/network/namespace.sh @@ -0,0 +1,112 @@ +#!/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 + +# 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 ]; then + exit 0 +fi +if [ "$UID" -ge 60000 ] && [ "$UID" -lt 65536 ]; then + exit 0 +fi + +# Check the logger manpage for valid priority levels +log() { + logger --id=$$ --priority auth."$1" "$2" +} + +die() { + log crit "$@" + exit 1 +} + +# Construct the user's IPv6, as {server_prefix}::{uid} +get_user_ipv6() { + HEX_UID=$(echo "obase=16; ${UID}" | bc) + IPV6_SUFFIX=$(echo "$HEX_UID" | rev | fold -w4 | paste -sd: | rev) + echo "${IPV6_PREFIX}::${IPV6_SUFFIX}" +} + +# Execute some command in the user's netns +in_ns() { + ip netns exec "user-${PAM_USER}" "$@" +} + +# 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="br-users" +if [ -f "/etc/default/user_netns" ]; then + . /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 + log info "User ${PAM_USER} already has a netns" + exit 0 +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 ! in_ns 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 ! in_ns 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 ! 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 + 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 ###################################################################