Skip to content

Instantly share code, notes, and snippets.

@ansemjo
Created June 11, 2020 11:13
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ansemjo/5c7d3e0e231fb56c4f6aeed20909f3cc to your computer and use it in GitHub Desktop.
Save ansemjo/5c7d3e0e231fb56c4f6aeed20909f3cc to your computer and use it in GitHub Desktop.
yet another script to add wireguard client peers on an openwrt router from ssh
#!/bin/bash
set -eu -o pipefail
# MIT License
#
# Copyright (c) 2020 Anton Semjonov
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# ------------------------------------------------------------------ #
# This script will generate a new client configuration on an OpenWRT router
# and allow you to display a QR code and store the conf file in /tmp.
# It generates a private key for you which might not be what you want. In
# case you generate a new one on your client be sure to copy the new public
# key into your router's peer config!
#
# The script makes some assumptions to work corectly:
# - bash and libreadline must be installed
# - router has a **.**.**.1/24 IP on its WireGuard interface
# miscellaneous output functions
info() { printf "\033[36m$1\033[0m" "$2"; }
err() { printf '\033[31;1merr: \033[0;31m%s\033[0m\n' "$*"; }
# print usage information
manual() { cat >&2 <<MANUAL
usage: $ addwgpeer [-h] [-l] [-i interface] [...]
-h : display usage help
-i interface : specify WireGuard interface to use
-l : list existing peer configurations on interface
-a address : specify client ip address
-e endpoint : public endpoint address for client
-d dns : dns address for client
-r allowedips : list of allowed/routable ips for client
-c : persist -e/-d/-r settings in interface config with uci
MANUAL
}
# commandline parser
OPTSTORE="no"
LISTPEERS="no"
while getopts ":i:a:e:d:r:chl" OPTION; do
case "$OPTION" in
i) INTERFACE="$OPTARG" ;;
a) ADDRESS="$OPTARG" ;;
e) ENDPOINT="$OPTARG" ;;
d) DNS="$OPTARG" ;;
r) ALLOWEDIPS="$OPTARG" ;;
c) OPTSTORE="yes" ;;
l) LISTPEERS="yes" ;;
h) manual; exit 0; ;;
\?) err "invalid option: -$OPTARG"; manual; exit 1; ;;
:) err "invalid option: -$OPTARG requires an argument"; manual; exit 1; ;;
esac
done
shift $((OPTIND-1))
# check or find wireguard interface
if [[ -n ${INTERFACE+given} ]]; then
if ! [[ $(uci get "network.$INTERFACE.proto") == wireguard ]]; then
err "not a WireGuard interface: $(printf '%q' "$INTERFACE")"
exit 1
fi
wire=$INTERFACE
info "Using WireGuard interface: %s\n" "$wire"
else
# function to find wireguard interface from network config
find-wire() {
uci show network | sed -n 's/^network\.\([a-z0-9_]\+\)=interface$/\1/p' |\
while read iface; do
if [[ $(uci -q get "network.$iface.proto") == wireguard ]]; then
echo "$iface";
fi;
done;
}
wire="$(find-wire)"
if [[ -z $wire ]]; then
err "NO WireGuard interfaces found!"
exit 1
elif [[ $(echo "$wire" | wc -l) -gt 1 ]]; then
info "Multiple WireGuard interfaces found: %s\n" "$wire"
read -p "Enter interface: " -ei "$(echo "$wire" | head -1)" wire
else
info "Using WireGuard interface: %s\n" "$wire"
fi
fi
# ------------------------------------------------------------------ #
# --> display existing peer configs if -l flag given
if [[ $LISTPEERS == yes ]]; then
info "%s\n" "Listing existing peer configurations ..."
uci show network | grep "^network.@wireguard_$wire\[" | sed "s/\(.*\]=wireguard_$wire\)$/\n\1/"
echo
exit 0
fi
# --> store options in interface config if -c flag given
if [[ $OPTSTORE == yes ]]; then
if [[ -z ${ENDPOINT+given} ]] && [[ -z ${DNS+given} ]] && [[ -z ${ALLOWEDIPS+given} ]]; then
err "no options given to persist ..."
exit 1
fi
info "Persisting settings in %q ...\n" "network.$wire"
if [[ -n ${ENDPOINT+given} ]]; then
(set -x; uci set "network.$wire.addwgpeer_endpoint=$ENDPOINT";)
fi
if [[ -n ${DNS+given} ]]; then
(set -x; uci set "network.$wire.addwgpeer_dns=$DNS";)
fi
if [[ -n ${ALLOWEDIPS+given} ]]; then
(set -x; uci set "network.$wire.addwgpeer_allowedips=$ALLOWEDIPS";)
fi
(set -x; uci commit network;)
exit 0
fi
# ------------------------------------------------------------------ #
info "%s\n" "Begin semi-interactive client configuration ..."
echo
# --> generate a new random keypair
PRIVATEKEY=$(wg genkey)
PUBLICKEY=$(echo "$PRIVATEKEY" | wg pubkey)
printf "[Interface]\nPrivateKey = %s\n" "$PRIVATEKEY"
# --> check that the interface address and subnet are as expected
wireaddr=$(uci get "network.$wire.addresses")
if [[ -z $(echo "$wireaddr" | grep '\.1/24$') ]]; then
err "unexpected interface address on $wire: $wireaddr"
exit 1
fi
subnet=${wireaddr%%\.1/24}
# --> check if an ip address is not used by any peer
ipunused() {
local used_ips=$(uci show network | sed -n "s/^network\.@wireguard_$wire\[[0-9]\+\]\.allowed_ips=.\?\([0-9.]\+\)\/[0-9]\+.\?$/\1/p")
for used in $used_ips "$subnet.1" "$subnet.255"; do
if [[ ${1?newip} == $used ]]; then
return 1
fi
done
}
# --> check given address of find a random unused ipv4 address for peer
if [[ -n ${ADDRESS+given} ]]; then
if ! ipunused "${ADDRESS%%/[0-9]*}"; then
printf "\033[33;1m# WARN: \033[0;33maddress %q already in use by another peer\033[0m\n" "${ADDRESS%%/[0-9]*}"
fi
if ! [[ $ADDRESS =~ /[0-9]*$ ]]; then
ADDRESS="$ADDRESS/32"
fi
else
while true; do
addr="$subnet.$(printf '%d' "0x$(tr -dc '0-9a-f' </dev/urandom | head -c 2)")"
if ipunused "$addr"; then
ADDRESS="$addr/32"
break
fi
done
fi
printf "Address = %s\n" "$ADDRESS"
# --> print dns address for client
if [[ -z ${DNS+given} ]]; then
DNS=$(uci -q get "network.$wire.addwgpeer_dns" || true)
read -p "DNS = " -ei "${DNS:-$subnet.1}" DNS
else
printf "DNS = %s\n" "$DNS"
fi
echo
# --> print router peer block
SERVERPUB=$(uci get "network.$wire.private_key" | wg pubkey)
PRESHARED=$(wg genkey)
printf "[Peer]\nPublicKey = %s\nPresharedKey = %s\n" "$SERVERPUB" "$PRESHARED"
# --> get public endpoint address
if [[ -z ${ENDPOINT+given} ]]; then
ENDPOINT=$(uci -q get "network.$wire.addwgpeer_endpoint" || true)
if [[ -z $ENDPOINT ]]; then
# fallback to querying wtfismyip.com
ENDPOINT="$(wget -4 -q -O- https://ipv4.wtfismyip.com/text):51820"
fi
read -p "Endpoint = " -ei "$ENDPOINT" ENDPOINT
else
printf "Endpoint = %s\n" "$ENDPOINT"
fi
# --> print advertised routes
if [[ -z ${ALLOWEDIPS+given} ]]; then
ALLOWEDIPS=$(uci -q get "network.$wire.addwgpeer_allowedips" || true)
read -p "AllowedIPs = " -ei "${ALLOWEDIPS:-0.0.0.0/0, ::/0}" ALLOWEDIPS
else
printf "AllowedIPs = %s\n" "$ALLOWEDIPS"
fi
# function to print the complete conf from collected values
printconf() {
cat <<CONF
[Interface]
PrivateKey = $PRIVATEKEY
Address = $ADDRESS
DNS = $DNS
[Peer]
PublicKey = $SERVERPUB
PresharedKey = $PRESHARED
Endpoint = $ENDPOINT
AllowedIPs = $ALLOWEDIPS
CONF
}
echo
# ------------------------------------------------------------------ #
# --> ask to store the new peer in network configuration
info "%s" "Store the new client as a peer in router? [Y/n] "
read storeyn
if [[ -z $storeyn ]] || [[ ${storeyn^^} == Y ]]; then
info "%s" "Enter a description: "
read DESCRIPTION
(set -x
newpeer=$(uci add network "wireguard_$wire")
uci set "network.$newpeer.description=$DESCRIPTION"
uci set "network.$newpeer.public_key=$PUBLICKEY"
uci set "network.$newpeer.preshared_key=$PRESHARED"
uci set "network.$newpeer.allowed_ips=$ADDRESS"
uci set "network.$newpeer.route_allowed_ips=1"
uci commit network
)
info "Restart %s interface now? [Y/n] " "$wire"
read ifreupyn
if [[ -z $ifreupyn ]] || [[ ${ifreupyn^^} == Y ]]; then
(set -x; ifdown "$wire"; ifup "$wire";)
else
printf "\033[33mYou need to run '\033[1m%s\033[0;33m' for WireGuard to pick up the changes.\033[0m\n" "ifdown $wire; ifup $wire"
fi
echo
# --> display qr code if qrencode is installed
if command -V qrencode &>/dev/null; then
info "%s" "Display configuration QR code? [Y/n] "
read qrcodeyn
if [[ -z $qrcodeyn ]] || [[ ${qrcodeyn^^} == Y ]]; then
printconf | qrencode -t ansiutf8
echo
fi
else
info "%s\n" "Cannot display QR code because 'qrencode' is not installed."
fi
# --> ask to store conf in /tmp
info "%s" "Store the client configuration to a file in /tmp? [y/N] "
read storeyn
if [[ ${storeyn^^} == Y ]]; then
sanitizeddesc=$(echo "$DESCRIPTION" | tr " " "_" | tr -dc "a-zA-Z0-9_")
filename="/tmp/wireguard-$(date +%s)-$sanitizeddesc.conf"
printconf > "$filename"
printf "Saved configuration in %q\n" "$filename"
fi
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment