[PATCH] wg-quick linux: Add strip-and-eval cmd to extract keys from PostUp

Robin Schneider ypid at riseup.net
Sun Oct 4 23:20:49 CEST 2020


The manpage mentions the trick to use PostUp to read the PrivateKey (or
PresharedKey) from a command (or file). However, when you actually use
that you notice that this is currently not fully supported. The issue is
that

```Shell
wg syncconf wgnet0 <(wg-quick strip wgnet0)
```

from the manpage now breaks the VPN because it *removes* the private key
from the WireGuard interface. The reason is that `strip` removes PostUp
of course.

This patch tries to add full support to read WireGuard keys from files
or command outputs by evaluating PostUp using a best effort approach
(using regex). It will not work for everything but when you follow the
manpage closely, it will work.

I also propose to update the systemd template to make seamless use of
this. This is not a must because the sysadmin can easily change the
ExecReload using systemd drop-in files.

Note that the patchset is incomplete (currently only for Linux).
I don’t have all the other OSes laying around. When the patch looks ok, 
I can apply it to the other versions also.

Example use of this patch:
https://github.com/ypid/ansible-wireguard/tree/prepare-for-debops

Signed-off-by: Robin Schneider <ypid at riseup.net>
---
 src/man/wg-quick.8            |  9 +++++++++
 src/systemd/wg-quick at .service |  2 +-
 src/wg-quick/linux.bash       | 36 ++++++++++++++++++++++++++++++++---
 3 files changed, 43 insertions(+), 4 deletions(-)

diff --git a/src/man/wg-quick.8 b/src/man/wg-quick.8
index b84eb64..f620704 100644
--- a/src/man/wg-quick.8
+++ b/src/man/wg-quick.8
@@ -13,6 +13,8 @@ wg-quick - set up a WireGuard interface simply
 .I save
 |
 .I strip
+|
+.I strip-and-eval
 ] [
 .I CONFIG_FILE
 |
@@ -34,6 +36,7 @@ with all
 .BR wg-quick (8)-specific
 options removed, suitable for use with
 .BR wg (8).
+Use \fIstrip-and-eval\fP in case you use `PostUp' to read PrivateKey or PresharedKey from a file or command and need them in the output.
 
 \fICONFIG_FILE\fP is a configuration file, whose filename is the interface name
 followed by `.conf'. Otherwise, \fIINTERFACE\fP is an interface name, with configuration
@@ -256,6 +259,12 @@ sessions:
 
 \fB    # wg syncconf wgnet0 <(wg-quick strip wgnet0)\fP
 
+\fIstrip-and-eval\fP additionally extracts PrivateKey and PresharedKey from `PostUp` statements and translates them into configuration that
+.BR wg (8)
+does understand:
+
+\fB    # wg syncconf wgnet0 <(wg-quick strip-and-eval wgnet0)\fP
+
 .SH SEE ALSO
 .BR wg (8),
 .BR ip (8),
diff --git a/src/systemd/wg-quick at .service b/src/systemd/wg-quick at .service
index dbdab44..1d59446 100644
--- a/src/systemd/wg-quick at .service
+++ b/src/systemd/wg-quick at .service
@@ -15,7 +15,7 @@ Type=oneshot
 RemainAfterExit=yes
 ExecStart=/usr/bin/wg-quick up %i
 ExecStop=/usr/bin/wg-quick down %i
-ExecReload=/bin/bash -c 'exec /usr/bin/wg syncconf %i <(exec /usr/bin/wg-quick strip %i)'
+ExecReload=/bin/bash -c 'exec /usr/bin/wg syncconf %i <(exec /usr/bin/wg-quick strip-and-eval %i)'
 Environment=WG_ENDPOINT_RESOLUTION_RETRIES=infinity
 
 [Install]
diff --git a/src/wg-quick/linux.bash b/src/wg-quick/linux.bash
index e4d4c4f..89f9ef9 100755
--- a/src/wg-quick/linux.bash
+++ b/src/wg-quick/linux.bash
@@ -38,8 +38,11 @@ die() {
 }
 
 parse_options() {
+	local parsing_mode part_of_command peer_pubkey
 	local interface_section=0 line key value stripped v
+	declare -A peer_pubkey_to_psk
 	CONFIG_FILE="$1"
+	parsing_mode="${2:-safe}"
 	[[ $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"
@@ -63,12 +66,35 @@ parse_options() {
 			Table) TABLE="$value"; continue ;;
 			PreUp) PRE_UP+=( "$value" ); continue ;;
 			PreDown) PRE_DOWN+=( "$value" ); continue ;;
-			PostUp) POST_UP+=( "$value" ); continue ;;
+			PostUp) POST_UP+=( "$value" );
+				if [[ $parsing_mode == "unsafe" ]]; then
+					part_of_command=""
+					if [[ $value =~ ^wg\ +set\ +%i\ +private-key\ +(.+)$ ]]; then
+						key='PrivateKey'
+						part_of_command="${BASH_REMATCH[1]}"
+					elif [[ $value =~ ^wg\ +set\ +%i\ +peer\ +(.+)\ +preshared-key\ +(.+)$ ]]; then
+						key='PresharedKey'
+						peer_pubkey="${BASH_REMATCH[1]}"
+						part_of_command="${BASH_REMATCH[2]}"
+					fi
+					if [[ -n "$part_of_command" ]]; then
+						part_of_command="${part_of_command//%i/$INTERFACE}"
+						value="$(eval "cat $part_of_command")"
+						case "$key" in
+							PresharedKey) peer_pubkey_to_psk["$peer_pubkey"]="$value" ;;
+							*) WG_CONFIG+="$key = $value"$'\n' ;;
+						esac
+					fi
+				fi
+				continue ;;
 			PostDown) POST_DOWN+=( "$value" ); continue ;;
 			SaveConfig) read_bool SAVE_CONFIG "$value"; continue ;;
 			esac
 		fi
 		WG_CONFIG+="$line"$'\n'
+		if [[ $interface_section -eq 0 && $key == 'PublicKey' && -n "${peer_pubkey_to_psk[$value]}" ]]; then
+			WG_CONFIG+="PresharedKey = ${peer_pubkey_to_psk[$value]}"$'\n'
+		fi
 	done < "$CONFIG_FILE"
 	shopt -u nocasematch
 }
@@ -224,7 +250,7 @@ add_default() {
 	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 
+	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"
@@ -298,7 +324,7 @@ execute_hooks() {
 
 cmd_usage() {
 	cat >&2 <<-_EOF
-	Usage: $PROGRAM [ up | down | save | strip ] [ CONFIG_FILE | INTERFACE ]
+	Usage: $PROGRAM [ up | down | save | strip | strip-and-eval ] [ CONFIG_FILE | INTERFACE ]
 
 	  CONFIG_FILE is a configuration file, whose filename is the interface name
 	  followed by \`.conf'. Otherwise, INTERFACE is an interface name, with
@@ -381,6 +407,10 @@ elif [[ $# -eq 2 && $1 == strip ]]; then
 	auto_su
 	parse_options "$2"
 	cmd_strip
+elif [[ $# -eq 2 && $1 == strip-and-eval ]]; then
+	auto_su
+	parse_options "$2" "unsafe"
+	cmd_strip
 else
 	cmd_usage
 	exit 1
-- 
2.20.1



More information about the WireGuard mailing list