Skip to content
This repository has been archived by the owner on Mar 16, 2024. It is now read-only.

Commit

Permalink
Re-add support for iptables
Browse files Browse the repository at this point in the history
Add package for `useradd`
  • Loading branch information
wfg committed Jun 30, 2022
1 parent 650d55f commit 51fa5f2
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 69 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## Version 3.1.0 - 2022-06-30
### Changed
- `KILL_SWITCH` now requires `iptables` or `nftables` to be enabled. It defaults to `iptables`. See documentation for more information.

### Added
- Modified OpenVPN configuration file cleanup function.

## Version 3.0.0 - 2022-06-14
### Changed
- Refactored scripts
Expand Down
6 changes: 4 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ RUN apk add --no-cache \
bash \
bind-tools \
dante-server \
nftables \
iptables \
openvpn \
nftables \
shadow \
tinyproxy

COPY data/ /data/

ENV KILL_SWITCH=on
ENV KILL_SWITCH=iptables
ENV USE_VPN_DNS=on
ENV VPN_LOG_LEVEL=3

Expand Down
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ services:
| `VPN_AUTH_SECRET` | | Docker secret that contain the credentials for accessing the VPN. |
| `VPN_LOG_LEVEL` | `3` | OpenVPN logging verbosity (`1`-`11`) |
| `SUBNETS` | | A list of one or more comma-separated subnets (e.g. `192.168.0.0/24,192.168.1.0/24`) to allow outside of the VPN tunnel. |
| `KILL_SWITCH` | `on` | Whether or not to enable the network kill switch. |
| `KILL_SWITCH` | `iptables` | Which packet filterer to use for the kill switch. This value likely depends on your underlying host. Recommended to leave default unless you have problems. Acceptable values are `iptables` and `nftables`. To disable the kill switch, set to any other value. |
| `HTTP_PROXY` | | Whether or not to enable the built-in HTTP proxy server. To enable, set to any "truthy" value (see below the table). Any other value (including unset) will cause the proxy server to not run. It listens on port 8080. |
| `HTTP_PROXY_USERNAME` | | Credentials for accessing the HTTP proxy. If `HTTP_PROXY_USERNAME` is specified, you should also specify `HTTP_PROXY_PASSWORD`. |
| `HTTP_PROXY_PASSWORD` | | Credentials for accessing the HTTP proxy. If `HTTP_PROXY_PASSWORD` is specified, you should also specify `HTTP_PROXY_USERNAME`. |
Expand Down Expand Up @@ -126,6 +126,13 @@ docker run --rm -it --network=container:openvpn-client alpine wget -qO - ifconfi
```

### Troubleshooting
#### `can't initialize iptables`
If you see a message like the below in your logs, try setting `KILL_SWITCH` to `nftables`:
```
iptables v1.8.8 (legacy): can't initialize iptables table `filter': Table does not exist (do you need to insmod?)
Perhaps iptables or your kernel needs to be upgraded.
```

#### VPN authentication
Your OpenVPN configuration file may not come with authentication baked in.
To provide OpenVPN the necessary credentials, create a file (any name will work, but this example will use `credentials.txt`) next to the OpenVPN configuration file with your username on the first line and your password on the second line.
Expand Down
2 changes: 1 addition & 1 deletion data/config/socks-proxy.conf
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
logoutput: /var/log/sockd.log
logoutput: /var/log/dante.log
errorlog: stderr

internal: eth0 port = 1080
Expand Down
195 changes: 130 additions & 65 deletions data/scripts/entry.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,17 @@
set -e


cleanup() {
if [[ $openvpn_child ]]; then
kill SIGTERM "$openvpn_child"
fi

sleep 0.5
rm -f "$modified_config_file"
echo "info: exiting"
exit 0
}

is_enabled() {
[[ ${1,,} =~ ^(true|t|yes|y|1|on|enable|enabled)$ ]]
}
Expand Down Expand Up @@ -64,6 +75,7 @@ echo "info: original configuration file: $original_config_file"

# Create a new configuration file to modify so the original is left untouched.
modified_config_file=vpn/openvpn.$(tr -dc A-Za-z0-9 </dev/urandom | head -c8).conf
trap cleanup SIGTERM

echo "info: modified configuration file: $modified_config_file"
grep -Ev '(^up\s|^down\s)' "$original_config_file" > "$modified_config_file"
Expand All @@ -73,76 +85,126 @@ sed -i 's/\r$//g' "$modified_config_file"


default_gateway=$(ip -4 route | grep 'default via' | awk '{print $3}')
if is_enabled "$KILL_SWITCH" ; then
echo "info: kill switch is on"

nftables_config_file=config/nftables.conf

local_subnet=$(ip -4 route | grep 'scope link' | awk '{print $1}')

printf '%s\n' \
'#!/usr/bin/nft' '' \
'flush ruleset' '' \
'# base ruleset' \
'add table inet killswitch' '' \
'add chain inet killswitch incoming { type filter hook input priority 0; policy drop; }' \
'add rule inet killswitch incoming ct state established,related accept' \
'add rule inet killswitch incoming iifname lo accept' '' \
'add chain inet killswitch outgoing { type filter hook output priority 0; policy drop; }' \
'add rule inet killswitch outgoing ct state established,related accept' \
'add rule inet killswitch outgoing oifname lo accept' '' > $nftables_config_file

printf '%s\n' \
'# allow traffic to/from the Docker subnet' \
"add rule inet killswitch incoming ip saddr $local_subnet accept" \
"add rule inet killswitch outgoing ip daddr $local_subnet accept" '' >> $nftables_config_file

if [[ $SUBNETS ]]; then
printf '# allow traffic to/from the specified subnets\n' >> $nftables_config_file
for subnet in ${SUBNETS//,/ }; do
ip route add "$subnet" via "$default_gateway" dev eth0
printf '%s\n' \
"add rule inet killswitch incoming ip saddr $subnet accept" \
"add rule inet killswitch outgoing ip daddr $subnet accept" '' >> $nftables_config_file
done
fi

global_port=$(grep "^port " "$modified_config_file" | awk '{print $2}')
global_protocol=$(grep "^proto " "$modified_config_file" | awk '{print $2}') # {$2 = substr($2, 1, 3)} 2
remotes=$(grep "^remote " "$modified_config_file" | awk '{print $2, $3, $4}')

printf '# allow traffic to the VPN server(s)\n' >> $nftables_config_file
ip_regex='^(([1-9]?[0-9]|1[0-9][0-9]|2([0-4][0-9]|5[0-5]))\.){3}([1-9]?[0-9]|1[0-9][0-9]|2([0-4][0-9]|5[0-5]))$'
while IFS= read -r line; do
IFS=' ' read -ra remote <<< "$line"
address=${remote[0]}
port=${remote[1]:-${global_port:-1194}}
protocol=${remote[2]:-${global_protocol:-udp}}

if [[ $address =~ $ip_regex ]]; then
printf '%s\n' \
"add rule inet killswitch outgoing oifname eth0 ip daddr $address $protocol dport $port accept" >> $nftables_config_file
else
for ip in $(dig -4 +short "$address"); do
case "$KILL_SWITCH" in
'iptables')
echo "info: kill switch is using iptables"

iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

local_subnet=$(ip -4 route | grep 'scope link' | awk '{print $1}')
iptables -A INPUT -s "$local_subnet" -j ACCEPT
iptables -A OUTPUT -d "$local_subnet" -j ACCEPT

if [[ $SUBNETS ]]; then
for subnet in ${SUBNETS//,/ }; do
ip route add "$subnet" via "$default_gateway" dev eth0
iptables -A INPUT -s "$subnet" -j ACCEPT
iptables -A OUTPUT -d "$subnet" -j ACCEPT
done
fi

global_port=$(grep "^port " "$modified_config_file" | awk '{print $2}')
global_protocol=$(grep "^proto " "$modified_config_file" | awk '{print $2}') # {$2 = substr($2, 1, 3)} 2
remotes=$(grep "^remote " "$modified_config_file" | awk '{print $2, $3, $4}')
ip_regex='^(([1-9]?[0-9]|1[0-9][0-9]|2([0-4][0-9]|5[0-5]))\.){3}([1-9]?[0-9]|1[0-9][0-9]|2([0-4][0-9]|5[0-5]))$'
while IFS= read -r line; do
IFS=' ' read -ra remote <<< "$line"
address=${remote[0]}
port=${remote[1]:-${global_port:-1194}}
protocol=${remote[2]:-${global_protocol:-udp}}

if [[ $address =~ $ip_regex ]]; then
iptables -A OUTPUT -o eth0 -d "$address" -p "$protocol" --dport "$port" -j ACCEPT
else
for ip in $(dig -4 +short "$address"); do
iptables -A OUTPUT -o eth0 -d "$ip" -p "$protocol" --dport "$port" -j ACCEPT
printf "%s %s\n" "$ip" "$address" >> /etc/hosts
done
fi
done <<< "$remotes"
iptables -A INPUT -i tun0 -j ACCEPT
iptables -A OUTPUT -o tun0 -j ACCEPT
iptables -P INPUT DROP
iptables -P OUTPUT DROP
iptables -P FORWARD DROP
iptables-save > config/iptables.conf
;;

'nftables')
echo "info: kill switch is using nftables"
nftables_config_file=config/nftables.conf

printf '%s\n' \
'#!/usr/bin/nft' '' \
'flush ruleset' '' \
'# base ruleset' \
'add table inet killswitch' '' \
'add chain inet killswitch incoming { type filter hook input priority 0; policy drop; }' \
'add rule inet killswitch incoming ct state established,related accept' \
'add rule inet killswitch incoming iifname lo accept' '' \
'add chain inet killswitch outgoing { type filter hook output priority 0; policy drop; }' \
'add rule inet killswitch outgoing ct state established,related accept' \
'add rule inet killswitch outgoing oifname lo accept' '' > $nftables_config_file

local_subnet=$(ip -4 route | grep 'scope link' | awk '{print $1}')
printf '%s\n' \
'# allow traffic to/from the Docker subnet' \
"add rule inet killswitch incoming ip saddr $local_subnet accept" \
"add rule inet killswitch outgoing ip daddr $local_subnet accept" '' >> $nftables_config_file

if [[ $SUBNETS ]]; then
printf '# allow traffic to/from the specified subnets\n' >> $nftables_config_file
for subnet in ${SUBNETS//,/ }; do
ip route add "$subnet" via "$default_gateway" dev eth0
printf '%s\n' \
"add rule inet killswitch outgoing oifname eth0 ip daddr $ip $protocol dport $port accept" >> $nftables_config_file
printf "%s %s\n" "$ip" "$address" >> /etc/hosts
"add rule inet killswitch incoming ip saddr $subnet accept" \
"add rule inet killswitch outgoing ip daddr $subnet accept" '' >> $nftables_config_file
done
fi
done <<< "$remotes"

printf '%s\n' \
'' '# allow traffic over the VPN interface' \
"add rule inet killswitch incoming iifname tun0 accept" \
"add rule inet killswitch outgoing oifname tun0 accept" >> $nftables_config_file
global_port=$(grep "^port " "$modified_config_file" | awk '{print $2}')
global_protocol=$(grep "^proto " "$modified_config_file" | awk '{print $2}') # {$2 = substr($2, 1, 3)} 2
remotes=$(grep "^remote " "$modified_config_file" | awk '{print $2, $3, $4}')

nft -f $nftables_config_file
else
echo "info: kill switch is off"
for subnet in ${SUBNETS//,/ }; do
ip route add "$subnet" via "$default_gateway" dev eth0
done
fi
printf '# allow traffic to the VPN server(s)\n' >> $nftables_config_file
ip_regex='^(([1-9]?[0-9]|1[0-9][0-9]|2([0-4][0-9]|5[0-5]))\.){3}([1-9]?[0-9]|1[0-9][0-9]|2([0-4][0-9]|5[0-5]))$'
while IFS= read -r line; do
IFS=' ' read -ra remote <<< "$line"
address=${remote[0]}
port=${remote[1]:-${global_port:-1194}}
protocol=${remote[2]:-${global_protocol:-udp}}

if [[ $address =~ $ip_regex ]]; then
printf '%s\n' \
"add rule inet killswitch outgoing oifname eth0 ip daddr $address $protocol dport $port accept" >> $nftables_config_file
else
for ip in $(dig -4 +short "$address"); do
printf '%s\n' \
"add rule inet killswitch outgoing oifname eth0 ip daddr $ip $protocol dport $port accept" >> $nftables_config_file
printf "%s %s\n" "$ip" "$address" >> /etc/hosts
done
fi
done <<< "$remotes"

printf '%s\n' \
'' '# allow traffic over the VPN interface' \
"add rule inet killswitch incoming iifname tun0 accept" \
"add rule inet killswitch outgoing oifname tun0 accept" >> $nftables_config_file

nft -f $nftables_config_file
;;

*)
echo "info: kill switch is off"
for subnet in ${SUBNETS//,/ }; do
ip route add "$subnet" via "$default_gateway" dev eth0
done
;;

esac

if is_enabled "$HTTP_PROXY" ; then
scripts/run-http-proxy.sh &
Expand Down Expand Up @@ -174,4 +236,7 @@ if [[ $VPN_AUTH_SECRET ]]; then
openvpn_args+=("--auth-user-pass" "/run/secrets/$VPN_AUTH_SECRET")
fi

exec openvpn "${openvpn_args[@]}"
openvpn "${openvpn_args[@]}" &
openvpn_child=$!

wait $openvpn_child

0 comments on commit 51fa5f2

Please sign in to comment.