Created
June 11, 2020 11:13
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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