From 3a6949dfaffda130a2945f714305bcafb8ac98fa Mon Sep 17 00:00:00 2001 From: Nick Hainke Date: Thu, 4 Feb 2021 16:34:02 +0100 Subject: [PATCH] wg-installer: add wg-installer This tool can be used to automatically create wireguard tunnels. Using rpcd a new wireguard interface is created on the server where the client can connect to. Wiregurad server automatically installs a user and associated ACL to use the wireguard-installer-server features. The user is called wginstaller and so is the password. Get Usage: wg-client-installer get_usage --ip 127.0.0.1 --user wginstaller --password wginstaller Register Interface: wg-client-installer register --ip 127.0.0.1 --user wginstaller --password wginstaller --bandwidth 10 --mtu 1400 Signed-off-by: Nick Hainke --- net/wg-installer/Makefile | 69 +++++++++ net/wg-installer/README.md | 26 ++++ net/wg-installer/common/wg.sh | 13 ++ .../wg-client/config/wgclient.conf | 8 ++ net/wg-installer/wg-client/lib/rpcd_ubus.sh | 134 ++++++++++++++++++ .../wg-client/wg-client-installer.sh | 119 ++++++++++++++++ .../wg-server/config/wginstaller.json | 19 +++ .../wg-server/config/wgserver.conf | 6 + .../wg-server/lib/install_wginstaller_user.sh | 22 +++ .../wg-server/lib/wg_functions.sh | 46 ++++++ net/wg-installer/wg-server/wginstaller.sh | 32 +++++ 11 files changed, 494 insertions(+) create mode 100644 net/wg-installer/Makefile create mode 100644 net/wg-installer/README.md create mode 100644 net/wg-installer/common/wg.sh create mode 100644 net/wg-installer/wg-client/config/wgclient.conf create mode 100644 net/wg-installer/wg-client/lib/rpcd_ubus.sh create mode 100644 net/wg-installer/wg-client/wg-client-installer.sh create mode 100644 net/wg-installer/wg-server/config/wginstaller.json create mode 100644 net/wg-installer/wg-server/config/wgserver.conf create mode 100644 net/wg-installer/wg-server/lib/install_wginstaller_user.sh create mode 100644 net/wg-installer/wg-server/lib/wg_functions.sh create mode 100644 net/wg-installer/wg-server/wginstaller.sh diff --git a/net/wg-installer/Makefile b/net/wg-installer/Makefile new file mode 100644 index 000000000..d43407b03 --- /dev/null +++ b/net/wg-installer/Makefile @@ -0,0 +1,69 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=wg-installer +PKG_RELEASE:=$(AUTORELEASE) + +PKG_MAINTAINER:=Nick Hainke + +include $(INCLUDE_DIR)/package.mk + +Build/Compile= + +define Package/wg-installer/Default + SECTION:=net + CATEGORY:=Network + TITLE:=WireGuard Installer + URL:=https://github.com/Freifunk-Spalter/ + PKGARCH:=all + DEPENDS:=+kmod-wireguard +owipcalc +endef + +define Package/wg-installer-server + $(call Package/wg-installer/Default) + TITLE+= (server) + DEPENDS:=+rpcd +uhttpd +uhttpd-mod-ubus +owipcalc +endef + +define Package/wg-installer-server/install + $(INSTALL_DIR) $(1)/usr/share/wginstaller/ + $(INSTALL_BIN) ./wg-server/lib/install_wginstaller_user.sh $(1)/usr/share/wginstaller/install_wginstaller_user.sh + $(INSTALL_BIN) ./wg-server/lib/wg_functions.sh $(1)/usr/share/wginstaller/wg_functions.sh + $(INSTALL_BIN) ./common/wg.sh $(1)/usr/share/wginstaller/wg.sh + + $(INSTALL_DIR) $(1)/usr/libexec/rpcd/ + $(INSTALL_BIN) ./wg-server/wginstaller.sh $(1)/usr/libexec/rpcd/wginstaller + + $(INSTALL_DIR) $(1)/usr/share/rpcd/acl.d + $(CP) ./wg-server/config/wginstaller.json $(1)/usr/share/rpcd/acl.d/ + + $(INSTALL_DIR) $(1)/etc/config + $(INSTALL_CONF) ./wg-server/config/wgserver.conf $(1)/etc/config/wgserver +endef + +define Package/wg-installer-server/postinst + #!/bin/sh + if [ -z $${IPKG_INSTROOT} ] ; then + . /usr/share/wginstaller/install_wginstaller_user.sh + fi +endef + +define Package/wg-installer-client + $(call Package/wg-installer/Default) + TITLE+= (client) + DEPENDS:=+curl +wireguard-tools +endef + +define Package/wg-installer-client/install + $(INSTALL_DIR) $(1)/usr/share/wginstaller/ + $(INSTALL_BIN) ./wg-client/lib/rpcd_ubus.sh $(1)/usr/share/wginstaller/rpcd_ubus.sh + $(INSTALL_BIN) ./common/wg.sh $(1)/usr/share/wginstaller/wg.sh + + $(INSTALL_DIR) $(1)/usr/bin + $(INSTALL_BIN) ./wg-client/wg-client-installer.sh $(1)/usr/bin/wg-client-installer + + $(INSTALL_DIR) $(1)/etc/config + $(INSTALL_CONF) ./wg-client/config/wgclient.conf $(1)/etc/config/wgclient +endef + +$(eval $(call BuildPackage,wg-installer-server)) +$(eval $(call BuildPackage,wg-installer-client)) diff --git a/net/wg-installer/README.md b/net/wg-installer/README.md new file mode 100644 index 000000000..f64fa61d3 --- /dev/null +++ b/net/wg-installer/README.md @@ -0,0 +1,26 @@ +## Wireguard Installer + +This tool can be used to automatically create wireguard tunnels. Using rpcd a new wireguard interface is created on the server where the client can connect to. + +## Installation + +For Server + + opkg install wireguard-installer-server + +For Client + + opkg install wireguard-installer-client + +Wiregurad server automatically installs a user and associated ACL to use the wireguard-installer-server features. +The user is called wginstaller and so is the password. + +## Usage + +Get Usage Statistics + + wg-client-installer get_usage --ip 127.0.0.1 --user wginstaller --password wginstaller + +Register Tunnel Interface + + wg-client-installer register --ip 127.0.0.1 --user wginstaller --password wginstaller --bandwidth 10 diff --git a/net/wg-installer/common/wg.sh b/net/wg-installer/common/wg.sh new file mode 100644 index 000000000..372d7f4ca --- /dev/null +++ b/net/wg-installer/common/wg.sh @@ -0,0 +1,13 @@ +next_port () { + local port_start=$1 + local port_end=$2 + + ports=$(wg show all listen-port | awk '{print $2}') + + for i in $(seq $port_start $port_end); do + if ! echo $ports | grep -q "$i"; then + echo $i + return + fi + done +} diff --git a/net/wg-installer/wg-client/config/wgclient.conf b/net/wg-installer/wg-client/config/wgclient.conf new file mode 100644 index 000000000..b7adc33ba --- /dev/null +++ b/net/wg-installer/wg-client/config/wgclient.conf @@ -0,0 +1,8 @@ +config client + option wg_key '/root/wg.key' + option wg_pub '/root/wg.pub' + option base_prefix '2000::/64' + option port_start '51820' + option port_end '52820' + option try_insecure '1' + option try_http '1' diff --git a/net/wg-installer/wg-client/lib/rpcd_ubus.sh b/net/wg-installer/wg-client/lib/rpcd_ubus.sh new file mode 100644 index 000000000..b1e283300 --- /dev/null +++ b/net/wg-installer/wg-client/lib/rpcd_ubus.sh @@ -0,0 +1,134 @@ +. /usr/share/libubox/jshn.sh + +query_gw () { + local ip=$1 + local req=$2 + + # first try https + ret=$(curl https://$ip/ubus -d "$req") 2>/dev/null + if [ $? -eq 0 ]; then + echo $ret + return 0 + fi + + # try with --insecure + if [ $(uci get wgclient.@client[0].try_insecure) == '1' ]; then + ret=$(curl --insecure https://$ip/ubus -d "$req") 2>/dev/null + if [ $? -eq 0 ]; then + echo $ret + return 0 + fi + fi + + # try with http + if [ $(uci get wgclient.@client[0].try_http) == '1' ]; then + ret=$(curl http://$ip/ubus -d "$req") 2>/dev/null + if [ $? -eq 0 ]; then + echo $ret + return 0 + fi + fi + + return 1 +} + +request_token () { + local ip=$1 + local user=$2 + local password=$3 + + json_init + json_add_string "jsonrpc" "2.0" + json_add_int "id" "1" + json_add_string "method" "call" + json_add_array "params" + json_add_string "" "00000000000000000000000000000000" + json_add_string "" "session" + json_add_string "" "login" + json_add_object + json_add_string "username" $user + json_add_string "password" $password + json_close_object + json_close_array + req=$(json_dump) + ret=$(query_gw $ip "$req") 2>/dev/null + if [ $? != 0 ]; then + return 1 + fi + json_load "$ret" + json_get_vars result result + json_select result + json_select 2 + json_get_var ubus_rpc_session ubus_rpc_session + echo $ubus_rpc_session +} + +wg_rpcd_get_usage () { + local token=$1 + local ip=$2 + local secret=$3 + + json_init + json_add_string "jsonrpc" "2.0" + json_add_int "id" "1" + json_add_string "method" "call" + json_add_array "params" + json_add_string "" $token + json_add_string "" "wginstaller" + json_add_string "" "get_usage" + json_add_object + json_close_object + json_close_array + req=$(json_dump) + ret=$(query_gw $ip "$req") 2>/dev/null + if [ $? != 0 ]; then + return 1 + fi + + # return values + json_load "$ret" + json_get_vars result result + json_select result + json_select 2 + json_get_var num_interfaces num_interfaces + echo "num_interfaces: ${num_interfaces}" +} + +wg_rpcd_register () { + local token=$1 + local ip=$2 + local uplink_bw=$3 + local mtu=$4 + local public_key=$5 + + json_init + json_add_string "jsonrpc" "2.0" + json_add_int "id" "1" + json_add_string "method" "call" + json_add_array "params" + json_add_string "" $token + json_add_string "" "wginstaller" + json_add_string "" "register" + json_add_object + json_add_int "uplink_bw" $uplink_bw + json_add_int "mtu" $mtu + json_add_string "public_key" $public_key + json_close_object + json_close_array + req=$(json_dump) + ret=$(query_gw $ip "$req") 2>/dev/null + if [ $? != 0 ]; then + return 1 + fi + + json_load "$ret" + json_get_vars result result + json_select result + json_select 2 + json_get_var pubkey pubkey + json_get_var gw_ip gw_ip + json_get_var port port + echo "pubkey: ${pubkey}" + echo "gw_ip: ${gw_ip}" + echo "port: ${port}" +} diff --git a/net/wg-installer/wg-client/wg-client-installer.sh b/net/wg-installer/wg-client/wg-client-installer.sh new file mode 100644 index 000000000..7484f4973 --- /dev/null +++ b/net/wg-installer/wg-client/wg-client-installer.sh @@ -0,0 +1,119 @@ +#!/bin/sh + +. /usr/share/wginstaller/rpcd_ubus.sh +. /usr/share/wginstaller/wg.sh + +CMD=$1 +shift + +while true; do + case "$1" in + -h | --help) + echo "help" + shift 1 + ;; + -i | --ip) + IP=$2 + shift 2 + ;; + --user) + USER=$2 + shift 2 + ;; + --password) + PASSWORD=$2 + shift 2 + ;; + --bandwidth) + BANDWIDTH=$2 + shift 2 + ;; + --mtu) + WG_MTU=$2 + shift 2 + ;; + '') + break + ;; + *) + break + ;; + esac +done + +escape_ip () { + local gw_ip=$1 + + # ipv4 processing + ret_ip=$(echo $gw_ip | tr '.' '_') + + # ipv6 processing + ret_ip=$(echo $ret_ip | tr ':' '_') + ret_ip=$(echo $ret_ip | cut -d '[' -f 2) + ret_ip=$(echo $ret_ip | cut -d ']' -f 1) + + echo $ret_ip +} + +register_client_interface () { + local pubkey=$1 + local gw_ip=$2 + local gw_port=$3 + local endpoint=$4 + local mtu_client=$5 + + gw_key=$(uci get wgclient.@client[0].wg_key) + interface_name="gw_$(escape_ip $endpoint)" + port_start=$(uci get wgclient.@client[0].port_start) + port_end=$(uci get wgclient.@client[0].port_end) + base_prefix=$(uci get wgclient.@client[0].base_prefix) + + port=$(next_port $port_start $port_end) + ifname="wg_$port" + + offset=$(($port - $port_start)) + client_ip=$(owipcalc $base_prefix add $offset next 128) + client_ip_assign="${client_ip}/128" + + echo "Installing Interface With:" + echo "Endpoint ${endpoint}" + echo "Client IP ${client_ip}" + echo "Port ${port}" + echo "Pubkey ${pubkey}" + + ip link add dev $ifname type wireguard + + ip -6 a a dev $ifname $client_ip + wg set $ifname listen-port $port private-key $gw_key peer $pubkey allowed-ips ::/0 endpoint "${endpoint}:${gw_port}" + ip link set up dev $ifname + ip link set mtu $mtu_client dev $ifname # configure mtu here! +} + +# rpc login +token="$(request_token $IP $USER $PASSWORD)" +if [ $? != 0 ]; then + echo "failed to register token" + exit 1 +fi + +# now call procedure +case $CMD in +"get_usage") + wg_rpcd_get_usage $token $IP + ;; +"register") + gw_pub=$(uci get wgclient.@client[0].wg_pub) + gw_pub_string=$(cat $gw_pub) + register_output=$(wg_rpcd_register $token $IP $BANDWIDTH $WG_MTU $gw_pub_string) + if [ $? != 0 ]; then + echo "Failed to Register!" + exit 1 + fi + pubkey=$(echo $register_output | awk '{print $2}') + ip_addr=$(echo $register_output | awk '{print $4}') + port=$(echo $register_output | awk '{print $6}') + client_ip=$(echo $register_output | awk '{print $8}') + register_client_interface $pubkey $ip_addr $port $IP $WG_MTU + ;; +*) echo "Usage: wg-client-installer [cmd] --ip [2001::1] --user wginstaller --password wginstaller" ;; +esac diff --git a/net/wg-installer/wg-server/config/wginstaller.json b/net/wg-installer/wg-server/config/wginstaller.json new file mode 100644 index 000000000..77ae25ec9 --- /dev/null +++ b/net/wg-installer/wg-server/config/wginstaller.json @@ -0,0 +1,19 @@ +{ + "wginstaller": { + "description": "WireGuard Installer", + "read": { + "ubus": { + "wginstaller": [ "*" ], + "session": [ + "access", + "login" + ] + } + }, + "write": { + "ubus": { + "wginstaller": [ "*" ] + } + } + } +} diff --git a/net/wg-installer/wg-server/config/wgserver.conf b/net/wg-installer/wg-server/config/wgserver.conf new file mode 100644 index 000000000..a88a8f94e --- /dev/null +++ b/net/wg-installer/wg-server/config/wgserver.conf @@ -0,0 +1,6 @@ +config server + option port_start '51820' + option port_end '52820' + option base_prefix '2002::/64' + option wg_key '/root/wg.key' + option wg_pub '/root/wg.pub' diff --git a/net/wg-installer/wg-server/lib/install_wginstaller_user.sh b/net/wg-installer/wg-server/lib/install_wginstaller_user.sh new file mode 100644 index 000000000..6ab13f1cc --- /dev/null +++ b/net/wg-installer/wg-server/lib/install_wginstaller_user.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +# do not override already existing user!!! +[ "$(uci show rpcd | grep wginstaller)" ] && exit 0 + +# install wginstaller user with standard credentials +# user: wginstaller +# password: wginstaller +uci add rpcd login +uci set rpcd.@login[-1].username='wginstaller' + +password=$(uhttpd -m wginstaller) +uci set rpcd.@login[-1].password=$password +uci add_list rpcd.@login[-1].read='wginstaller' +uci add_list rpcd.@login[-1].write='wginstaller' +uci commit rpcd + +# restart rpcd +/etc/init.d/rpcd restart + +# restart uhttpd +/etc/init.d/uhttpd restart diff --git a/net/wg-installer/wg-server/lib/wg_functions.sh b/net/wg-installer/wg-server/lib/wg_functions.sh new file mode 100644 index 000000000..6a5646b62 --- /dev/null +++ b/net/wg-installer/wg-server/lib/wg_functions.sh @@ -0,0 +1,46 @@ +. /usr/share/libubox/jshn.sh +. /usr/share/wginstaller/wg.sh + +wg_get_usage () { + num_interfaces = $(wg show interfaces | wc -w) + json_init + json_add_int "num_interfaces" $num_interfaces + echo $(json_dump) +} + +wg_register () { + local uplink_bw=$1 + local mtu=$2 + local public_key=$3 + + base_prefix=$(uci get wgserver.@server[0].base_prefix) + port_start=$(uci get wgserver.@server[0].port_start) + port_end=$(uci get wgserver.@server[0].port_end) + + port=$(next_port $port_start $port_end) + ifname="wg_$port" + + offset=$(($port - $port_start)) + gw_ip=$(owipcalc $base_prefix add $offset next 128) # gateway ip + gw_ip_assign="${gw_ip}/128" + + gw_key=$(uci get wgserver.@server[0].wg_key) + gw_pub=$(uci get wgserver.@server[0].wg_pub) + wg_server_pubkey=$(cat $gw_pub) + + # create wg tunnel + ip link add dev $ifname type wireguard + wg set $ifname listen-port $port private-key $gw_key peer $public_key allowed-ips ::0/0 + ip -6 a a $gw_ip_assign dev $ifname + ip -6 a a fe80::1/64 dev $ifname + ip link set up dev $ifname + ip link set mtu $mtu dev $ifname + + # craft return address + json_init + json_add_string "pubkey" $wg_server_pubkey + json_add_string "gw_ip" $gw_ip_assign + json_add_int "port" $port + + echo $(json_dump) +} diff --git a/net/wg-installer/wg-server/wginstaller.sh b/net/wg-installer/wg-server/wginstaller.sh new file mode 100644 index 000000000..7f787409c --- /dev/null +++ b/net/wg-installer/wg-server/wginstaller.sh @@ -0,0 +1,32 @@ +#!/bin/sh + +. /usr/share/libubox/jshn.sh +. /usr/share/wginstaller/wg_functions.sh + +case "$1" in +list) + cmd='{ "get_usage": {},' + cmd=$(echo $cmd ' "register": {"uplink_bw":"10", "mtu":"1400", "public_key": "xyz"} }') + echo $cmd + ;; +call) + case "$2" in + get_usage) + read input + logger -t "wginstaller" "call" "$2" "$input" + wg_get_usage + ;; + register) + read input + logger -t "wginstaller" "call" "$2" "$input" + + json_load "$input" + json_get_var uplink_bw uplink_bw + json_get_var mtu mtu + json_get_var public_key public_key + + wg_register $uplink_bw $mtu $public_key + ;; + esac + ;; +esac