diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..21c28ac --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +ca.rsa.4096.crt +.ca.rsa.4096.crt diff --git a/README.md b/README.md index aae24d1..6460112 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # piawg -POSIX shell script for OPNsense that manages Private Internet Access next-gen +Shell script for OPNsense that manages Private Internet Access next-gen WireGuard tunnels using credentials and configuration stored in OpenBao. Licensed under 0BSD. diff --git a/piawg.sh b/piawg.sh index d997c8b..5b4c051 100755 --- a/piawg.sh +++ b/piawg.sh @@ -2,6 +2,9 @@ # SPDX-License-Identifier: 0BSD # SPDX-FileCopyrightText: 2026 Kris Lamoureux +# Allow local variable scoping, therefore not strictly POSIX +# shellcheck disable=SC3043 + err() { printf '[ERROR]: %s\n' "$1" >&2 exit 1 @@ -14,6 +17,11 @@ check_http() { esac } +# Check for plausible looking PIA token +check_token() { + printf '%s\n' "$1" | grep -q '^[0-9A-Fa-f]\{128\}$' +} + bao_curl() { curl -sS --connect-timeout 5 --max-time 20 --retry 5 --retry-delay 2 \ -H 'Content-Type: application/json' \ @@ -22,43 +30,51 @@ bao_curl() { "$@" } -# Fetch latest token in OpenBao +# Fetch latest PIA token in OpenBao get_token() { + local pia_token_reply if ! pia_token_reply=$(bao_curl \ "$BAO_ADDR/v1/$BAO_KV_MOUNT/data/$BAO_PATH_TOKEN"); then err "Failed to fetch PIA token from '$BAO_ADDR'" fi printf '%s' "$pia_token_reply" - unset pia_token_reply } # Get a new PIA token and store renew_token() { - login_response="$(bao_curl "$BAO_ADDR/v1/$BAO_KV_MOUNT/data/$BAO_PATH_LOGIN")" + local login_response + local update_response + local token_response + local http_code + local pia_user + local pia_pass + local pia_token + + login_response="$(bao_curl \ + "$BAO_ADDR/v1/$BAO_KV_MOUNT/data/$BAO_PATH_LOGIN" + )" http_code="$(printf '%s' "$login_response" | tail -1)" if ! check_http "$http_code"; then err "Failed to get PIA login details (HTTP $http_code)" fi - unset http_code login_response="$(printf '%s' "$login_response" | sed '$d')" pia_user="$(printf '%s' "$login_response" | jq -r '.data.data.username')" pia_pass="$(printf '%s' "$login_response" | jq -r '.data.data.password')" unset login_response - if ! token_reply="$(curl -s -X POST "$PIA_API" \ + if ! token_response="$(curl -s -X POST "$PIA_API" \ -F "username=$pia_user" \ -F "password=$pia_pass")"; then err "Failed to get a new PIA token" fi unset pia_pass unset pia_user - pia_token="$(echo "$token_reply" | jq -r .token)" - unset token_reply - if ! printf '%s' "$pia_token" | grep -Eq '^[0-9A-Fa-f]{128}$'; then - err "Invalid token found during renewal attempt" - fi - if ! update_response="$(bao_curl -X POST -d "$(jq -n --arg t "$pia_token" '{data:{token:$t}}')" \ + pia_token="$(echo "$token_response" | jq -r .token)" + unset token_response + check_token "$pia_token" || err "Invalid token found during renewal attempt" + if ! update_response="$(bao_curl -X POST -d \ + "$(jq -n --arg t "$pia_token" '{data:{token:$t}}')" \ "$BAO_ADDR/v1/$BAO_KV_MOUNT/data/$BAO_PATH_TOKEN")"; then err "Failed to save PIA token to '$BAO_ADDR'" fi @@ -70,7 +86,7 @@ renew_token() { } # Check for required external commands -for rbin in curl jq; do +for rbin in curl jq openssl; do command -v "$rbin" >/dev/null 2>&1 || err "Required binary '$rbin' not found" done @@ -100,6 +116,9 @@ fi # Overridable defaults : "${PIA_API:=https://www.privateinternetaccess.com/api/client/v2/token}" +: "${PIA_CRT:=https://www.privateinternetaccess.com/openvpn/ca.rsa.4096.crt}" +: "${PIA_HASH:=1fd25658456eab3041fba77ccd398ab8\ +124edcc1b8b2fc1d55fdf6b1bbfc9d70}" : "${BAO_AUTH_PATH:=approle}" : "${BAO_KV_MOUNT:=kv}" : "${BAO_PATH_LOGIN:=piawg/creds/login}" @@ -143,5 +162,17 @@ elif ! check_http "$http_code"; then err "Failed to get PIA token from '$BAO_ADDR' (HTTP $http_code)" fi -printf '%s\n' "$get_token_reply" -exit 0 +pia_token="$(printf '%s' "$get_token_reply" | jq -r .data.data.token)" +check_token "$pia_token" || err "Failed to get valid PIA token" +printf "[debug] %s: '%s'\n" "pia_token" "$pia_token" + +# Download PIA RSA CA certificate +if [ ! -f ./ca.rsa.4096.crt ]; then + [ -f ./.ca.rsa.4096.crt ] && rm ./.ca.rsa.4096.crt + curl -sS --connect-timeout 5 --max-time 20 --retry 5 --retry-delay 2 \ + -o ./.ca.rsa.4096.crt "$PIA_CRT" + pia_file_hash="$(openssl x509 -in ./.ca.rsa.4096.crt -outform DER | + openssl dgst -sha256 -r | awk '{print $1}')" + [ "$pia_file_hash" != "$PIA_HASH" ] && err "PIA CA fingerprint mismatch" + mv ./.ca.rsa.4096.crt ./ca.rsa.4096.crt +fi