Choose random PIA server in a specified region instead of hardcoded. And more TODOs addressed.

This commit is contained in:
Zuckerberg 2023-03-12 22:55:46 -06:00
parent 759fe04185
commit 820cd392f1

View File

@ -7,13 +7,10 @@
# https://github.com/thrnz/docker-wireguard-pia/blob/master/extra/wg-gen.sh # https://github.com/thrnz/docker-wireguard-pia/blob/master/extra/wg-gen.sh
# TODO handle potential errors (or at least print status, success, and failures to the console) # TODO handle potential errors (or at least print status, success, and failures to the console)
# TODO handle 2 month limit for port # TODO parameterize names of systemd services so that multiple wg VPNs could coexist in theory easier
# TODO handle VPN container with different name # TODO implement this module such that the wireguard VPN doesn't have to live in a container
# - TODO parameterize names of systemd services so that multiple wg VPNs could coexist in theory easier
# - TODO implement this module such that the wireguard VPN doesn't have to live in a container
# TODO add some variance to the port forward timer
# TODO look at wg-gen script for example of looking up a random server in a region and connect to that (user should not need to specify IP addr)
# TODO don't add forward rules if the PIA port is the same as cfg.forwardedPort # TODO don't add forward rules if the PIA port is the same as cfg.forwardedPort
# TODO verify signatures of PIA responses
with builtins; with builtins;
with lib; with lib;
@ -28,13 +25,53 @@ let
PIA_TOKEN=`curl -s -u "$PIA_USER:$PIA_PASS" https://www.privateinternetaccess.com/gtoken/generateToken | jq -r '.token'` PIA_TOKEN=`curl -s -u "$PIA_USER:$PIA_PASS" https://www.privateinternetaccess.com/gtoken/generateToken | jq -r '.token'`
''; '';
chooseWireguardServer = ''
servers=$(mktemp)
servers_json=$(mktemp)
curl -s "https://serverlist.piaservers.net/vpninfo/servers/v6" > "$servers"
# extract json part only
head -n 1 "$servers" | tr -d '\n' > "$servers_json"
echo "Available location ids:" && jq '.regions | .[] | {name, id, port_forward}' "$servers_json"
# Some locations have multiple servers available. Pick a random one.
totalservers=$(jq -r '.regions | .[] | select(.id=="'${cfg.serverLocation}'") | .servers.wg | length' "$servers_json")
if ! [[ "$totalservers" =~ ^[0-9]+$ ]] || [ "$totalservers" -eq 0 ] 2>/dev/null; then
echo "Location \"${cfg.serverLocation}\" not found."
exit 1
fi
serverindex=$(( RANDOM % totalservers))
WG_HOSTNAME=$(jq -r '.regions | .[] | select(.id=="'${cfg.serverLocation}'") | .servers.wg | .['$serverindex'].cn' "$servers_json")
WG_SERVER_IP=$(jq -r '.regions | .[] | select(.id=="'${cfg.serverLocation}'") | .servers.wg | .['$serverindex'].ip' "$servers_json")
WG_SERVER_PORT=$(jq -r '.groups.wg | .[0] | .ports | .[0]' "$servers_json")
# write chosen server
rm -f /tmp/${cfg.interfaceName}-server.conf
touch /tmp/${cfg.interfaceName}-server.conf
chmod 700 /tmp/${cfg.interfaceName}-server.conf
echo "$WG_HOSTNAME" >> /tmp/${cfg.interfaceName}-server.conf
echo "$WG_SERVER_IP" >> /tmp/${cfg.interfaceName}-server.conf
echo "$WG_SERVER_PORT" >> /tmp/${cfg.interfaceName}-server.conf
rm $servers_json $servers
'';
getChosenWireguardServer = ''
WG_HOSTNAME=`sed '1q;d' /tmp/${cfg.interfaceName}-server.conf`
WG_SERVER_IP=`sed '2q;d' /tmp/${cfg.interfaceName}-server.conf`
WG_SERVER_PORT=`sed '3q;d' /tmp/${cfg.interfaceName}-server.conf`
'';
refreshPIAPort = '' refreshPIAPort = ''
${getChosenWireguardServer}
signature=`sed '1q;d' /tmp/${cfg.interfaceName}-port-renewal` signature=`sed '1q;d' /tmp/${cfg.interfaceName}-port-renewal`
payload=`sed '2q;d' /tmp/${cfg.interfaceName}-port-renewal` payload=`sed '2q;d' /tmp/${cfg.interfaceName}-port-renewal`
bind_port_response=`curl -Gs -m 5 --connect-to "${cfg.serverHostname}::${cfg.serverIp}:" --cacert "${./ca.rsa.4096.crt}" --data-urlencode "payload=$payload" --data-urlencode "signature=$signature" "https://${cfg.serverHostname}:19999/bindPort"` bind_port_response=`curl -Gs -m 5 --connect-to "$WG_HOSTNAME::$WG_SERVER_IP:" --cacert "${./ca.rsa.4096.crt}" --data-urlencode "payload=$payload" --data-urlencode "signature=$signature" "https://$WG_HOSTNAME:19999/bindPort"`
''; '';
portForwarding = cfg.forwardPortForTransmission || cfg.forwardedPort != null; portForwarding = cfg.forwardPortForTransmission || cfg.forwardedPort != null;
containerServiceName = "container@${config.vpn-container.containerName}.service";
in { in {
options.pia.wireguard = { options.pia.wireguard = {
enable = mkEnableOption "Enable private internet access"; enable = mkEnableOption "Enable private internet access";
@ -51,13 +88,9 @@ in {
description = "The port wireguard listens on for this VPN connection"; description = "The port wireguard listens on for this VPN connection";
default = 51820; default = 51820;
}; };
serverHostname = mkOption { serverLocation = mkOption {
type = types.str; type = types.str;
default = "zurich406"; default = "swiss";
};
serverIp = mkOption {
type = types.str;
default = "156.146.62.153";
}; };
interfaceName = mkOption { interfaceName = mkOption {
type = types.str; type = types.str;
@ -81,7 +114,15 @@ in {
} }
]; ];
vpn-container.mounts = [ "/tmp/${cfg.interfaceName}.conf" "/tmp/${cfg.interfaceName}-address.conf" ]; # mounts used to pass the connection parameters to the container
# the container doesn't have internet until it uses these parameters so it cannot fetch them itself
vpn-container.mounts = [
"/tmp/${cfg.interfaceName}.conf"
"/tmp/${cfg.interfaceName}-server.conf"
"/tmp/${cfg.interfaceName}-address.conf"
];
# The container takes ownership of the wireguard interface on its startup
containers.vpn.interfaces = [ cfg.interfaceName ]; containers.vpn.interfaces = [ cfg.interfaceName ];
# TODO: while this is much better than "loose" networking, it seems to have issues with firewall restarts # TODO: while this is much better than "loose" networking, it seems to have issues with firewall restarts
@ -103,9 +144,9 @@ in {
requires = [ "network-online.target" ]; requires = [ "network-online.target" ];
after = [ "network.target" "network-online.target" ]; after = [ "network.target" "network-online.target" ];
before = [ "container@vpn.service" ]; before = [ containerServiceName ];
requiredBy = [ "container@vpn.service" ]; requiredBy = [ containerServiceName ];
partOf = [ "container@vpn.service" ]; partOf = [ containerServiceName ];
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
path = with pkgs; [ wireguard-tools jq curl iproute ]; path = with pkgs; [ wireguard-tools jq curl iproute ];
@ -113,6 +154,10 @@ in {
serviceConfig = { serviceConfig = {
Type = "oneshot"; Type = "oneshot";
RemainAfterExit = true; RemainAfterExit = true;
# restart once a month; PIA forwarded port expires after two months
# because the container is "PartOf" this unit, it gets restarted too
RuntimeMaxSec="30d";
}; };
script = '' script = ''
@ -120,8 +165,7 @@ in {
# cannot do without internet to start with. NAT'ing the host's internet would address this # cannot do without internet to start with. NAT'ing the host's internet would address this
# issue but is not ideal because then leaking network outside of the VPN is more likely. # issue but is not ideal because then leaking network outside of the VPN is more likely.
WG_HOSTNAME=${cfg.serverHostname} ${chooseWireguardServer}
WG_SERVER_IP=${cfg.serverIp}
${getPIAToken} ${getPIAToken}
@ -130,7 +174,7 @@ in {
pubKey=$(echo "$privKey" | wg pubkey) pubKey=$(echo "$privKey" | wg pubkey)
# authorize our WG keys with the PIA server we are about to connect to # authorize our WG keys with the PIA server we are about to connect to
wireguard_json=`curl -s -G --connect-to "$WG_HOSTNAME::$WG_SERVER_IP:" --cacert "${./ca.rsa.4096.crt}" --data-urlencode "pt=$PIA_TOKEN" --data-urlencode "pubkey=$pubKey" https://$WG_HOSTNAME:1337/addKey` wireguard_json=`curl -s -G --connect-to "$WG_HOSTNAME::$WG_SERVER_IP:" --cacert "${./ca.rsa.4096.crt}" --data-urlencode "pt=$PIA_TOKEN" --data-urlencode "pubkey=$pubKey" https://$WG_HOSTNAME:$WG_SERVER_PORT/addKey`
# create wg-quick config file # create wg-quick config file
rm -f /tmp/${cfg.interfaceName}.conf /tmp/${cfg.interfaceName}-address.conf rm -f /tmp/${cfg.interfaceName}.conf /tmp/${cfg.interfaceName}-address.conf
@ -167,7 +211,7 @@ in {
}; };
vpn-container.config.systemd.services.pia-vpn-wireguard = { vpn-container.config.systemd.services.pia-vpn-wireguard = {
description = "PIA VPN WireGuard Tunnel"; description = "Initializes the PIA VPN WireGuard Tunnel";
requires = [ "network-online.target" ]; requires = [ "network-online.target" ];
after = [ "network.target" "network-online.target" ]; after = [ "network.target" "network-online.target" ];
@ -188,6 +232,8 @@ in {
# Thus, assumes wg interface was already created: # Thus, assumes wg interface was already created:
# ip link add ${cfg.interfaceName} type wireguard # ip link add ${cfg.interfaceName} type wireguard
${getChosenWireguardServer}
myaddress=`cat /tmp/${cfg.interfaceName}-address.conf` myaddress=`cat /tmp/${cfg.interfaceName}-address.conf`
wg setconf ${cfg.interfaceName} /tmp/${cfg.interfaceName}.conf wg setconf ${cfg.interfaceName} /tmp/${cfg.interfaceName}.conf
@ -205,7 +251,7 @@ in {
# Reserve port # Reserve port
${getPIAToken} ${getPIAToken}
payload_and_signature=`curl -s -m 5 --connect-to "${cfg.serverHostname}::${cfg.serverIp}:" --cacert "${./ca.rsa.4096.crt}" -G --data-urlencode "token=$PIA_TOKEN" "https://${cfg.serverHostname}:19999/getSignature"` payload_and_signature=`curl -s -m 5 --connect-to "$WG_HOSTNAME::$WG_SERVER_IP:" --cacert "${./ca.rsa.4096.crt}" -G --data-urlencode "token=$PIA_TOKEN" "https://$WG_HOSTNAME:19999/getSignature"`
signature=$(echo "$payload_and_signature" | jq -r '.signature') signature=$(echo "$payload_and_signature" | jq -r '.signature')
payload=$(echo "$payload_and_signature" | jq -r '.payload') payload=$(echo "$payload_and_signature" | jq -r '.payload')
port=$(echo "$payload" | base64 -d | jq -r '.port') port=$(echo "$payload" | base64 -d | jq -r '.port')
@ -299,7 +345,10 @@ in {
enable = portForwarding; enable = portForwarding;
partOf = [ "pia-vpn-wireguard-forward-port.service" ]; partOf = [ "pia-vpn-wireguard-forward-port.service" ];
wantedBy = [ "timers.target" ]; wantedBy = [ "timers.target" ];
timerConfig.OnCalendar = "*:0/10"; # 10 minutes timerConfig = {
OnCalendar = "*:0/10"; # 10 minutes
RandomizedDelaySec = "1m"; # vary by 1 min to give PIA servers some relief
};
}; };
age.secrets."pia-login.conf".file = ../../secrets/pia-login.conf; age.secrets."pia-login.conf".file = ../../secrets/pia-login.conf;