Forked from duboisf/openconnect_vpn_inside_netns.sh
Created
January 30, 2024 23:41
-
-
Save socketbox/929378a16b43ed9026a226eb25fabe18 to your computer and use it in GitHub Desktop.
OpenConnect VPN Inside Linux Network Namespace
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# start openconnect tunnel and shell inside Linux network namespace | |
# | |
# this is a fork of schnouki's script, see original blog post | |
# https://schnouki.net/posts/2014/12/12/openvpn-for-a-single-application-on-linux/ | |
# | |
# original script can be found here | |
# https://gist.github.com/Schnouki/fd171bcb2d8c556e8fdf | |
# ------------ adjust values below ------------ | |
VPN_ENDPOINT="${VPN_ENDPOINT:-}" | |
VPN_USER="${VPN_USER:-}" | |
VPN_GROUP="${VPN_GROUP:-}" | |
NETNS_NAME="${NETNS_NAME:-}" | |
NETNS_EXEC="ip netns exec $NETNS_NAME" | |
WIRED_INTERFACE="enp1s0" | |
WIRELESS_INTERFACE="wlp2s0" | |
OUT_IF="${NETNS_NAME}-out" | |
IN_IF="${NETNS_NAME}-in" | |
OUT_IP="10.200.200.1" | |
IN_IP="10.200.200.2" | |
URLS_TO_FORWARD="${URLS_TO_FORWARD:-}" | |
set -ueo pipefail | |
MISSING_VAR= | |
for VAR_NAME in NETNS_NAME VPN_ENDPOINT VPN_USER VPN_GROUP; do | |
eval VAR="\${$VAR_NAME}" | |
if [[ -z $VAR ]]; then | |
MISSING_VAR=1 | |
echo "Must define env var \$$VAR_NAME" | |
fi | |
done | |
[[ -z $MISSING_VAR ]] || exit 1 | |
if [[ $USER != root ]]; then | |
echo "You must run this script as root, preferably with sudo." | |
exit 1 | |
fi | |
create_ns() { | |
echo "Add network interface" | |
# create the network namespace | |
ip netns add "$NETNS_NAME" | |
# start the loopback interface in the namespace | |
$NETNS_EXEC ip addr add 127.0.0.1/8 dev lo | |
$NETNS_EXEC ip link set lo up | |
# create virtual network interfaces that will let OpenVPN (in the | |
# namespace) access the real network, and configure the interface in the | |
# namespace (${IN_IF}) to use the interface out of the namespace (${OUT_IF}) | |
# as its default gateway | |
ip link add "${OUT_IF}" type veth peer name "${IN_IF}" | |
ip link set "${OUT_IF}" up | |
ip link set "${IN_IF}" netns "${NETNS_NAME}" up | |
ip addr add "${OUT_IP}"/24 dev "${OUT_IF}" | |
$NETNS_EXEC ip addr add "${IN_IP}"/24 dev "${IN_IF}" | |
$NETNS_EXEC ip link set dev "${IN_IF}" mtu 1492 | |
$NETNS_EXEC ip route add default via "${OUT_IP}" dev "${IN_IF}" | |
# make sure ipv4 forwarding is enabled | |
sysctl -w net.ipv4.ip_forward=1 | |
# configure the nameserver to use inside the namespace | |
# TODO use VPN-provided DNS servers in order to prevent leaks | |
mkdir -p "/etc/netns/${NETNS_NAME}" | |
cat <<EOF | tee "/etc/netns/${NETNS_NAME}/resolv.conf" || exit 1 | |
nameserver 8.8.8.8 | |
nameserver 8.8.4.4 | |
EOF | |
# Setup IP masquerading in the default namespace for paquets coming from the network namespace | |
# so that they can communicate with the internet | |
iptables -t nat -A POSTROUTING -o "${WIRED_INTERFACE}" -m mark --mark 0x29a -j MASQUERADE | |
iptables -t nat -A POSTROUTING -o "${WIRELESS_INTERFACE}" -m mark --mark 0x29a -j MASQUERADE | |
iptables -t mangle -A PREROUTING -i "${OUT_IF}" -j MARK --set-xmark 0x29a/0xffffffff | |
if which ufw && [[ $(ufw status | head -n 1) == *active ]]; then | |
# If you have ufw, you need to allow paquets coming from the netns to flow through ufw | |
ufw route allow in on "${OUT_IF}" out on "${WIRED_INTERFACE}" | |
ufw route allow in on "${OUT_IF}" out on "${WIRELESS_INTERFACE}" | |
fi | |
VPN_PASS=$(systemd-ask-password --keyname=openconnect-$NETNS_NAME --accept-cached "Password for vpn $NETNS_NAME:") | |
echo -e "${VPN_PASS}\npush" | $NETNS_EXEC /usr/sbin/openconnect \ | |
--interface vpn0 $VPN_ENDPOINT \ | |
-u $VPN_USER --authgroup=$VPN_GROUP --passwd-on-stdin & | |
sleep 1 | |
while ! $NETNS_EXEC ip link show dev vpn0 &>/dev/null; do sleep 1; done | |
# Enable IP masquerading for IP paquets comming from default namespace. | |
# My use case was forwarding IPs for private docker registries since the docker | |
# daemon runs in the default namespace. I added routes to forward the IPs to | |
# the vpn namespace, but for it to work you need to masquerade those IP paquets | |
# being forwarded from the default namespace. | |
$NETNS_EXEC iptables -t mangle -A PREROUTING -i $IN_IF --src $OUT_IP -j MARK --set-xmark 0x8/0x0 | |
$NETNS_EXEC iptables -t nat -A POSTROUTING -m mark --mark 0x8 -j MASQUERADE | |
for url in ${URLS_TO_FORWARD}; do | |
for ip in $(dig +short $url); do | |
ip route replace $ip via ${IN_IP} | |
done | |
done | |
} | |
tear_down_ns() { | |
set +eo pipefail | |
echo "Stopping VPN" | |
if NS_PIDS=$(ip netns pids $NETNS_NAME 2> /dev/null) && [[ -n $NS_PIDS ]]; then | |
echo "$NS_PIDS" | xargs -rd'\n' kill -SIGINT &>/dev/null | |
fi | |
echo "Cleaning up iptables" | |
iptables -t nat -D POSTROUTING -o "${WIRED_INTERFACE}" -m mark --mark 0x29a -j MASQUERADE &>/dev/null | |
iptables -t nat -D POSTROUTING -o "${WIRELESS_INTERFACE}" -m mark --mark 0x29a -j MASQUERADE &>/dev/null | |
iptables -t mangle -D PREROUTING -i "${OUT_IF}" -j MARK --set-xmark 0x29a/0xffffffff &>/dev/null | |
if which ufw && [[ $(ufw status | head -n 1) == *active ]]; then | |
# cleanup ufw | |
ufw route delete allow in on "${OUT_IF}" out on "${WIRED_INTERFACE}" &>/dev/null | |
ufw route delete allow in on "${OUT_IF}" out on "${WIRELESS_INTERFACE}" &>/dev/null | |
fi | |
echo "Cleanup network namespace" | |
rm -rf "/etc/netns/${NETNS_NAME}" &>/dev/null | |
ip netns delete "${NETNS_NAME}" &>/dev/null | |
ip link delete "${OUT_IF}" &>/dev/null | |
} | |
set -x | |
trap tear_down_ns EXIT | |
create_ns | |
echo "Starting shell inside $NETNS_NAME network namespace" | |
# start shell in new network namespace | |
SU_USER=${SUDO_USER:-} | |
$NETNS_EXEC su ${SU_USER} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment