From 163ccbd03f69634394c674bc3119dbedcf924063 Mon Sep 17 00:00:00 2001 From: deflax Date: Fri, 10 Nov 2023 02:03:18 +0200 Subject: [PATCH] Initial migration --- README.md | 35 +++ client-tools/startvpn.desktop | 8 + client-tools/wg-rapid | 400 ++++++++++++++++++++++++++++++++++ config.dist | 15 ++ gen-ip-database.sh | 44 ++++ init.sh | 257 ++++++++++++++++++++++ ldapsync.py | 171 +++++++++++++++ mail.md | 20 ++ peer_add.sh | 167 ++++++++++++++ peer_addall.sh | 12 + peer_del.sh | 62 ++++++ peer_disable.sh | 50 +++++ peer_mail.sh | 69 ++++++ wgldap.sh | 5 + wgldapsync.service | 7 + wgldapsync.timer | 9 + wgstats.sh | 38 ++++ 17 files changed, 1369 insertions(+) create mode 100644 README.md create mode 100755 client-tools/startvpn.desktop create mode 100755 client-tools/wg-rapid create mode 100644 config.dist create mode 100755 gen-ip-database.sh create mode 100755 init.sh create mode 100755 ldapsync.py create mode 100644 mail.md create mode 100755 peer_add.sh create mode 100755 peer_addall.sh create mode 100755 peer_del.sh create mode 100755 peer_disable.sh create mode 100755 peer_mail.sh create mode 100755 wgldap.sh create mode 100644 wgldapsync.service create mode 100644 wgldapsync.timer create mode 100755 wgstats.sh diff --git a/README.md b/README.md new file mode 100644 index 0000000..a73d976 --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ + \ \ /_) ___| | + \ \ \ / | __| _ \ | _` | __| _ \ + \ \ \ / | | __/ | | ( | | __/ + \_/\_/ _|_| \___|\____|\__,_|\__|\___| + + +Wireguard based VPN server endpoint with LDAP support + +Tested on Debian 12 bookworm + +# Server Commands + +./init.sh - setup system services (wireguard, unbound, iptables, sysctl) + +./peer_add.sh - define new peer for a new remote device. generates config and QR code inside /etc/wireguard/clients + +./peer_del.sh - delete a peer and salvage its ip address back to the ip pool + +./peer_addall.sh - recreates wireguard state using existing clients in /etc/wireguard/clients dir + +./peer_mail.sh - send the generated profile to the client and remove the sensitive data from server + +# Server Tools + +./gen-ip-database.sh - generate an ip pool for wireguard peers + +./wgstats.sh - show peer stats similar based on wg show all dump + +./wgldap.sh - tail the log of the wgldapsync service + +# Client Side Tools + +./client-tools/wg-rapid - modified wireguard client based on wg-quick that works with systemd-resolv + +./client-tools/startvpn.desktop - shortcut for wg-rapid. update the parameter with peer filename diff --git a/client-tools/startvpn.desktop b/client-tools/startvpn.desktop new file mode 100755 index 0000000..3c2fd88 --- /dev/null +++ b/client-tools/startvpn.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Name=startvpn +Exec=/usr/local/bin/wg-rapid profile +Comment= +Terminal=true +Icon=network-vpn +Type=Application +Name[en_US]=startvpn diff --git a/client-tools/wg-rapid b/client-tools/wg-rapid new file mode 100755 index 0000000..6a94f4e --- /dev/null +++ b/client-tools/wg-rapid @@ -0,0 +1,400 @@ +#!/bin/bash +# SPDX-License-Identifier: GPL-2.0 +# +# Copyright (C) 2015-2020 Jason A. Donenfeld . All Rights Reserved. +# Copyright (C) 2021-2022 Daniel afx +# + +#set -e -o pipefail +shopt -s extglob +export LC_ALL=C + +SELF="$(readlink -f "${BASH_SOURCE[0]}")" +export PATH="${SELF%/*}:$PATH" + +WG_CONFIG="" + +CONFIG_FILE="" +PROGRAM="${0##*/}" +ARGS=( "$@" ) + +EXTERNAL_NETWORK_TEST_IP="1.1.1.1" + +cmd_usage() { + cat >&2 <<-_EOF + Usage: $PROGRAM [ CONFIG_NAME ] + + CONFIG_NAME is the name of a configuration file, which is also the interface + name followed by \`.conf'. It should be a configuration found at + /etc/wireguard/INTERFACE.conf. It is to be readable by wg(8)'s \`setconf' + sub-command, with the exception of the following additions + to the [Interface] section, which are handled by $PROGRAM: + + - Address: may be specified one or more times and contains one or more + IP addresses (with an optional CIDR mask) to be set for the interface. + - DNS: an optional DNS server to use while the device is up. + - MTU: an optional MTU for the interface; if unspecified, auto-calculated. + - Table: an optional routing table to which routes will be added; if + unspecified or \`auto', the default table is used. If \`off', no routes + are added. + - PreUp, PostUp, PreDown, PostDown: script snippets which will be executed + by bash(1) at the corresponding phases of the link, most commonly used + to configure DNS. The string \`%i' is expanded to INTERFACE. + + If for some reason the interface is already up, you could use: + $PROGRAM [ CONFIG_NAME ] down + _EOF +} + +# Helper Functions +auto_su() { + [[ $UID == 0 ]] || exec sudo -p "$PROGRAM must be run as root. Please enter the password for %u to continue: " -- "$BASH" -- "$SELF" "${ARGS[@]}" +} + +say() { + echo " ] $*" +} + +die() { + echo "!] $*" >&2 + exit 1 +} + +cmd() { + echo "#] $*" >&2 + "$@" +} + +execute_hooks() { + local hook + for hook in "$@"; do + hook="${hook//%i/$INTERFACE}" + echo "#] $hook" >&2 + (eval "$hook") + done +} + +parse_options() { + INTERFACE="" + ADDRESSES=( ) + MTU="" + DNS=( ) + DNS_SEARCH=( ) + TABLE="" + PRE_UP=( ) + POST_UP=( ) + PRE_DOWN=( ) + POST_DOWN=( ) + local interface_section=0 line key value stripped v + CONFIG_FILE="$1" + [[ $CONFIG_FILE =~ ^[a-zA-Z0-9_=+.-]{1,15}$ ]] && CONFIG_FILE="/etc/wireguard/$CONFIG_FILE.conf" + [[ -e $CONFIG_FILE ]] || die "\`$CONFIG_FILE' does not exist" + [[ $CONFIG_FILE =~ (^|/)([a-zA-Z0-9_=+.-]{1,15})\.conf$ ]] || die "The config file must be a valid interface name, followed by .conf" + CONFIG_FILE="$(readlink -f "$CONFIG_FILE")" + ((($(stat -c '0%#a' "$CONFIG_FILE") & $(stat -c '0%#a' "${CONFIG_FILE%/*}") & 0007) == 0)) || echo "Warning: \`$CONFIG_FILE' is world accessible" >&2 + INTERFACE="${BASH_REMATCH[2]}" + shopt -s nocasematch + while read -r line || [[ -n $line ]]; do + stripped="${line%%\#*}" + key="${stripped%%=*}"; key="${key##*([[:space:]])}"; key="${key%%*([[:space:]])}" + value="${stripped#*=}"; value="${value##*([[:space:]])}"; value="${value%%*([[:space:]])}" + [[ $key == "["* ]] && interface_section=0 + [[ $key == "[Interface]" ]] && interface_section=1 + if [[ $interface_section -eq 1 ]]; then + case "$key" in + Address) ADDRESSES+=( ${value//,/ } ); continue ;; + MTU) MTU="$value"; continue ;; + DNS) for v in ${value//,/ }; do + [[ $v =~ (^[0-9.]+$)|(^.*:.*$) ]] && DNS+=( $v ) || DNS_SEARCH+=( $v ) + done; continue ;; + Table) TABLE="$value"; continue ;; + PreUp) PRE_UP+=( "$value" ); continue ;; + PreDown) PRE_DOWN+=( "$value" ); continue ;; + PostUp) POST_UP+=( "$value" ); continue ;; + PostDown) POST_DOWN+=( "$value" ); continue ;; + esac + fi + WG_CONFIG+="$line"$'\n' + done < "$CONFIG_FILE" + shopt -u nocasematch +} + +set_config() { + cmd wg setconf "$INTERFACE" <(echo "$WG_CONFIG") +} + +# Setup Interface and Address +add_if() { + local ret + if ! cmd ip link add "$INTERFACE" type wireguard; then + ret=$? + [[ -e /sys/module/wireguard ]] || ! command -v "${WG_QUICK_USERSPACE_IMPLEMENTATION:-wireguard-go}" >/dev/null && exit $ret + echo "!] Missing WireGuard kernel module. Falling back to slow userspace implementation." >&2 + cmd "${WG_QUICK_USERSPACE_IMPLEMENTATION:-wireguard-go}" "$INTERFACE" + fi +} + +del_if() { + local table + #[[ $HAVE_SET_DNS -eq 0 ]] || unset_dns + #[[ $HAVE_SET_FIREWALL -eq 0 ]] || remove_firewall + if [[ -z $TABLE || $TABLE == auto ]] && get_fwmark table && [[ $(wg show "$INTERFACE" allowed-ips) =~ /0(\ |$'\n'|$) ]]; then + while [[ $(ip -4 rule show 2>/dev/null) == *"lookup $table"* ]]; do + cmd ip -4 rule delete table $table + done + while [[ $(ip -4 rule show 2>/dev/null) == *"from all lookup main suppress_prefixlength 0"* ]]; do + cmd ip -4 rule delete table main suppress_prefixlength 0 + done + while [[ $(ip -6 rule show 2>/dev/null) == *"lookup $table"* ]]; do + cmd ip -6 rule delete table $table + done + while [[ $(ip -6 rule show 2>/dev/null) == *"from all lookup main suppress_prefixlength 0"* ]]; do + cmd ip -6 rule delete table main suppress_prefixlength 0 + done + fi + cmd ip link delete dev "$INTERFACE" +} + +add_addr() { + local proto=-4 + [[ $1 == *:* ]] && proto=-6 + cmd ip $proto address add "$1" dev "$INTERFACE" +} + +set_mtu_up() { + local mtu=0 endpoint output + if [[ -n $MTU ]]; then + cmd ip link set mtu "$MTU" up dev "$INTERFACE" + return + fi + while read -r _ endpoint; do + [[ $endpoint =~ ^\[?([a-z0-9:.]+)\]?:[0-9]+$ ]] || continue + output="$(ip route get "${BASH_REMATCH[1]}" || true)" + [[ ( $output =~ mtu\ ([0-9]+) || ( $output =~ dev\ ([^ ]+) && $(ip link show dev "${BASH_REMATCH[1]}") =~ mtu\ ([0-9]+) ) ) && ${BASH_REMATCH[1]} -gt $mtu ]] && mtu="${BASH_REMATCH[1]}" + done < <(wg show "$INTERFACE" endpoints) + if [[ $mtu -eq 0 ]]; then + read -r output < <(ip route show default || true) || true + [[ ( $output =~ mtu\ ([0-9]+) || ( $output =~ dev\ ([^ ]+) && $(ip link show dev "${BASH_REMATCH[1]}") =~ mtu\ ([0-9]+) ) ) && ${BASH_REMATCH[1]} -gt $mtu ]] && mtu="${BASH_REMATCH[1]}" + fi + [[ $mtu -gt 0 ]] || mtu=1500 + cmd ip link set mtu $(( mtu - 80 )) up dev "$INTERFACE" +} + +# Checks the active internet connection interface +transport_interface() { + local netiface + netiface=$(ip route get ${EXTERNAL_NETWORK_TEST_IP} | grep -Po '(?<=dev\s)\w+' | cut -f1 -d ' ') + if [ -z ${netiface} ]; then + die "Unable to reach ${EXTERNAL_NETWORK_TEST_IP}. Check the Internet connection" + exit 1 + else + echo "${netiface}" +fi +} + +# Setup DNS +HAVE_SET_DNS=0 +set_dns() { + [[ ${#DNS[@]} -gt 0 ]] || return 0 + #{ printf 'nameserver %s\n' "${DNS[@]}" + # [[ ${#DNS_SEARCH[@]} -eq 0 ]] || printf 'search %s\n' "${DNS_SEARCH[*]}" + #} | cmd resolvconf -a "$(resolvconf_iface_prefix)$INTERFACE" -m 0 -x + cmd resolvectl dns ${INTERFACE} "${DNS[@]}" + cmd resolvectl domain ${INTERFACE} "~." + cmd resolvectl domain $(transport_interface) "lan" + HAVE_SET_DNS=1 +} + +unset_dns() { + [[ ${#DNS[@]} -gt 0 ]] || return 0 + cmd resolvectl domain $(transport_interface) "lan" + cmd resolvectl domain $(transport_interface) "~." + +} + +# Setup Routes and Firewall +add_route() { + local proto=-4 + [[ $1 == *:* ]] && proto=-6 + [[ $TABLE != off ]] || return 0 + + if [[ -n $TABLE && $TABLE != auto ]]; then + cmd ip $proto route add "$1" dev "$INTERFACE" table "$TABLE" + elif [[ $1 == */0 ]]; then + add_default "$1" + else + [[ -n $(ip $proto route show dev "$INTERFACE" match "$1" 2>/dev/null) ]] || cmd ip $proto route add "$1" dev "$INTERFACE" + fi +} + +get_fwmark() { + local fwmark + fwmark="$(wg show "$INTERFACE" fwmark)" || return 1 + [[ -n $fwmark && $fwmark != off ]] || return 1 + printf -v "$1" "%d" "$fwmark" + return 0 +} + +HAVE_SET_FIREWALL=0 +add_default() { + local table line + if ! get_fwmark table; then + table=51820 + while [[ -n $(ip -4 route show table $table 2>/dev/null) || -n $(ip -6 route show table $table 2>/dev/null) ]]; do + ((table++)) + done + cmd wg set "$INTERFACE" fwmark $table + fi + local proto=-4 iptables=iptables pf=ip + [[ $1 == *:* ]] && proto=-6 iptables=ip6tables pf=ip6 + cmd ip $proto route add "$1" dev "$INTERFACE" table $table + cmd ip $proto rule add not fwmark $table table $table + cmd ip $proto rule add table main suppress_prefixlength 0 + + local marker="-m comment --comment \"wg-quick(8) rule for $INTERFACE\"" restore=$'*raw\n' nftable="wg-quick-$INTERFACE" nftcmd + printf -v nftcmd '%sadd table %s %s\n' "$nftcmd" "$pf" "$nftable" + printf -v nftcmd '%sadd chain %s %s preraw { type filter hook prerouting priority -300; }\n' "$nftcmd" "$pf" "$nftable" + printf -v nftcmd '%sadd chain %s %s premangle { type filter hook prerouting priority -150; }\n' "$nftcmd" "$pf" "$nftable" + printf -v nftcmd '%sadd chain %s %s postmangle { type filter hook postrouting priority -150; }\n' "$nftcmd" "$pf" "$nftable" + while read -r line; do + [[ $line =~ .*inet6?\ ([0-9a-f:.]+)/[0-9]+.* ]] || continue + printf -v restore '%s-I PREROUTING ! -i %s -d %s -m addrtype ! --src-type LOCAL -j DROP %s\n' "$restore" "$INTERFACE" "${BASH_REMATCH[1]}" "$marker" + printf -v nftcmd '%sadd rule %s %s preraw iifname != "%s" %s daddr %s fib saddr type != local drop\n' "$nftcmd" "$pf" "$nftable" "$INTERFACE" "$pf" "${BASH_REMATCH[1]}" + done < <(ip -o $proto addr show dev "$INTERFACE" 2>/dev/null) + printf -v restore '%sCOMMIT\n*mangle\n-I POSTROUTING -m mark --mark %d -p udp -j CONNMARK --save-mark %s\n-I PREROUTING -p udp -j CONNMARK --restore-mark %s\nCOMMIT\n' "$restore" $table "$marker" "$marker" + printf -v nftcmd '%sadd rule %s %s postmangle meta l4proto udp mark %d ct mark set mark \n' "$nftcmd" "$pf" "$nftable" $table + printf -v nftcmd '%sadd rule %s %s premangle meta l4proto udp meta mark set ct mark \n' "$nftcmd" "$pf" "$nftable" + [[ $proto == -4 ]] && cmd sysctl -q net.ipv4.conf.all.src_valid_mark=1 + if type -p nft >/dev/null; then + cmd nft -f <(echo -n "$nftcmd") + else + echo -n "$restore" | cmd $iptables-restore -n + fi + HAVE_SET_FIREWALL=1 + return 0 +} + +remove_firewall() { + if type -p nft >/dev/null; then + local table nftcmd + while read -r table; do + [[ $table == *" wg-quick-$INTERFACE" ]] && printf -v nftcmd '%sdelete %s\n' "$nftcmd" "$table" + done < <(nft list tables 2>/dev/null) + [[ -z $nftcmd ]] || cmd nft -f <(echo -n "$nftcmd") + fi + if type -p iptables >/dev/null; then + local line iptables found restore + for iptables in iptables ip6tables; do + restore="" found=0 + while read -r line; do + [[ $line == "*"* || $line == COMMIT || $line == "-A "*"-m comment --comment \"wg-quick(8) rule for $INTERFACE\""* ]] || continue + [[ $line == "-A"* ]] && found=1 + printf -v restore '%s%s\n' "$restore" "${line/#-A/-D}" + done < <($iptables-save 2>/dev/null) + [[ $found -ne 1 ]] || echo -n "$restore" | cmd $iptables-restore -n + done + fi +} + +# Up/Down Functions +cmd_up() { + local i + #[[ -z $(ip link show dev "$INTERFACE" 2>/dev/null) ]] || die "\`$INTERFACE' already exists" + [[ -z $(ip link show dev "$INTERFACE" 2>/dev/null) ]] || cmd_down + trap 'del_if; exit' INT TERM EXIT + say "Starting UP the ${INTERFACE} interface ..." + execute_hooks "${PRE_UP[@]}" + add_if + set_config + for i in "${ADDRESSES[@]}"; do + add_addr "$i" + done + set_mtu_up + set_dns + for i in $(while read -r _ i; do for i in $i; do [[ $i =~ ^[0-9a-z:.]+/[0-9]+$ ]] && echo "$i"; done; done < <(wg show "$INTERFACE" allowed-ips) | sort -nr -k 2 -t /); do + add_route "$i" + done + execute_hooks "${POST_UP[@]}" + trap - INT TERM EXIT + sleep 3 +} + +cmd_down() { + say "Bringing DOWN the ${INTERFACE} interface ..." + [[ " $(wg show interfaces) " == *" $INTERFACE "* ]] || die "$INTERFACE is not a WireGuard interface" + execute_hooks "${PRE_DOWN[@]}" + del_if + [[ $HAVE_SET_DNS -eq 0 ]] || unset_dns + [[ $HAVE_SET_FIREWALL -eq 0 ]] || remove_firewall + #unset_dns || true + #remove_firewall || true + execute_hooks "${POST_DOWN[@]}" + sleep 1 +} + +# Main +if [[ $# -eq 0 ]]; then + cmd_usage + exit 1 +elif [[ $# -eq 1 ]]; then + auto_su + parse_options "$1" + cmd_up + while true; do + clear + say "wg-rapid by afx. ver.2204" + echo " " + say "[q] to stop the VPN connection." + say "[d] or close the terminal window to keep the VPN connection setup configured" + say "[o] forward all networking through the VPN tunnel" + say "[p] forward the predefined routes only through the VPN tunnel" + echo " " + wg show + echo " " + read -t 1 -N 1 input + if [[ $input = "q" ]] || [[ $input = "Q" ]]; then + echo + break + fi + if [[ $input = "d" ]] || [[ $input = "D" ]]; then + echo + say "$PROGRAM detached." + exit 0 + fi + if [[ $input = "p" ]] || [[ $input = "P" ]]; then + cmd_down + LINE_ROUTE_LOCAL=`grep -n 'Route only vpn trafic through vpn' /etc/wireguard/$1.conf | cut -d ':' -f 1` + ((LINE_ROUTE_LOCAL=LINE_ROUTE_LOCAL+1)) + LINE_ROUTE_ALL=`grep -n 'Route ALL traffic through vpn' /etc/wireguard/$1.conf | cut -d ':' -f 1` + ((LINE_ROUTE_ALL=LINE_ROUTE_ALL+1)) + sed -i "${LINE_ROUTE_LOCAL} s/^##*//" /etc/wireguard/$1.conf + sed -i "${LINE_ROUTE_ALL} s/^/#/" /etc/wireguard/$1.conf + parse_options "$1" + cmd_up + fi + if [[ $input = "o" ]] || [[ $input = "O" ]]; then + cmd_down + sleep 1 + LINE_ROUTE_LOCAL=`grep -n 'Route only vpn trafic through vpn' /etc/wireguard/$1.conf | cut -d ':' -f 1` + ((LINE_ROUTE_LOCAL=LINE_ROUTE_LOCAL+1)) + LINE_ROUTE_ALL=`grep -n 'Route ALL traffic through vpn' /etc/wireguard/$1.conf | cut -d ':' -f 1` + ((LINE_ROUTE_ALL=LINE_ROUTE_ALL+1)) + sed -i "${LINE_ROUTE_LOCAL} s/^/#/" /etc/wireguard/$1.conf + sed -i "${LINE_ROUTE_ALL} s/^##*//" /etc/wireguard/$1.conf + parse_options "$1" + cmd_up + fi + done + cmd_down +elif [[ $# -eq 2 ]]; then + auto_su + parse_options "$1" + if [ "$2" == "down" ]; then + cmd_down + exit 0 + else + say "$PROGRAM [ CONFIG_NAME ] [ down ]" + fi +fi + +exit 0 diff --git a/config.dist b/config.dist new file mode 100644 index 0000000..d89317e --- /dev/null +++ b/config.dist @@ -0,0 +1,15 @@ +public_ifname=ens3 +ip_db=/etc/wireguard/ipdb.pool +server_endpoint_address=wire.example.com +net_prefix=69 +allowed_routes="10.15.0.0/16, 192.168.0.0/24" +monitor_host=10.15.0.15 +email_origin=wire.example.com +email_host=email-smtp.eu-west-1.amazonaws.com +email_user=AUSER +email_pass=APASS +email_destination=admin@domain +ldap_server=ldap://idm.example.com +ldap_login=cn=admin +ldap_password=PASS +ldap_basedn=dc=admin,dc=com diff --git a/gen-ip-database.sh b/gen-ip-database.sh new file mode 100755 index 0000000..f26461d --- /dev/null +++ b/gen-ip-database.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash + +source config + +check_root() { + if [ "$EUID" -ne 0 ]; then + printf %b\\n "] Please run the script as root." + exit 1 + fi +} + +gen_cidr() { + base=${1%/*} + masksize=${1#*/} + + [ $masksize -lt 8 ] && { echo "] Max range is /8."; exit 1;} + + mask=$(( 0xFFFFFFFF << (32 - $masksize) )) + + IFS=. read a b c d <<< $base + + ip=$(( ($b << 16) + ($c << 8) + $d )) + + ipstart=$(( $ip & $mask )) + ipend=$(( ($ipstart | ~$mask ) & 0x7FFFFFFF )) + + seq $ipstart $ipend | while read i; do + echo $a.$(( ($i & 0xFF0000) >> 16 )).$(( ($i & 0xFF00) >> 8 )).$(( $i & 0x00FF )) + done +} + +check_root + +# This generates all host address for our vpn subnet but skips the following: +# - vpn server address, which ends with .0.1 +# - the network address and the broadcast address +# - all host addresses in between that looks like /24 net and mask, they should work but they are +# misleading in that regard, and we do have enough addresses already +if [ -f $ip_db ]; then + echo "] IP pool exists at $ip_db. Remove it first." + exit 1 +else + gen_cidr 10.${net_prefix}.0.0/20 | grep -v \\.0$ | grep -v .255$ | grep -v \\.0\\.1$ > $ip_db +fi diff --git a/init.sh b/init.sh new file mode 100755 index 0000000..f2a77d7 --- /dev/null +++ b/init.sh @@ -0,0 +1,257 @@ +#!/usr/bin/env bash + +source config + +check_root() { + if [ "$EUID" -ne 0 ]; then + printf %b\\n "] Please run the script as root." + exit 1 + fi +} + +# Welcome +echo "" +cat README.md +echo "" + +check_root + +# enable IPv4 forwarding +sed -i 's/\#net.ipv4.ip_forward=1/net.ipv4.ip_forward=1/g' /etc/sysctl.conf + +# negate the need to reboot after the above change +sysctl -p + +# update/upgrade server and refresh repo +apt update -y && apt upgrade -y && apt autoremove -y + +# remove the default firewall +ufw disable +apt remove --purge ufw -y +apt install iptables netfilter-persistent -y + +# install fail2ban +apt install fail2ban -y + +# install python-ldap +apt install python3-dev python3-pip python3-ldap -y + +# install wireguard +systemctl stop wg-quick@wg0.service +systemctl disable wg-quick@wg0.service +apt install wireguard -y +apt install qrencode -y + +# install jq +apt install jq -y + +# install curl +apt install curl -y + +# create Wireguard interface config +bash -c "cat > /etc/wireguard/wg0.conf" << ENDOFFILE +[Interface] +PrivateKey = server_private_key +Address = 10.net_prefix.0.1/20 +ListenPort = 550net_prefix + +PostUp = iptables -A FORWARD -i ${public_ifname} -o wg0 -j ACCEPT; iptables -A FORWARD -i wg0 -o ${public_ifname} -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT; iptables -t nat -A POSTROUTING -o ${public_ifname} -j MASQUERADE +PostDown = iptables -D FORWARD -i ${public_ifname} -o wg0 -j ACCEPT; iptables -D FORWARD -i wg0 -o ${public_ifname} -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT; iptables -t nat -D POSTROUTING -o ${public_ifname} -j MASQUERADE +SaveConfig = true +ENDOFFILE + +cat << EOF | bash +cd /etc/wireguard/ +umask 077 +[ ! -f server_private.key ] && wg genkey | tee server_private.key | wg pubkey > server_public.key +EOF +sed -i "s/net_prefix/${net_prefix}/g" /etc/wireguard/wg0.conf +sed -i "s/server_private_key/$(sed 's:/:\\/:g' /etc/wireguard/server_private.key)/" /etc/wireguard/wg0.conf + +# make root owner of the Wireguard config file +chown -v root:root /etc/wireguard/wg0.conf +chmod -v 600 /etc/wireguard/wg0.conf + +# make Wireguard interface start at boot +systemctl enable wg-quick@wg0.service + + +# flush all chains +iptables -P INPUT ACCEPT +iptables -P FORWARD ACCEPT +iptables -P OUTPUT ACCEPT +iptables -t nat -F +iptables -t mangle -F +iptables -F +# delete all chains +iptables -X + +# configure the firewall and make it persistent +DEBIAN_FRONTEND=noninteractive apt install iptables-persistent -y +systemctl enable netfilter-persistent +iptables -P INPUT DROP +iptables -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT +iptables -A INPUT -p all -s localhost -j ACCEPT +iptables -A INPUT -p tcp --dport 22 -j ACCEPT +iptables -A INPUT -p tcp --dport 655 -j ACCEPT +iptables -A INPUT -p udp --dport 655 -j ACCEPT +iptables -A INPUT -p tcp -s ${monitor_host} --dport 10050 -j ACCEPT +iptables -A INPUT -p udp --dport 550${net_prefix} -j ACCEPT +iptables -A INPUT -p all -i wg0 -j ACCEPT +iptables -P FORWARD ACCEPT +iptables -A FORWARD -i wg0 -o wg0 -j REJECT +iptables -P OUTPUT ACCEPT +netfilter-persistent save + +# install Unbound DNS +systemctl stop unbound.service +systemctl disable unbound.service +apt install unbound unbound-host -y + +# download list of DNS root servers +curl -o /var/lib/unbound/root.hints https://www.internic.net/domain/named.cache + +# create unbound log file +mkdir -p /var/log/unbound +chown unbound:unbound /var/log/unbound/ +touch /var/log/unbound/unbound.log +chown unbound:unbound /var/log/unbound/unbound.log + +echo "/var/log/unbound/unbound.log rw," > /etc/apparmor.d/local/usr.sbin.unbound +apparmor_parser -r /etc/apparmor.d/usr.sbin.unbound + +# create custom conf +touch /etc/unbound/custom.conf +chown unbound:unbound /etc/unbound/custom.conf + +# create Unbound config file +bash -c "cat > /etc/unbound/unbound.conf" << ENDOFFILE +server: + num-threads: 4 + + # enable logs + verbosity: 1 + logfile: /var/log/unbound/unbound.log + chroot: "" + log-queries: yes + + # list of root DNS servers + root-hints: "/var/lib/unbound/root.hints" + + # use the root server's key for DNSSEC + auto-trust-anchor-file: "/var/lib/unbound/root.key" + + # respond to DNS requests on all interfaces + interface: 0.0.0.0 + max-udp-size: 3072 + + # IPs authorised to access the DNS Server + access-control: 0.0.0.0/0 refuse + access-control: 127.0.0.1 allow + access-control: 10.net_prefix.0.0/20 allow + + # not allowed to be returned for public Internet names + private-address: 10.net_prefix.0.0/20 + + #hide DNS Server info + hide-identity: yes + hide-version: yes + + # limit DNS fraud and use DNSSEC + harden-glue: yes + harden-dnssec-stripped: yes + harden-referral-path: yes + + # add an unwanted reply threshold to clean the cache and avoid, when possible, DNS poisoning + unwanted-reply-threshold: 10000000 + + # have the validator print validation failures to the log + val-log-level: 1 + + # minimum lifetime of cache entries in seconds + cache-min-ttl: 1800 + + # maximum lifetime of cached entries in seconds + cache-max-ttl: 14400 + prefetch: yes + prefetch-key: yes + + # additional entries + include: /etc/unbound/custom.conf +ENDOFFILE + +sed -i "s/net_prefix/${net_prefix}/g" /etc/unbound/unbound.conf + +# give root ownership of the Unbound config +chown -R unbound:unbound /var/lib/unbound + +# enable Unbound in place of systemd-resovled +systemctl enable unbound-resolvconf +systemctl enable unbound +systemctl start unbound + +# disable systemd-resolved +systemctl stop systemd-resolved +systemctl disable systemd-resolved +unlink /etc/resolv.conf +bash -c "cat > /etc/resolv.conf" << ENDOFFILE +nameserver 127.0.0.1 +ENDOFFILE + +# Initial database generation +bash -c "./gen-ip-database.sh" + +#provide scripts in /usr/local/bin +__dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cp -v ${__dir}/wgstats.sh /usr/local/bin/ +cp -v ${__dir}/wgldap.sh /usr/local/bin/ + +#install Postfix mailserver +if [ $email_origin == "wire.example.com" ]; then + echo "] WARN: Mailing is disabled!" +else + echo "] Setting up mail server $email_origin ..." + if [ ! -f /etc/postfix/main.cf ]; then + echo "] Mail server config does not exist. Installing..." + + # install postfix + echo "postfix postfix/mailname string ${email_origin}" | debconf-set-selections + echo "postfix postfix/main_mailer_type string 'Internet Site'" | debconf-set-selections + apt install -y postfix mailutils libsasl2-2 ca-certificates libsasl2-modules mutt zip + + # setup mail server for email reports + /usr/sbin/postconf -e "relayhost = [${email_host}]:587" \ + "smtp_sasl_auth_enable = yes" \ + "smtp_sasl_security_options = noanonymous" \ + "smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd" \ + "smtp_use_tls = yes" \ + "smtp_tls_security_level = encrypt" \ + "smtp_tls_note_starttls_offer = yes" + echo "[${email_host}]:587 ${email_user}:${email_pass}" > /etc/postfix/sasl_passwd + /usr/sbin/postmap hash:/etc/postfix/sasl_passwd + chown root:root /etc/postfix/sasl_passwd /etc/postfix/sasl_passwd.db + chmod 0600 /etc/postfix/sasl_passwd /etc/postfix/sasl_passwd.db + /usr/sbin/postconf -e "smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt" + /usr/sbin/postconf -e "myorigin = ${email_origin}" + sleep 2 + service postfix restart + fi +fi + +# Setup LDAP sync service +if [ $ldap_server == "ldap://idm.example.com" ]; then + echo "] WARN: LDAP disabled!" +else + echo "] Setting up LDAP server $ldap_server" + cp -v ${__dir}/wgldapsync.service /etc/systemd/system/wgldapsync.service + cp -v ${__dir}/wgldapsync.timer /etc/systemd/system/wgldapsync.timer + systemctl daemon-reload + systemctl enable wgldapsync.timer + systemctl status wgldapsync.service + systemctl status wgldapsync.timer +fi + +# reboot to make changes effective +echo "] System reboot after 30 seconds..." +sleep 30 +reboot diff --git a/ldapsync.py b/ldapsync.py new file mode 100755 index 0000000..c394808 --- /dev/null +++ b/ldapsync.py @@ -0,0 +1,171 @@ +#!/usr/bin/python3 + +import os +import sys +import configparser +import pprint +import socket +import ldap +import subprocess + +DEBUG = False +DEBUG_LDAP = False +CONFIG_PATH = "/root/wiregate/" +LOCAL_DB_PATH = '/etc/wireguard/clients/' + +# parse config +cds = 'wiregate' #ConfigParser dummy section +with open(CONFIG_PATH + '/config', 'r') as f: + config_string = '[' + cds + ']\n' + f.read() + config = configparser.ConfigParser() + config.read_string(config_string) + +baseDN = config.get(cds, 'ldap_basedn') +LDAP_SERVER = config.get(cds, 'ldap_server') +LDAP_LOGIN = config.get(cds, 'ldap_login') + ',' + baseDN +LDAP_PASSWORD = config.get(cds, 'ldap_password') + +serverhost = socket.gethostname() +searchScope = ldap.SCOPE_SUBTREE + +def LdapQuery(searchFilter, searchAttrs): + ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER) + + if DEBUG_LDAP: + print("Connecting to " + LDAP_SERVER) + l = ldap.initialize(LDAP_SERVER) + try: + #l.set_option(ldap.OPT_REFERRALS, 0) + #l.set_option(ldap.OPT_PROTOCOL_VERSION, 3) + #l.set_option(ldap.OPT_X_TLS, ldap.OPT_X_TLS_DEMAND) + #l.set_option(ldap.OPT_X_TLS_DEMAND, True) + #l.set_option(ldap.OPT_DEBUG_LEVEL, 255) + l.set_option(ldap.OPT_NETWORK_TIMEOUT, 10.0) + l.simple_bind_s(LDAP_LOGIN, LDAP_PASSWORD) + + query = {} + ldap_result_id = l.search(baseDN, searchScope, searchFilter, searchAttrs) + while 1: + rType, rData = l.result(ldap_result_id, 0) + if (rData == []): + break + else: + if rType == ldap.RES_SEARCH_ENTRY: + cn = rData[0][0] + data = rData[0][1] + + #Flatten, just for more easy access + for (k, v) in data.items(): + if len(v) == 1: + data[k] = v[0] + + #uid = data["uid"] + query[cn] = data + return query + + except ldap.LDAPError as e: + print(e) + sys.exit(2) + + finally: + if DEBUG_LDAP: + print('Server unbind.') + l.unbind_s() + return 0 + +def main(): + print("] WireGate LDAP sync") + + # query ldap server and gather list of REMOTE peers which belong to cn=,ou=Groups,baseDN + ldapGroups = LdapQuery('(|(&(objectClass=groupOfUniqueNames)(cn=' + serverhost + ')))', ['uniqueMember']) + + if ldapGroups == {}: + print('] Group ' + serverhost + ' not found') + sys.exit(1) + + if ldapGroups != 0: + members = ldapGroups['cn=' + serverhost + ',ou=Groups,' + baseDN]['uniqueMember'] + if not isinstance(members, list): + members = [members] + print('] Group: ' + serverhost) + print('] Remote members: ' + str(len(members))) + + remote_peers = [] + for member in members: + d_member = member.decode('ascii') + if DEBUG: + print('] Processing ' + str(d_member)) + + searchFilterUser='(' + d_member.split(',')[0] + ')' + ldapUser = LdapQuery(searchFilterUser, ['mail', 'l']) + user = ldapUser[d_member] + #get attributes + #mail + m_mail = str(user['mail'].decode('ascii')) + m_domain = m_mail.split('@')[1] + m_user = m_mail.split('@')[0] + + m_peername = m_domain + '-' + m_user + + #get additional peers provided with the l attribute + if 'l' in user: + labels = user['l'] + if not isinstance(labels, list): + labels = [labels] + for label in labels: + peer = m_peername + '-' + str(label.decode('ascii')) + remote_peers.append({ 'mail': m_mail, 'peer': peer }) + + #get the parent peer + peer = m_peername + remote_peers.append({ 'mail': m_mail, 'peer': peer }) + + #searchFilterUserSubs='(|(&(objectClass=*)(member=uid=%s,cn=users,ou=Groups,' + baseDN + ')))' + user_subscriptions= LdapQuery('(|(&(objectClass=groupOfUniqueNames)(uniqueMember=' + str(d_member) + ')))', ['cn']) + if DEBUG: + pp = pprint.PrettyPrinter(depth=3) + pp.pprint(user_subscriptions) + + print("] Remote peers: " + str(len(remote_peers))) + if DEBUG: + pp = pprint.PrettyPrinter(depth=3) + pp.pprint(remote_peers) + + # query LOCAL peers database + local_peers = [] + for file in os.listdir(LOCAL_DB_PATH): + if file.endswith(".info"): + try: + cds = 'localdata' #ConfigParser dummy section + with open(LOCAL_DB_PATH + '/' + file, 'r') as f: + data_string = '[' + cds + ']\n' + f.read() + local_peer_data = configparser.ConfigParser() + local_peer_data.read_string(data_string) + local_mail = local_peer_data.get(cds, 'email') + local_peer = local_peer_data.get(cds, 'peer') + local_peers.append({ 'mail': local_mail, 'peer': local_peer }) + + except Exception as e: + print(e) + sys.exit(2) + + print("] Local peers: " + str(len(local_peers))) + if DEBUG: + pp = pprint.PrettyPrinter(depth=3) + pp.pprint(local_peers) + + # add / enable REMOTE peers if they DO NOT exist in the local database + for r_peer in remote_peers: + #print('add peer ' + r_peer['peer'] + ' - ' + r_peer['mail']) + process = subprocess.Popen([CONFIG_PATH + "peer_add.sh", "-p", r_peer['peer'], "-e", r_peer['mail']]) + process.wait() + + # disable (do not remove) LOCAL peers which DO NOT exist in the remote database + for l_peer in local_peers: + if l_peer not in remote_peers: + #print('rem peer ' + l_peer['peer'] + ' - ' + l_peer['mail']) + process = subprocess.Popen([CONFIG_PATH + "peer_disable.sh", "-p", l_peer['peer']]) + process.wait() + +if __name__ == "__main__": + sys.exit(main()) diff --git a/mail.md b/mail.md new file mode 100644 index 0000000..3585a63 --- /dev/null +++ b/mail.md @@ -0,0 +1,20 @@ +Hello, + +To be able to access the resources on our development infrastructure, you will need to set up a VPN profile. + +To install the necessary client application, please visit the following page to obtain packages for the operating system your device is currently using: + +https://www.wireguard.com/install/ + +Please take care of the configuration files and QR images as equivalent to a secure password. + +For Windows, MacOS, Android and iOS simply import profile.conf or profile_alltraffic.conf file using the import tunnel function of the Wireguard software. + +You may choose to import both versions and switch between them, where the `alltraffic` version pushes all network routes through the endpoint, which is useful for example if a third party system requires a whitelisted company address or to switch geographic policy. The tradeoff is the limited bandwidth and delay of the endpoint for every network request, which could affect some resource intensive applications. + +The connected device follows the remote network policy for the routed traffic and DNS requests. + +For Linux you may use the wg-rapid script `sudo cp linux/wg-rapid /usr/local/bin`. Then copy either profile.conf file or the profile_alltraffic.conf version of it as /etc/wireguard/office.conf and start with `wg-rapid office` Please not that the linux script has built-in capability to switch between preselected routes, so it doesn't matter which one of them you choose to copy in this case. + +Using a VPN client is somewhat platform and infrastructure specific, you may always contact #team-sysops for further assistance. + diff --git a/peer_add.sh b/peer_add.sh new file mode 100755 index 0000000..06ade75 --- /dev/null +++ b/peer_add.sh @@ -0,0 +1,167 @@ +#!/usr/bin/env bash + +source config + +check_root() { + if [ "$EUID" -ne 0 ]; then + printf %b\\n "] Please run the script as root." + exit 1 + fi +} + +usage() +{ + echo "Usage: peer_add.sh -p -e " +} + +random_ip_pickup() { + # Randomly select an IP and remove it from the pool + local ipsleft=`cat ${ip_db} | wc -l` + if [[ "${ipsleft}" -eq 0 ]]; then + echo "empty" + else + local random_ip=$(shuf -n 1 ${ip_db}) + grep -v "${random_ip}$" ${ip_db} > ${ip_db}.tmp + mv ${ip_db}.tmp ${ip_db} + echo "${random_ip}" + fi +} + +check_root + +no_args="true" +while getopts p:e: option +do + case $option in + (p) + name=${OPTARG};; + (e) + email=${OPTARG};; + (*) + usage + exit;; + esac + no_args="false" +done + +[[ "$no_args" == "true" ]] && { usage; exit 1; } + +# Check if IP pool exist +if [ ! -f ${ip_db} ]; then + echo "] IP pool does not exists at ${ip_db}. Generate it first." + exit 3 +fi + +# Check if both arguments exist +if [ -z ${name} ] || [ -z ${email} ]; then + echo "] Not enough arguments" + exit 1 +fi + +# Check if peer config exist +if [ -f /etc/wireguard/clients/${name}_public.key ]; then + peer_exists_in_wg=$(wg show wg0 dump | grep $(cat /etc/wireguard/clients/${name}_public.key) | wc -l) + if [ ! ${peer_exists_in_wg} -eq 0 ]; then + #echo "] ${name} already activated" + exit 2 + fi +fi + +# Generate wireguard peer keys and config +if [ -z ${server_endpoint_address} ]; then + server_endpoint_address=$(ip addr show ${public_ifname} | grep -o "inet [0-9]*\.[0-9]*\.[0-9]*\.[0-9]*" | grep -o "[0-9]*\.[0-9]*\.[0-9]*\.[0-9]*") +fi + +# Creating clients subdir +mkdir -p /etc/wireguard/clients + +if [ ! -f /etc/wireguard/clients/${name}.info ]; then + echo "] ${name} config will be generated." + peer_address_from_pool=$(random_ip_pickup) + if [ ${peer_address_from_pool} = "empty" ]; then + echo "] IP Pool is empty" + exit 5 + fi + + bash -c "cat > /etc/wireguard/clients/${name}_wg0.conf" << ENDOFFILE +[Interface] +PrivateKey = client_private_key +Address = selected_peer_address/32 +DNS = 10.net_prefix.0.1 + +[Peer] +PublicKey = server_public_key +Endpoint = server_endpoint:550net_prefix +# Route only vpn trafic through vpn +AllowedIPs = 10.net_prefix.0.0/20, allowed_routes +# Route ALL traffic through vpn +#AllowedIPs = 0.0.0.0/0 +PersistentKeepalive = 21 +ENDOFFILE + + bash -c "cat > /etc/wireguard/clients/${name}_alltraffic_wg0.conf" << ENDOFFILE +[Interface] +PrivateKey = client_private_key +Address = selected_peer_address/32 +DNS = 10.net_prefix.0.1 + +[Peer] +PublicKey = server_public_key +Endpoint = server_endpoint:550net_prefix +# Route only vpn trafic through vpn +#AllowedIPs = 10.net_prefix.0.0/20, allowed_routes +# Route ALL traffic through vpn +AllowedIPs = 0.0.0.0/0 +PersistentKeepalive = 21 +ENDOFFILE + + cat << EOF | bash +cd /etc/wireguard/clients +umask 077 +[ ! -f ${name}_private.key ] && wg genkey | tee ${name}_private.key | wg pubkey > ${name}_public.key +EOF + + sed -i "s/net_prefix/${net_prefix}/g" /etc/wireguard/clients/${name}_wg0.conf + sed -i "s#allowed_routes#${allowed_routes}#g" /etc/wireguard/clients/${name}_wg0.conf + sed -i "s/selected_peer_address/${peer_address_from_pool}/g" /etc/wireguard/clients/${name}_wg0.conf + sed -i "s/server_endpoint/${server_endpoint_address}/g" /etc/wireguard/clients/${name}_wg0.conf + sed -i "s/server_public_key/$(sed 's:/:\\/:g' /etc/wireguard/server_public.key)/" /etc/wireguard/clients/${name}_wg0.conf + sed -i "s/client_private_key/$(sed 's:/:\\/:g' /etc/wireguard/clients/${name}_private.key)/" /etc/wireguard/clients/${name}_wg0.conf + + sed -i "s/net_prefix/${net_prefix}/g" /etc/wireguard/clients/${name}_alltraffic_wg0.conf + sed -i "s#allowed_routes#${allowed_routes}#g" /etc/wireguard/clients/${name}_alltraffic_wg0.conf + sed -i "s/selected_peer_address/${peer_address_from_pool}/g" /etc/wireguard/clients/${name}_alltraffic_wg0.conf + sed -i "s/server_endpoint/${server_endpoint_address}/g" /etc/wireguard/clients/${name}_alltraffic_wg0.conf + sed -i "s/server_public_key/$(sed 's:/:\\/:g' /etc/wireguard/server_public.key)/" /etc/wireguard/clients/${name}_alltraffic_wg0.conf + sed -i "s/client_private_key/$(sed 's:/:\\/:g' /etc/wireguard/clients/${name}_private.key)/" /etc/wireguard/clients/${name}_alltraffic_wg0.conf + qrencode -t PNG -o /etc/wireguard/clients/${name}_alltraffic_qr.png < /etc/wireguard/clients/${name}_alltraffic_wg0.conf + + echo "peer=${name}" > /etc/wireguard/clients/${name}.info + echo "email=${email}" >> /etc/wireguard/clients/${name}.info + echo "ip=${peer_address_from_pool}" >> /etc/wireguard/clients/${name}.info + peer_ip=$(cat /etc/wireguard/clients/${name}.info | grep "^ip=" | cut -d '=' -f 2) + + #send mail with the generated config + bash -c "./peer_mail.sh -p ${name}" +else + echo "] ${name} config already exists." + peer_ip=$(cat /etc/wireguard/clients/${name}.info | grep "^ip=" | cut -d '=' -f 2) + + #check if private key was previously disabled + if [ -f /etc/wireguard/clients/${name}_public.disabled ]; then + echo "] ${name} was previously disabled." + mv /etc/wireguard/clients/${name}_public.disabled /etc/wireguard/clients/${name}_public.key + fi + + #check if peer ip already exist in the database + ip_exists_in_pool=$(grep "${peer_ip}$" ${ip_db} | wc -l) + if [ ${ip_exists_in_pool} -eq 1 ]; then + echo "] ${peer_ip} exists in pool. Removing to avoid duplicates." + grep -v "${peer_ip}$" ${ip_db} > ${ip_db}.tmp + mv ${ip_db}.tmp ${ip_db} + fi +fi + +echo "] ${name} (${email}) config set. Endpoint address is ${server_endpoint_address}. Selected user VPN IP is ${peer_ip}" +wg set wg0 peer $(cat /etc/wireguard/clients/${name}_public.key) allowed-ips ${peer_ip}/32 persistent-keepalive 21 + diff --git a/peer_addall.sh b/peer_addall.sh new file mode 100755 index 0000000..1ed174a --- /dev/null +++ b/peer_addall.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +ALLCLIENTS=/etc/wireguard/clients/*.info + +__dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +for client in $ALLCLIENTS; do + peer=$(cat ${client} | grep '^peer=' | cut -d '=' -f 2) + email=$(cat ${client} | grep '^email=' | cut -d '=' -f 2) + echo "] peer_add.sh - peer: $peer - email: $email" + bash ${__dir}/peer_add.sh -p ${peer} -e ${email} +done diff --git a/peer_del.sh b/peer_del.sh new file mode 100755 index 0000000..b6decd6 --- /dev/null +++ b/peer_del.sh @@ -0,0 +1,62 @@ +#!/usr/bin/env bash + +source config + +check_root() { + if [ "$EUID" -ne 0 ]; then + printf %b\\n "] Please run the script as root." + exit 1 + fi +} + +usage() +{ + echo "Usage: peer_del.sh -p " +} + +check_root + +no_args="true" +while getopts p: option +do + case $option in + (p) + name=${OPTARG};; + (*) + usage + exit;; + esac + no_args="false" +done + +[[ "$no_args" == "true" ]] && { usage; exit 1; } + +# Check if peer config exist +if [ ! -f /etc/wireguard/clients/${name}.info ]; then + echo "] Peer ${name} does not exists" + exit 2 +fi + +echo "] Removing wireguard config for ${name}" +salvaged_ip=$(cat /etc/wireguard/clients/${name}.info | grep "^ip=" | cut -d '=' -f 2) +echo "] Salvaged IP is ${salvaged_ip} and will be returned back to ${ip_db}" +echo ${salvaged_ip} >> ${ip_db} + +#check if config is previously disabled +if [ -f /etc/wireguard/clients/${name}_public.disabled ]; then + echo "] ${name} was previously disabled." + mv /etc/wireguard/clients/${name}_public.disabled /etc/wireguard/clients/${name}_public.key +fi + +wg set wg0 peer $(cat /etc/wireguard/clients/${name}_public.key) remove +rm /etc/wireguard/clients/${name}_public.key +rm /etc/wireguard/clients/${name}.info + +# remove additional sensitive info +rm -f /etc/wireguard/clients/${name}_wg0.conf +rm -f /etc/wireguard/clients/${name}_qr.png +rm -f /etc/wireguard/clients/${name}_alltraffic_wg0.conf +rm -f /etc/wireguard/clients/${name}_alltraffic_qr.png +rm -f /etc/wireguard/clients/${name}_private.key + +exit 0 diff --git a/peer_disable.sh b/peer_disable.sh new file mode 100755 index 0000000..7f8da85 --- /dev/null +++ b/peer_disable.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash + +source config + +check_root() { + if [ "$EUID" -ne 0 ]; then + printf %b\\n "] Please run the script as root." + exit 1 + fi +} + +usage() +{ + echo "Usage: peer_disable.sh -p " +} + +check_root + +no_args="true" +while getopts p: option +do + case $option in + (p) + name=${OPTARG};; + (*) + usage + exit;; + esac + no_args="false" +done + +[[ "$no_args" == "true" ]] && { usage; exit 1; } + +# Check if peer config exist +if [ ! -f /etc/wireguard/clients/${name}.info ]; then + echo "] Peer ${name} does not exists" + exit 2 +fi + +#check if config is previously disabled +if [ -f /etc/wireguard/clients/${name}_public.disabled ]; then + #echo "] ${name} is already disabled." + exit 0 +fi + +echo "] Disable wireguard config for ${name}" +wg set wg0 peer $(cat /etc/wireguard/clients/${name}_public.key) remove +mv /etc/wireguard/clients/${name}_public.key /etc/wireguard/clients/${name}_public.disabled + +exit 0 diff --git a/peer_mail.sh b/peer_mail.sh new file mode 100755 index 0000000..654b3b5 --- /dev/null +++ b/peer_mail.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash + +source config + +check_root() { + if [ "$EUID" -ne 0 ]; then + printf %b\\n "] Please run the script as root." + exit 1 + fi +} + +usage() +{ + echo "Usage: peer_mail.sh -p " +} + +check_root + +no_args="true" +while getopts p: option +do + case $option in + (p) + name=${OPTARG};; + (*) + usage + exit;; + esac + no_args="false" +done + +[[ "$no_args" == "true" ]] && { usage; exit 1; } + +email_client=$(cat /etc/wireguard/clients/${name}.info | grep "^email=" | cut -d '=' -f 2) + +if [ -z ${email_client} ]; then + echo "] Peer not found." + exit 2 +fi + +echo "] Sending the profile data to $email_destination" +# strip non alphanumeric characters from peer name +stname=$(echo ${name} | sed "s/[^[:alnum:]-]//g") + +# fill profile tmp dir with data +mkdir payload +mkdir payload/mobile +cp -v /etc/wireguard/clients/${name}_wg0.conf payload/profile.conf +cp -v /etc/wireguard/clients/${name}_alltraffic_wg0.conf payload/profile_alltraffic.conf +cp -v /etc/wireguard/clients/${name}_alltraffic_qr.png payload/mobile/profile_alltraffic_QR.png + +mkdir payload/linux +cp -v client-tools/wg-rapid payload/linux/wg-rapid +cp -v client-tools/startvpn.desktop payload/linux/startvpn.desktop +chmod +x payload/linux/startvpn.desktop + +# pack the attachment +cd payload +zip -r ../payload.zip . +cd .. +mv payload.zip ${stname}_profile.zip + +# sent the message +mutt -s "WireGate VPN for ${email_client}" ${email_destination} -a ${stname}_profile.zip < mail.md +#mutt -s "WireGate VPN for ${email_client}" ${email_client} -a ${stname}_profile.zip < mail.md + +rm -f -r -v payload + +exit 0 diff --git a/wgldap.sh b/wgldap.sh new file mode 100755 index 0000000..5c6e14d --- /dev/null +++ b/wgldap.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +__dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +journalctl -u wgldapsync -f diff --git a/wgldapsync.service b/wgldapsync.service new file mode 100644 index 0000000..45ce351 --- /dev/null +++ b/wgldapsync.service @@ -0,0 +1,7 @@ +[Unit] +Description=WireGate LDAP auto-sync service + +[Service] +Type=simple +WorkingDirectory=/root/wiregate +ExecStart=/usr/bin/python3 /root/wiregate/ldapsync.py diff --git a/wgldapsync.timer b/wgldapsync.timer new file mode 100644 index 0000000..a43de8a --- /dev/null +++ b/wgldapsync.timer @@ -0,0 +1,9 @@ +[Unit] +Description=WireGate LDAP auto-sync timer + +[Timer] +OnUnitActiveSec=30min +OnBootSec=30min + +[Install] +WantedBy=timers.target diff --git a/wgstats.sh b/wgstats.sh new file mode 100755 index 0000000..4a636c4 --- /dev/null +++ b/wgstats.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +__dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +echo "] WireGate plugins:" +systemctl list-timers --all | grep 'ACTIVATES\|wgldapsync' +echo "" + +echo "] WireGate peers:" +tmpfile1=$(mktemp /tmp/wgstats.1.XXXXXX) +tmpfile2=$(mktemp /tmp/wgstats.2.XXXXXX) + +ALLPEERS=$(wg show all dump | grep -v off) + +echo "$ALLPEERS" | while IFS= read -r peer ; do + peerkey=$(echo "$peer" | cut -d $'\t' -f 2) + peerfile=$(basename $(grep -l "${peerkey}" /etc/wireguard/clients/*_public.key)) + peername=$(echo ${peerfile} | cut -d '_' -f 1) + clientip=$(echo "$peer" | cut -d $'\t' -f 4) + peerip=$(echo "$peer" | cut -d $'\t' -f 5) + peerlatesths=$(echo "$peer" | cut -d $'\t' -f 6) + if [ ${peerlatesths} -eq 0 ]; then + peerlatesthsfmt="Never" + else + peerlatesthsfmt=$(date -d@${peerlatesths}) + fi + peerrx=$(echo "$peer" | cut -d $'\t' -f 7 | numfmt --to=iec-i --suffix=B) + peertx=$(echo "$peer" | cut -d $'\t' -f 8 | numfmt --to=iec-i --suffix=B) + echo "${peerlatesths},$peername,$clientip,$peerip,${peerlatesthsfmt},$peerrx,$peertx" >> $tmpfile1 +done + +sort -k1 -n -t "," $tmpfile1 | cut -d "," -f 2- > $tmpfile2 +sed -i '1s/^/Peer,Client Address,Peer Address,Latest Handshake,Data Recieved,Data Sent\n/' $tmpfile2 +column -e -t -s "," $tmpfile2 + +rm $tmpfile1 +rm $tmpfile2 +exit 0