#!/bin/sh # # s2s_combined.sh: create configuration scripts that set up a # site-to-site VPN between two OpenWrt hosts using wireguard. # The site configurations are symmetric, each is a server with the # other as a peer. # # This script generates two script files, one for each site. The # generated files contain matched pre-shared keys and private/public key # values for the two sites. # # Run this script on one of the routers (site A for example), then # copy the generated script for the other site to the other router. # Run the appropriate script on each router: '/tmp/site-.sh' # # After running the generated scripts, DELETE the scripts, so that # nobody copies them and steals the keys. The keys are stored in the # network configuration files, which you are already protecting since # they have other confidential information such as wifi passwords, TLS # server keys, etc. # # The generated scripts configure a wireguard tunnel that carries IPv4 # and IPv6 through the tunnel. It routes the other site's IPv6 ULA # range through the tunnel, plus one IPv4 range. You can add more # routed networks later through LuCI or uci. # clear echo "======================================" echo "| Automated WireGuard Script |" echo "| Site-to-Site VPN |" echo "| Script generator |" echo "======================================" echo -n "Defining variables... " # Set the following values as needed to configure the generated scripts: # # Make this non-empty if you want to create scripts that only show # the configuration and don't actually set it. WG_TRIAL="" # The hostnames of the two OpenWrt routers. Use a dynamic DNS service # if needed so that your routers can find each other. WG_SITE_A_HOSTNAME="siteA.dynamic-dns.net" WG_SITE_B_HOSTNAME="siteB.dynamic-dns.net" # The description WG_SITE_A_DESCRIPTION="Site A, ${WG_SITE_A_HOSTNAME}" WG_SITE_B_DESCRIPTION="Site B, ${WG_SITE_B_HOSTNAME}" # The interface names at each site WG_SITE_A_IF="wg_s2s_a" WG_SITE_B_IF="wg_s2s_b" WG_PORT="51820" # The IPv4 range at each site. # Site A will route traffic to WG_SITE_B_LAN_RANGE through the tunnel # and vice-versa for site B WG_SITE_A_LAN_RANGE="192.168.0.0/24" WG_SITE_B_LAN_RANGE="192.168.1.0/24" # The IPv6 ULA prefixes for each host. The tunnel will be configured # to route to the peer's ULA addresses. Get this with # "uci get network.globals.ula_prefix |sed -e 's,::/.*,,'" WG_SITE_A_ULA_PREFIX="fdff:ffff:ffff" WG_SITE_B_ULA_PREFIX="fdee:eeee:eeee" # Route the IPv6 ULA prefix for the remote site through the tunnel WG_SITE_A_LAN_RANGE6="${WG_SITE_A_ULA_PREFIX}::/48" WG_SITE_B_LAN_RANGE6="${WG_SITE_B_ULA_PREFIX}::/48" # The firewall zone names at each site (the VPN tunnel endpoints are placed # in these zones). The zones must already exist before you run # the generated scripts. WG_SITE_A_VPN_ZONE=vpn WG_SITE_B_VPN_ZONE=vpn # The firewall WAN zone names at each site, used to configure WAN # firewall ingress rules to accept wireguard traffic on the chosen port WG_SITE_A_WAN_ZONE=wan WG_SITE_B_WAN_ZONE=wan # You probably don't need to change these unless you don't like these # internal names WG_SITE_A_CONFIG_NAME=s2s_vpn_site_a WG_SITE_B_CONFIG_NAME=s2s_vpn_site_b WG_FW_RULE_ID="wg_s2s_${WG_PORT}" echo "Done" if [ ! -z "${WG_TRIAL}" ]; then ECHO="echo echo" else ECHO="echo" fi cleanup() { echo -n "Removing temporary key files... " rm -f /tmp/wg_site_a.key /tmp/wg_site_a.pub rm -f /tmp/wg_site_b.key /tmp/wg_site_b.pub rm -f /tmp/wg_site_a_and_b.psk echo "Done" } trap cleanup EXIT cd /tmp # Generate keys umask go= echo -n "Generating WireGuard keys for sites A and B... " wg genkey | tee wg_site_a.key | wg pubkey > wg_site_a.pub wg genkey | tee wg_site_b.key | wg pubkey > wg_site_b.pub wg genpsk > wg_site_a_and_b.psk # Site_A keys WG_SITE_A_KEY="$(cat wg_site_a.key)" WG_SITE_A_PUB="$(cat wg_site_a.pub)" # Site_B keys WG_SITE_B_KEY="$(cat wg_site_b.key)" WG_SITE_B_PUB="$(cat wg_site_b.pub)" # Pre-shared key known by both WG_PSK="$(cat wg_site_a_and_b.psk)" echo "Done" create_site_config() { LOCAL_DESCRIPTION="${1}" LOCAL_VPN_ZONE="${2}" LOCAL_IF="${3}" LOCAL_WAN_ZONE="${4}" LOCAL_KEY="${5}" LOCAL_HOSTNAME="${6}" REMOTE_CONF="${7}" REMOTE_PUB="${8}" REMOTE_PSK="${9}" REMOTE_LAN_RANGE="${10}" REMOTE_LAN_RANGE6="${11}" REMOTE_HOSTNAME="${12}" REMOTE_DESCRIPTION="${13}" if [ -z "$REMOTE_DESCRIPTION" ]; then echo not enough args to subroutine 1>&2 exit 1 fi echo "#!/bin/sh" echo "clear" echo "echo ======================================" echo "echo \"| Automated WireGuard Script |\"" echo "echo \"| Site-to-Site VPN |\"" echo "echo \"| Configuration |\"" echo "echo ======================================" echo "echo Generated to configure \"${LOCAL_HOSTNAME}\" to tunnel with \"${REMOTE_HOSTNAME}\"" echo "echo -n Creating firewall rule for WAN ingress..." # find the zone in the firewall. There doesn't seem to be a way # to ask uci to show the zone with name=X, so we have # to search for it echo "i=0" echo "zone=" echo 'while uci -q get firewall.@zone[$i].name >/dev/null; do' echo ' if [ "$(uci -q get firewall.@zone[$i].name)" = "'${LOCAL_VPN_ZONE}'" ]; then' echo ' zone=$i' echo ' break' echo ' fi' echo ' i=$((i + 1))' echo 'done' echo 'if [ -z "$zone" ]; then' echo ' echo firewall zone '${LOCAL_VPN_ZONE}' not found' echo ' exit 1' echo 'fi' ${ECHO} uci del_list firewall.@zone['$zone'].network=\"${LOCAL_IF}\" ${ECHO} uci add_list firewall.@zone['$zone'].network=\"${LOCAL_IF}\" ${ECHO} uci -q delete firewall.${WG_FW_RULE_ID} ${ECHO} uci set firewall.${WG_FW_RULE_ID}=\"rule\" ${ECHO} uci set firewall.${WG_FW_RULE_ID}.name=\"Allow-WireGuard-${WG_PORT}\" ${ECHO} uci set firewall.${WG_FW_RULE_ID}.src=\"${LOCAL_WAN_ZONE}\" ${ECHO} uci set firewall.${WG_FW_RULE_ID}.dest_port=\"${WG_PORT}\" ${ECHO} uci set firewall.${WG_FW_RULE_ID}.proto=\"udp\" ${ECHO} uci set firewall.${WG_FW_RULE_ID}.target=\"ACCEPT\" ${ECHO} uci commit firewall ${ECHO} service firewall restart echo "echo Done" # Configure network, $LOCAL_DESCRIPTION tunnel endpoint echo "echo -n Configure wireguard interface \"${LOCAL_IF}\"..." ${ECHO} uci -q delete network.${LOCAL_IF} ${ECHO} uci set network.${LOCAL_IF}=\"interface\" ${ECHO} uci set network.${LOCAL_IF}.proto=\"wireguard\" ${ECHO} uci set network.${LOCAL_IF}.private_key=\"${LOCAL_KEY}\" ${ECHO} uci set network.${LOCAL_IF}.listen_port=\"${WG_PORT}\" echo "echo Done" # Add local site's ideas about remote site echo "echo -n Configure peer \"${REMOTE_DESCRIPTION}\"..." ${ECHO} uci -q delete network.${REMOTE_CONF} ${ECHO} uci set network.${REMOTE_CONF}=\"wireguard_${LOCAL_IF}\" ${ECHO} uci set network.${REMOTE_CONF}.public_key=\"${REMOTE_PUB}\" ${ECHO} uci set network.${REMOTE_CONF}.preshared_key=\"${REMOTE_PSK}\" ${ECHO} uci set network.${REMOTE_CONF}.description=\""${REMOTE_DESCRIPTION}"\" ${ECHO} uci add_list network.${REMOTE_CONF}.allowed_ips=\"${REMOTE_LAN_RANGE}\" ${ECHO} uci add_list network.${REMOTE_CONF}.allowed_ips=\"${REMOTE_LAN_RANGE6}\" ${ECHO} uci set network.${REMOTE_CONF}.route_allowed_ips=\'1\' ${ECHO} uci set network.${REMOTE_CONF}.persistent_keepalive=\'25\' ${ECHO} uci set network.${REMOTE_CONF}.endpoint_host=\"${REMOTE_HOSTNAME}\" ${ECHO} uci set network.${REMOTE_CONF}.endpoint_port=\"${WG_PORT}\" ${ECHO} uci commit network ${ECHO} service network restart echo "echo Done" echo "echo ======================================" echo "echo \"| Next steps |\"" echo "echo ======================================" echo "echo Remove this script: \"\$0\"" echo "echo It contains copies of your secret keys that" echo "echo you do not need anymore, because they are now in the network" echo "echo configuration files. Delete the script to avoid key theft." } echo -n "Creating configuration script for \"${WG_SITE_A_DESCRIPTION}\" ... " create_site_config \ "${WG_SITE_A_DESCRIPTION}" \ "${WG_SITE_A_VPN_ZONE}" \ "${WG_SITE_A_IF}" \ "${WG_SITE_A_WAN_ZONE}" \ "${WG_SITE_A_KEY}"\ "${WG_SITE_A_HOSTNAME}"\ "${WG_SITE_B_CONFIG_NAME}" \ "${WG_SITE_B_PUB}" \ "${WG_PSK}" \ "${WG_SITE_B_LAN_RANGE}" \ "${WG_SITE_B_LAN_RANGE6}" \ "${WG_SITE_B_HOSTNAME}" \ "${WG_SITE_B_DESCRIPTION}" >site-${WG_SITE_A_HOSTNAME}.sh chmod u+x site-${WG_SITE_A_HOSTNAME}.sh echo "Done" echo -n "Creating configuration script for \"${WG_SITE_B_DESCRIPTION}\" ... " create_site_config \ "${WG_SITE_B_DESCRIPTION}" \ "${WG_SITE_B_VPN_ZONE}" \ "${WG_SITE_B_IF}" \ "${WG_SITE_B_WAN_ZONE}" \ "${WG_SITE_B_KEY}"\ "${WG_SITE_B_HOSTNAME}"\ "${WG_SITE_A_CONFIG_NAME}" \ "${WG_SITE_A_PUB}" \ "${WG_PSK}" \ "${WG_SITE_A_LAN_RANGE}" \ "${WG_SITE_A_LAN_RANGE6}" \ "${WG_SITE_A_HOSTNAME}" \ "${WG_SITE_A_DESCRIPTION}" >site-${WG_SITE_B_HOSTNAME}.sh chmod u+x site-${WG_SITE_B_HOSTNAME}.sh echo "Done" echo "======================================" echo "| Next steps |" echo "======================================" echo "1. Copy /tmp/site-${WG_SITE_A_HOSTNAME}.sh to ${WG_SITE_A_HOSTNAME} and run it there," echo " then delete all copies of it to protect your keys." echo "2. Copy /tmp/site-${WG_SITE_B_HOSTNAME}.sh to ${WG_SITE_B_HOSTNAME} and run it there," echo " then delete all copies of it to protect your keys." echo "======================================" if [ ! -z "${WG_TRIAL}" ]; then echo "=========================================" echo "| Trial mode, scripts are nonfunctional |" echo "=========================================" fi exit 0