Skip to content

Instantly share code, notes, and snippets.

@kajeagentspi
Created December 7, 2020 08:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kajeagentspi/0370ce054d782e18c4e3822040f1fd0e to your computer and use it in GitHub Desktop.
Save kajeagentspi/0370ce054d782e18c4e3822040f1fd0e to your computer and use it in GitHub Desktop.
PIA Token patch
#!/bin/bash
# check endpoint is port forward enabled (pia only)
function port_forward_status() {
echo "[info] Port forwarding is enabled"
echo "[info] Checking endpoint '${VPN_REMOTE_SERVER}' is port forward enabled..."
# run curl to grab api result
jq_query_result=$(curl --silent --insecure "${pia_vpninfo_api}")
if [[ "${?}" != 0 ]]; then
echo "[warn] PIA VPN info API currently down, skipping endpoint port forward check"
else
# run jq query to get endpoint name (dns) only, use xargs to turn into single line string
jq_query_details=$(echo "${jq_query_result}" | jq -r "${jq_query_portforward_enabled}" 2> /dev/null | xargs)
# run grep to check that defined vpn remote is in the list of port forward enabled endpoints
# grep -w = exact match (whole word), grep -q = quiet mode (no output)
echo "${jq_query_details}" | grep -qw "${VPN_REMOTE_SERVER}"
if [[ "${?}" != 0 ]]; then
echo "[warn] PIA endpoint '${VPN_REMOTE_SERVER}' is not in the list of endpoints that support port forwarding, DL/UL speeds maybe slow"
echo "[info] Please consider switching to one of the endpoints shown below"
else
echo "[info] PIA endpoint '${VPN_REMOTE_SERVER}' is in the list of endpoints that support port forwarding"
fi
# convert to list with separator being space
IFS=' ' read -ra jq_query_details_list <<< "${jq_query_details}"
echo "[info] List of PIA endpoints that support port forwarding:-"
# loop over list of port forward enabled endpooints and echo out to console
for i in "${jq_query_details_list[@]}"; do
echo "[info] ${i}"
done
fi
}
# attempt to get incoming port (pia only)
function get_incoming_port_nextgen() {
retry_count=12
retry_wait_secs=10
while true; do
# get vpn local ip (not used) and gateway ip
source /root/getvpnip.sh
while true; do
if [ "${retry_count}" -eq "0" ]; then
echo "[warn] Unable to download PIA json to generate token, exiting script..."
trigger_failure ; return 1
fi
# get token json response AFTER vpn established
# note binding to the vpn interface (using --interface flag for curl) is required
# due to users potentially using the 10.x.x.x range for lan, causing failure
token_json_response=$(curl --interface "${VPN_DEVICE_TYPE}" --silent --insecure -u "${VPN_USER}:${VPN_PASS}" "https://10.0.0.1/authv3/generateToken")
if [ "$(echo "${token_json_response}" | jq -r '.status')" != "OK" ]; then
break
echo "[warn] Unable to successfully download PIA json to generate token from URL 'https://10.0.0.1/authv3/generateToken'"
echo "[info] ${retry_count} retries left"
echo "[info] Retrying in ${retry_wait_secs} secs..."
retry_count=$((retry_count-1))
sleep "${retry_wait_secs}"s & wait $!
else
# reset retry count on successful step
retry_count=12
break
fi
done
while true; do
if [ "${retry_count}" -eq "0" ]; then
echo "[warn] Unable to download PIA json payload, exiting script..."
trigger_failure ; return 1
fi
# get token
token="INSERT_TOKEN_HERE"
#token=$(echo "${token_json_response}" | jq -r '.token')
# get payload and signature
# note use of urlencode, this is required, otherwise login failure can occur
payload_and_sig=$(curl --insecure --silent --max-time 5 --get --data-urlencode "token=${token}" "https://${vpn_gateway_ip}:19999/getSignature")
if [ "$(echo "${payload_and_sig}" | jq -r '.status')" != "OK" ]; then
echo "[warn] Unable to successfully download PIA json payload from URL 'https://${vpn_gateway_ip}:19999/getSignature' using token '${token}'"
echo "[info] ${retry_count} retries left"
echo "[info] Retrying in ${retry_wait_secs} secs..."
retry_count=$((retry_count-1))
sleep "${retry_wait_secs}"s & wait $!
else
# reset retry count on successful step
retry_count=12
break
fi
done
payload=$(echo "${payload_and_sig}" | jq -r '.payload')
signature=$(echo "${payload_and_sig}" | jq -r '.signature')
# decode payload to get token, port, and expires date (2 months)
payload_decoded=$(echo "${payload}" | base64 -d | jq)
if [ "${?}" -eq 0 ]; then
token=$(echo "${payload_decoded}" | jq -r '.token')
port=$(echo "${payload_decoded}" | jq -r '.port')
# note expires_at time in this format'2020-11-24T22:12:07.627551124Z'
expires_at=$(echo "${payload_decoded}" | jq -r '.expires_at')
if [[ "${DEBUG}" == "true" ]]; then
echo "[debug] Token is '${token}'"
echo "[debug] Port allocated is '${port}'"
echo "[debug] Port expires at '${expires_at}'"
fi
else
echo "[warn] Unable to decode payload, exiting script..."
trigger_failure ; return 1
fi
if [[ "${port}" =~ ^-?[0-9]+$ ]]; then
# write port number to text file (read by downloader script)
echo "${port}" > /tmp/getvpnport
else
echo "[warn] Incoming port assigned is not a decimal value '${port}', exiting script..."
trigger_failure ; return 1
fi
# run function to bind port every 15 minutes (background)
bind_incoming_port_nextgen &
# current time in GMT minus 2 hours to ensure we are within the 2 month time period, compared to epoch
current_datetime_epoch=$(TZ=GMT date -d '2 hour ago' +%s)
# expires_at datetime as a date object in current time format, compared to epoch
expires_at_convert_epoch=$(date -d "${expires_at}" +%s)
# calculate time left before port expires
expires_at_delta=$(( (expires_at_convert_epoch - current_datetime_epoch) ))
# sleep for time difference
sleep "${expires_at_delta}"s & wait $!
done
}
# attempt to bind incoming port (pia only)
function bind_incoming_port_nextgen() {
# run function to set trap so we exit cleanly when kill issued
# due to this function running in background we need to set trap here as well as main
set_trap
retry_count=12
retry_wait_secs=10
while true; do
if [ "${retry_count}" -eq "0" ]; then
echo "[warn] Unable to bind incoming port '${port}', exiting script..."
trigger_failure ; return 1
fi
# note use of urlencode, this is required, otherwise login failure can occur
bind_port=$(curl --insecure --silent --max-time 5 --get --data-urlencode "payload=${payload}" --data-urlencode "signature=${signature}" "https://${vpn_gateway_ip}:19999/bindPort")
if [ "$(echo "${bind_port}" | jq -r '.status')" != "OK" ]; then
echo "[warn] Unable to bind port using URL 'https://${vpn_gateway_ip}:19999/bindPort'"
retry_count=$((retry_count-1))
echo "[info] ${retry_count} retries left"
echo "[info] Retrying in ${retry_wait_secs} secs..."
sleep "${retry_wait_secs}"s & wait $!
continue
else
# reset retry count on successful step
retry_count=12
fi
echo "[info] Successfully assigned and bound incoming port '${port}'"
# re-issue of bind required every 15 minutes
sleep 15m & wait $!
done
}
function trigger_failure() {
echo "[info] Port forwarding failure, creating file '/tmp/portfailure' to indicate failure..."
touch "/tmp/portfailure"
chmod +r "/tmp/portfailure"
}
function set_trap() {
# trap kill signal INT (-2), TERM (-15) or EXIT (internal bash).
# kill all child processes, break while loops and exit with exit code 1
# required to allow us to stop this script as it has several sleep
# commands and background function
trap 'kill $(jobs -p); break; exit 1' INT TERM EXIT
}
# check that app requires port forwarding and vpn provider is pia (note this env var is passed through to up script via openvpn --sentenv option)
if [[ "${APPLICATION}" != "sabnzbd" ]] && [[ "${APPLICATION}" != "privoxy" ]] && [[ "${VPN_PROV}" == "pia" ]]; then
if [[ "${STRICT_PORT_FORWARD}" == "no" ]]; then
echo "[info] Port forwarding is not enabled"
# create empty incoming port file (read by downloader script)
touch /tmp/getvpnport
else
echo "[info] Script started to assign incoming port"
# write pid of this script to file, this file is then used to kill this script if openvpn/wireguard restarted/killed
echo "${BASHPID}" > '/tmp/getvpnport.pid'
# run function to set trap so we exit cleanly when kill issued
set_trap
# pia api url for endpoint status (port forwarding enabled true|false)
pia_vpninfo_api="https://serverlist.piaservers.net/vpninfo/servers/v4"
# jq (json query tool) query to list port forward enabled servers by hostname (dns)
jq_query_portforward_enabled='.regions | .[] | select(.port_forward=='true') | .dns'
# jq (json query tool) query to select current vpn remote server (from ovpn file) and then get metadata server ip address
jq_query_metadata_ip=".regions | .[] | select(.dns|tostring | contains(\"${VPN_REMOTE_SERVER}\")) | .servers | .meta | .[] | .ip"
port_forward_status
get_incoming_port_nextgen
echo "[info] Script finished to assign incoming port"
fi
else
echo "[info] Application does not require port forwarding or VPN provider is != pia, skipping incoming port assignment"
fi
#!/bin/bash
function pia_create_wireguard_keys() {
# create ephemeral wireguard private and public keys
wireguard_private_key=$(wg genkey)
wireguard_public_key=$(echo "${wireguard_private_key}" | wg pubkey)
}
# note no retry count on this function as we must get a token to progress
function pia_generate_token() {
retry_wait_secs=10
while true; do
# jq (json query tool) query to select current vpn remote server (from env var) and then get metadata server ip address
jq_query_metadata_ip=".regions | .[] | select(.dns|tostring | contains(\"${VPN_REMOTE_SERVER}\")) | .servers | .meta | .[] | .ip"
# get metadata server ip address
vpn_remote_metadata_server_ip=$(echo "${PIA_VPNINFO_API}" | jq -r "${jq_query_metadata_ip}")
# get token json response BEFORE vpn established
token_json_response=$(curl --silent --insecure -u "${VPN_USER}:${VPN_PASS}" "https://${vpn_remote_metadata_server_ip}/authv3/generateToken")
if [ "$(echo "${token_json_response}" | jq -r '.status')" != "OK" ]; then
break
echo "[warn] Unable to successfully download PIA json to generate token from URL 'https://${vpn_remote_metadata_server_ip}/authv3/generateToken'"
echo "[info] Retrying in ${retry_wait_secs} secs..."
sleep "${retry_wait_secs}"s
else
break
fi
done
# get token
token="INSERT_TOKEN_HERE"
# token=$(echo "${token_json_response}" | jq -r '.token')
if [[ "${DEBUG}" == "true" ]]; then
echo "[debug] PIA 'meta ip' is '${vpn_remote_metadata_server_ip}'"
echo "[debug] PIA generated 'token' is '${token}'"
fi
}
function pia_wireguard_authenticate() {
# authenticate via the pia wireguard restful api
# this will return json with data required for authentication.
echo "[info] Trying to connect to the PIA WireGuard API on '${VPN_REMOTE_SERVER}'..."
pia_wireguard_authentication_json=$(curl --silent --get --insecure --data-urlencode "pt=${token}" --data-urlencode "pubkey=${wireguard_public_key}" "https://${VPN_REMOTE_SERVER}:1337/addKey")
}
function pia_get_wireguard_config() {
pia_wireguard_peer_ip=$(echo "${pia_wireguard_authentication_json}" | jq -r '.peer_ip')
pia_wireguard_server_key=$(echo "$pia_wireguard_authentication_json" | jq -r '.server_key')
# commented line below is legacy method for getting server port, now moved to init.sh,
# but keeping the below line in case we need to switch to the previous method
#pia_wireguard_server_port=$(echo "$pia_wireguard_authentication_json" | jq -r '.server_port')
# this is the gateway ip for wireguard, this is required in getvpnport.sh, which is called
# as part of the wireguardup.sh.
export vpn_gateway_ip=$(echo "$pia_wireguard_authentication_json" | jq -r '.server_vip')
if [[ "${DEBUG}" == "true" ]]; then
echo "[debug] PIA WireGuard 'peer ip' is '${pia_wireguard_peer_ip}'"
echo "[debug] PIA WireGuard 'server key' is '${pia_wireguard_server_key}'"
echo "[debug] PIA WireGuard 'server vip' (gsteway) is '${vpn_gateway_ip}'"
fi
}
function pia_create_wireguard_config_file() {
# get pia wireguard server ip address for hostname using hosts
# file lookup (hosts file entry created in start.sh)
#pia_wireguard_server_ip=$(getent hosts "${VPN_REMOTE_SERVER}" | awk '{ print $1 }')
cat <<EOF > "${VPN_CONFIG}"
[Interface]
Address = ${pia_wireguard_peer_ip}
PrivateKey = ${wireguard_private_key}
PostUp = '/root/wireguardup.sh'
PostDown = '/root/wireguarddown.sh'
[Peer]
PublicKey = ${pia_wireguard_server_key}
AllowedIPs = 0.0.0.0/0
Endpoint = ${VPN_REMOTE_SERVER}:${VPN_REMOTE_PORT}
EOF
}
function watchdog() {
# loop and watch out for files generated by user nobody scripts that indicate failure
while true; do
# if '/tmp/portclosed' file exists (generated by /home/nobody/watchdog.sh when incoming port
# detected as closed) then down wireguard
if [ -f "/tmp/portclosed" ];then
echo "[info] Sending 'down' command to WireGuard due to port closed..."
down_wireguard
rm -f "/tmp/portclosed"
# if '/tmp/dnsfailure' file exists (generated by /home/nobody/checkdns.sh when dns fails)
# then down wireguard
elif [ -f "/tmp/dnsfailure" ];then
echo "[info] Sending 'down' command to WireGuard due to dns failure..."
down_wireguard
rm -f "/tmp/dnsfailure"
# if '/tmp/portfailure' file exists (generated by /root/getvpnport.sh when incoming port
# allocation fails) then down wireguard
elif [ -f "/tmp/portfailure" ];then
echo "[info] Sending 'down' command to WireGuard due to incoming port allocation failure..."
down_wireguard
rm -f "/tmp/portfailure"
fi
# check if wireguard 'peer' exists, if not assume wireguard connection is down and bring up
wg show | grep --quiet 'peer'
if [ "${?}" -ne 0 ]; then
# run wireguard, will run as daemon background process
up_wireguard
fi
sleep 30s
done
}
function edit_wireguard() {
# delete any existing PostUp/PostDown scripts (cannot easily edit and replace lines without insertion)
sed -i -r '/.*PostUp = .*|.*PostDown = .*/d' "${VPN_CONFIG}"
# insert PostUp/PostDown script lines after [Interface]
sed -i -e "/\[Interface\]/a PostUp = '/root/wireguardup.sh'\nPostDown = '/root/wireguarddown.sh'" "${VPN_CONFIG}"
# removes all ipv6 address and port from wireguard config
sed -r -i -e 's/,?[a-z0-9]{4}::?[^,]+,?//g' "${VPN_CONFIG}"
# removes all ipv6 port only from wireguard config
sed -r -i -e 's/,?::[^,]+,?//g' "${VPN_CONFIG}"
}
function up_wireguard() {
echo "[info] Attempting to bring WireGuard interface 'up'..."
wg-quick up "${VPN_CONFIG}"
if [ "${?}" -eq 0 ]; then
echo "[info] WireGuard interface 'up'"
else
echo "[warn] WireGuard interface failed to come 'up', exit code is '${?}'"
fi
}
function down_wireguard() {
echo "[info] Attempting to bring WireGuard interface 'down'..."
wg-quick down "${VPN_CONFIG}"
echo "[info] WireGuard interface stopped"
}
function start_wireguard() {
# if vpn provider is pia then get required dynamic configuration and write to wireguard config file
if [[ "${VPN_PROV}" == "pia" ]]; then
pia_create_wireguard_keys
pia_generate_token
pia_wireguard_authenticate
pia_get_wireguard_config
pia_create_wireguard_config_file
else
# edit wireguard config to remove ipv6, required for mullvad and possibly other non pia
# vpn providers
edit_wireguard
fi
# setup ip tables and routing for application
source /root/iptable.sh
# start watchdog function
watchdog
}
start_wireguard
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment