Skip to content

Instantly share code, notes, and snippets.

@Ghosthree3
Last active January 1, 2024 11:21
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Ghosthree3/21323ece7b5461eb634fdee3d3530b01 to your computer and use it in GitHub Desktop.
Save Ghosthree3/21323ece7b5461eb634fdee3d3530b01 to your computer and use it in GitHub Desktop.
Script for setting up locked down Network Namespaces with a different VPN in each
#!/usr/bin/env bash
set -o pipefail
# This script sets up an additional network namespace for every VPN you wish so that you may
# use any VPN at any time for any program without tunneling everything else on your system.
# Usage:
# Review and edit every line of this script not in a function, then run it as root
# on system startup (yes every reboot), eg. crontab @reboot /root/bin/vpnns.sh
# It is recommended that you run this script manually at least once as you will
# more easily discover the reason for any failures.
# Don't rerun for the same VPNs without either undoing its work
# or rebooting, as it will likely create a lot of mess.
if [[ $EUID -ne 0 ]]; then
echo "This must be run as root."
exit 1
fi
# This script uses nftables, so if you are currently using iptables, or an iptables wrapper,
# it is suggested you convert all the nft rules into iptables ones, or simply upgrade to nftables.
# This script assumes you have an inet filter table with an input chain, and an
# ip nat table with a postrouting chain already set up in nftables on your system.
# If this is not the case uncomment the four lines below.
#nft add table inet filter
#nft add chain inet filter input { type filter hook input priority 0 \; policy accept \; }
#nft add table ip nat
#nft add chain ip nat postrouting { type nat hook postrouting priority 0 \; policy accept \; }
# If you already have veth devices on your system, set this to the next in line.
# ie. If you already have veth0 and veth1, set veth_count to 2.
veth_count=0
# This is the starting point for the third octet in the subnets we'll be using (10.154.?.0/24).
# If this is set to 101 the first subnet used will be 10.154.101.0/24, then 10.154.102.0/24, etc.
oct3=101
# This dir will be used to store the current and previous log for all the OpenVPN instances.
log_dir=/etc/openvpn/logs/
if [[ ! -d $log_dir ]]; then
mkdir -p $log_dir
fi
# Call with create_namespace openvpn_config_filename [vpn_server_ip]
# If vpn_server_ip is not provided then the script will attempt to guess it.
create_namespace () {
veths=($((veth_count++)) $((veth_count++)))
if [[ -z ${2} ]]; then
remote_ip=$(grep '^remote ' /etc/openvpn/client/${1}.conf | cut -d' ' -f2 || \
echo "Could not resolve the server IP for /etc/openvpn/client/${1}.conf" ; exit 1)
else
remote_ip=${2}
fi
ip netns add ${1}
ip netns exec ${1} ip addr add 127.0.0.1/8 dev lo
ip netns exec ${1} ip link set lo up
ip link add veth${veths[0]} type veth peer name veth${veths[1]}
ip link set veth${veths[0]} up
ip link set veth${veths[1]} netns ${1} up
ip addr add 10.154.${oct3}.1/24 dev veth${veths[0]}
ip netns exec ${1} ip addr add 10.154.${oct3}.2/24 dev veth${veths[1]}
ip netns exec ${1} ip route add default via 10.154.${oct3}.1 dev veth${veths[1]}
ip netns exec ${1} ip route add 192.168.0.0/16 via 10.154.${oct3}.1
nft add rule inet filter input iifname != veth${veths[0]} ip saddr 10.154.${oct3}.0/24 drop
nft add rule ip nat postrouting oifname != veth${veths[0]} ip saddr 10.154.${oct3}.0/24 masquerade
ip netns exec ${1} bash -c "\
nft add table inet filter && \
nft add chain inet filter input { type filter hook input priority 0 \; policy drop \; } && \
nft add chain inet filter output { type filter hook output priority 0 \; policy drop \; } && \
nft add rule inet filter input iifname tun0 accept && \
nft add rule inet filter output oifname tun0 accept && \
nft add rule inet filter input iifname veth${veths[1]} ip saddr 192.168.0.0/16 accept && \
nft add rule inet filter output oifname veth${veths[1]} ip daddr 192.168.0.0/16 accept && \
nft add rule inet filter input iifname veth${veths[1]} ip saddr ${remote_ip} accept && \
nft add rule inet filter output oifname veth${veths[1]} ip daddr ${remote_ip} accept"
mkdir -p /etc/netns/${1}
cat << 'EOF' > /etc/netns/${1}/resolv.conf
# OpenDNS IPv4 nameservers
nameserver 208.67.222.222
nameserver 208.67.220.220
EOF
mv ${log_dir}/${1}.log ${log_dir}/${1}.log.old
ip netns exec ${1} openvpn --log ${log_dir}/${1}.log --config /etc/openvpn/client/${1}.conf &
((oct3++))
}
# Call with create_binary /path/to/binary
create_binary () {
if [[ ! -d $(dirname "$1") ]]; then
(umask 022 && mkdir -p $(dirname "$1"))
fi
cat << 'EOF' > "$1"
#!/usr/bin/env bash
if [[ $EUID -ne 0 ]]; then
sudo "$0" "$@"
exit $?
fi
ip netns exec $1 sudo -u $SUDO_USER PULSE_SERVER=unix:/run/user/$SUDO_UID/pulse/native "${@:2}"
EOF
chmod 755 "$1"
}
#create_namespace vpn1
#create_namespace vpn2
#create_namespace vpn3 vpn3.server.ip.addr
# Uncomment the final line if you wish to have a 'binary' created to more easily use your VPNs.
# Usage: Prefix any command you wish to run with 'vpn openvpn_config_filename' and the program
# will be opened in that namespace instead of the system default, tunneling all its traffic.
# The line 'youruser ALL=(root) NOPASSWD: /path/to/binary' can be added to sudoers to avoid
# the requirement of a password every time you open a program, this shouldn't be a security
# concern (read the 'binary' script).
#create_binary /usr/local/bin/vpn
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment