Skip to content

Instantly share code, notes, and snippets.

@codemedic
Last active June 15, 2023 18:17
Show Gist options
  • Star 13 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save codemedic/80200e558df9104e0de0c5110af414ee to your computer and use it in GitHub Desktop.
Save codemedic/80200e558df9104e0de0c5110af414ee to your computer and use it in GitHub Desktop.
Docker network through host IPSec / Strongswan VPN
#!/usr/bin/env bash
# Link up docker network via IPSec VPN on docker-host.
#
# NOTE: This script can either be "sourced" into your .bashrc or executed directly. Be
# it sourced or executed, the usage syntax below is the same.
#
# Usage: [dry_run=1] [debug=1] vpn-docker-fix [docker-network-1 [docker-network-2 ...]]
#
# Env Variables:
# dry_run - Set to 1 to have a dry run, just printing out the iptables command
# debug - Set to 1 to see bash substitutions
vpn-docker-fix() {
# subshell to reduce "pollution" of the terminal shell when this script is "sourced"
# into .bashrc
(
: "${dry_run:=0}"
: "${debug:=0}"
_grep() {
/usr/bin/env grep "$@"
}
_awk() {
/usr/bin/env awk "$@"
}
_cut() {
/usr/bin/env cut "$@"
}
_log_stderr() {
echo "$*" >&2
}
_dry_run() {
if [ "$dry_run" = 1 ] || [ "$debug" = 1 ]; then
_log_stderr "$*"
fi
if [ "$dry_run" = 1 ]; then
return
fi
eval "$@"
}
_bash_debug_on() {
if [[ "$debug" != 1 ]]; then
return 0
fi
: "${bash_debug_level:=0}"
((bash_debug_level++))
[ -n "$bash_debug_state" ] || {
bash_debug_state=$(set +o | _grep xtrace)
set -x
}
}
_bash_debug_off() {
if [[ "$debug" != 1 ]]; then
return 0
fi
: "${bash_debug_level:=1}"
((bash_debug_level--))
if [ "$bash_debug_level" -le 0 ]; then
if [ -n "$bash_debug_state" ]; then
eval "$bash_debug_state"
unset bash_debug_state
fi
fi
}
# get docker network (if not specified)
get_docker_networks() {
local nets=()
if [[ $# == 0 ]]; then
nets=("$default_docker_network")
else
nets=("$@")
fi
printf '%s\n' "${nets[@]}"
}
# get docker network's CIDR
get_docker_network_subnet() {
if [ $# -lt 1 ] || [[ "$1" == "" ]]; then
_log_stderr "No docker network specified"
return 1
fi
local docker_network="$default_docker_network"
if [ "$1" != "$default_docker_network" ]; then
local net_hash
if ! net_hash=$(docker network ls -qf "Name=${1}") || [ -z "$net_hash" ]; then
_log_stderr "Docker network '$1' not found"
return 1
fi
docker_network="br-$net_hash"
fi
ip route show | _grep -v '169.254' | _grep " dev $docker_network" | _awk '{ print $1 }'
}
connect_docker_networks_to_vpn() {
if [ $# -lt 1 ] || [[ "$1" == "" ]]; then
_log_stderr "No docker network(s) specified"
return 1
fi
# vpnSubnet - VPN's CIDR
mapfile -t vpnSubnets < <(ip route list table 220 | _grep -o '^[0-9.]*/[0-9]*')
if [ "${#vpnSubnets}" -eq 0 ]; then
_log_stderr "VPN not active"
return 1
fi
# defaultRouteInterface - the active host network interface connecting to the VPN
defaultRouteInterface="$(ip route show | _grep -e "^default" | _awk '{ print $5 }')"
# virtualIP - virtual IP address of the host in the VPN
# - ignore dockerX and lo iterfaces
# - IP from interfaces that have valid-lifetime 'forever'
virtualIP="$(ip -4 -o addr show | _awk '/ (lo|docker|br-|tun)/ {next;} /valid_lft forever/ {print $4}' | _cut -d/ -f1)"
local docker_net docker_subnet vpn_subnet
for docker_net in "$@"; do
if docker_subnet="$(get_docker_network_subnet "$docker_net")"; then
for vpn_subnet in "${vpnSubnets[@]}"; do
_dry_run sudo iptables \
-j SNAT -t nat -I POSTROUTING 1 \
-o "$defaultRouteInterface" \
-d "$vpn_subnet" \
-s "$docker_subnet" \
--to-source "$virtualIP"
done
fi
done
}
_bash_debug_on
mapfile -t nets_chosen < <(get_docker_networks "$@")
connect_docker_networks_to_vpn "${nets_chosen[@]}"
_bash_debug_off
)
}
: "${default_docker_network:=docker0}"
# check if this script is being sourced or executed
if ! (return 0 2>/dev/null); then
# if not sourced, execute vpn-docker-fix with CLI agrs
vpn-docker-fix "$@"
else
_vpn-docker-fix_completions() {
local net nets=()
while read -r net; do
if [[ " ${COMP_WORDS[*]} " =~ " $net " ]]; then
continue
fi
nets+=("$net")
done < <(command docker network ls --format '{{.Name}}' --filter Driver=bridge | command grep -v ^bridge$)
mapfile -t COMPREPLY < <(compgen -W "${nets[*]}" -- ${COMP_WORDS[COMP_CWORD]})
}
complete -F _vpn-docker-fix_completions vpn-docker-fix
fi
@codemedic
Copy link
Author

This gist was developed based on this SO SF answer.
Once sourced into your bash shell, you can invoke command vpn-docker-fix in the below forms.

# setup an iptables rule to connect default network docker0 to VPN
vpn-docker-fix

# setup an iptables rule to connect the specific-network created via docker-compose or
# using 'docker network create' to VPN
vpn-docker-fix <specific-network>

# turn on debug
debug=1 vpn-docker-fix [...]

# dry run without applying any changes
dry_run=1 vpn-docker-fix [...]

# dry run and debug on
dry_run=1 debug=1 vpn-docker-fix [...]

@rszczypka
Copy link

do you run it on the host or in a docker container?

@codemedic
Copy link
Author

@rszczypka
You run it on the host.

@trytuna
Copy link

trytuna commented Mar 26, 2018

Thank you! That helped me!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment