#!/usr/bin/env sh # 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 } check_http() { case $1 in 2[0-9][0-9]) return 0 ;; *) return 1 ;; 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' \ -H "X-Vault-Token: $bao_token" \ -w '\n%{http_code}' \ "$@" } # 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" } # Get a new PIA token and store renew_token() { 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 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_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_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 unset pia_token http_code="$(printf '%s' "$update_response" | tail -1)" update_response="$(printf '%s' "$update_response" | sed '$d')" check_http "$http_code" || err "Failed to write PIA token to OpenBao (HTTP $http_code)" } # Check for required external commands for rbin in curl jq openssl; do command -v "$rbin" >/dev/null 2>&1 || err "Required binary '$rbin' not found" done # Setup config structure : "${PIAWG_CONF_DIR:=$HOME/.config/piawg}" : "${PIAWG_CONF:=$PIAWG_CONF_DIR/config}" [ -d "$HOME/.config" ] || install -d -m 0755 "$HOME/.config" [ -d "$PIAWG_CONF_DIR" ] || install -d -m 0700 "$PIAWG_CONF_DIR" if [ ! -f "$PIAWG_CONF" ]; then install -m 0600 /dev/null "$PIAWG_CONF" cat <<-'EOF' >"$PIAWG_CONF" # piawg configuration #BAO_ADDR= #BAO_ROLE= #BAO_SECRET= EOF fi # Source config if [ -r "$PIAWG_CONF" ]; then # shellcheck source=/dev/null . "$PIAWG_CONF" else err "Can't find config at '$PIAWG_CONF'" 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}" : "${BAO_PATH_TOKEN:=piawg/session/token}" # Must set these in PIAWG_CONF : "${BAO_ADDR:?[ERROR]: BAO_ADDR is not set}" : "${BAO_ROLE:?[ERROR]: BAO_ROLE is not set}" : "${BAO_SECRET:?[ERROR]: BAO_SECRET is not set}" # Get ephemeral session token from AppRole login if ! bao_token_reply=$(curl -sS \ --connect-timeout 5 \ --max-time 20 \ --retry 5 \ --retry-delay 2 \ -H 'Content-Type: application/json' \ -d "{\"role_id\":\"$BAO_ROLE\",\"secret_id\":\"$BAO_SECRET\"}" \ "$BAO_ADDR/v1/auth/$BAO_AUTH_PATH/login"); then err "Failed to login to '$BAO_ADDR'" fi bao_token=$(printf '%s' "$bao_token_reply" | jq -er '.auth.client_token') unset bao_token_reply [ -n "$bao_token" ] || err "Failed to get token from '$BAO_ADDR'" # Get latest PIA token get_token_reply="$(get_token)" http_code="$(printf '%s' "$get_token_reply" | tail -1)" get_token_reply="$(printf '%s' "$get_token_reply" | sed '$d')" # Renew token if path doesn't exist yet if [ "$http_code" -eq 404 ]; then renew_token get_token_reply="$(get_token)" http_code="$(printf '%s' "$get_token_reply" | tail -1)" get_token_reply="$(printf '%s' "$get_token_reply" | sed '$d')" if ! check_http "$http_code"; then err "Failed to get PIA token after renewal" fi elif ! check_http "$http_code"; then err "Failed to get PIA token from '$BAO_ADDR' (HTTP $http_code)" fi 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