Skip to content

Commit bcf5885

Browse files
PIA Dedicated IP Support
1 parent 762914c commit bcf5885

File tree

3 files changed

+125
-37
lines changed

3 files changed

+125
-37
lines changed

PIAWireguard.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
"piaUsername": "",
88
"piaPassword": "",
99
"piaRegionId": "uk",
10+
"piaDipToken": "",
1011
"piaPortForward": false,
12+
"piaUseDip": false,
1113
"tunnelGateway": null
1214
}

PIAWireguard.py

Lines changed: 117 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -58,12 +58,14 @@
5858
"piaUsername",
5959
"piaPassword",
6060
"piaRegionId",
61+
"piaDipToken",
6162
"piaPortForward",
63+
"piaUseDip",
6264
"tunnelGateway"
6365
]
6466

6567
# Check config contains the right settings
66-
if requiredConfig != list(config.keys()):
68+
if requiredConfig.sort() != list(config.keys()).sort():
6769
print("Your config json is missing some settings, please check it against the repo.")
6870
sys.exit(1)
6971

@@ -83,10 +85,16 @@
8385
opnsenseRouteUUID = ""
8486

8587
piaServerList = 'https://serverlist.piaservers.net/vpninfo/servers/v4'
88+
piaTokenApi = 'https://www.privateinternetaccess.com/api/client/v2/token'
89+
piaDedicatedIpApi = 'https://www.privateinternetaccess.com/api/client/v2/dedicated_ip'
8690
piaToken = ''
8791
piaCA = os.path.join(sys.path[0], "ca.rsa.4096.crt")
8892
piaPort = ''
8993
piaPortSignature = ''
94+
piaMetaCn = ''
95+
piaMetaIp = ''
96+
piaWgCn = ''
97+
piaWgIp = ''
9098
urlVerify = False # As we're connecting via local loopback I guess we don't really need to check the certificate. (I've noticed alot of people have the default self sigend anyway)
9199

92100
helpArg = False
@@ -196,6 +204,14 @@ def printDebug(text):
196204
print("Please define opnsenseURL variable with the correct value in the json file")
197205
sys.exit(0)
198206

207+
if config['piaUseDip'] == True and config['piaDipToken'] == '':
208+
print("If you wish to use PIA Dedicated IP, please supply DIP Token in piaDipToken")
209+
sys.exit(0)
210+
211+
if config['piaUseDip'] != True and config['piaUseDip'] != False:
212+
print("piaUseDip can only be true or false")
213+
sys.exit(0)
214+
199215
opnsenseURL = config['opnsenseURL']
200216

201217
# List current wireguard instances looking for PIA one
@@ -378,23 +394,76 @@ def printDebug(text):
378394
sys.exit(2)
379395
serverList = json.loads(r.text.split('\n')[0])
380396

381-
# Look for a pia server in the region we want.
382-
# PIA API will give us one server per region, PIA will try give us the best one
383-
wantedRegion = None
384-
for region in serverList['regions']:
385-
if region['id'] == config['piaRegionId']:
386-
wantedRegion = region
397+
if config['piaUseDip']:
398+
createObject = {
399+
"username": config['piaUsername'],
400+
"password": config['piaPassword']
401+
}
402+
headers = {'content-type': 'application/json'}
403+
generateTokenResponse = requests.post(piaTokenApi, data=json.dumps(createObject), headers=headers)
404+
if generateTokenResponse.status_code != 200:
405+
print("wireguardserver /v2/token request failed non 200 status code - Trying to get PIA token")
406+
sys.exit(2)
407+
piaToken = json.loads(generateTokenResponse.text)['token']
387408

388-
# couldn't find region, make sure the piaRegionId is set correctly
389-
if wantedRegion is None:
390-
print("region not found, correct config['piaRegionId'] set?")
391-
sys.exit(2)
409+
printDebug("Your PIA Token (Global), DO NOT GIVE THIS TO ANYONE")
410+
printDebug(generateTokenResponse.text)
411+
412+
piaAuthHeaders = {
413+
"Authorization": f"Token {piaToken}",
414+
"content-type": "application/json"
415+
}
416+
piaDip = {
417+
"tokens": [config['piaDipToken']]
418+
}
419+
dipDetailsResponse = requests.post(piaDedicatedIpApi, data=json.dumps(piaDip),headers=piaAuthHeaders)
420+
if dipDetailsResponse.status_code != 200:
421+
print("wireguardserver /v2/dedicated_ip request failed non 200 status code - Trying to get PIA DIP details")
422+
sys.exit(2)
423+
dipDetails = json.loads(dipDetailsResponse.text)[0]
424+
printDebug("DIP Details")
425+
printDebug(dipDetails)
426+
427+
if dipDetails['status'] != "active":
428+
print("PIA DIP isn't active")
429+
sys.exit(2)
430+
431+
piaWgCn = dipDetails['cn']
432+
piaWgIp = dipDetails['ip']
433+
434+
435+
# The DIP will belong to a region, so we need to find current region's meta server from the global server list.
436+
for region in serverList['regions']:
437+
if region['id'] == dipDetails['id']:
438+
piaMetaCn = region['servers']['meta'][0]['cn']
439+
piaMetaIp = region['servers']['meta'][0]['ip']
440+
441+
# couldn't find region, make sure the piaRegionId is set correctly
442+
if piaMetaCn == '':
443+
print("region not found, for DIP, is there an issue with the DIP?")
444+
sys.exit(2)
445+
else:
446+
# Look for a pia server in the region we want.
447+
# PIA API will give us one server per region, PIA will try give us the best one
448+
for region in serverList['regions']:
449+
if region['id'] == config['piaRegionId']:
450+
piaMetaCn = region['servers']['meta'][0]['cn']
451+
piaMetaIp = region['servers']['meta'][0]['ip']
452+
piaWgCn = region['servers']['wg'][0]['cn']
453+
piaWgIp = region['servers']['wg'][0]['ip']
454+
455+
# couldn't find region, make sure the piaRegionId is set correctly
456+
if piaMetaCn == '':
457+
print("region not found, correct config['piaRegionId'] set?")
458+
sys.exit(2)
392459

393460
# print some useful debug information about what servers
394461
printDebug("metaServer")
395-
printDebug(wantedRegion['servers']['meta'])
462+
printDebug(piaMetaCn)
463+
printDebug(piaMetaIp)
396464
printDebug("wgServer")
397-
printDebug(wantedRegion['servers']['wg'])
465+
printDebug(piaWgCn)
466+
printDebug(piaWgIp)
398467

399468
# If tunnelGateway is configured we need to add the route, to force the PIA wg tunnel over the wanted WAN
400469
if config['tunnelGateway'] is not None:
@@ -417,7 +486,7 @@ def printDebug(text):
417486
createObject = {
418487
"route": {
419488
"disabled": '0',
420-
"network": wantedRegion['servers']['wg'][0]['ip'] + '/32',
489+
"network": piaWgIp + '/32',
421490
"gateway": config['tunnelGateway'],
422491
"descr": opnsenseWGPeerName
423492
}
@@ -443,10 +512,10 @@ def printDebug(text):
443512
printDebug(f"Current Tunnel Gateway: {str(currentGateway)}")
444513
printDebug(f"Required Tunnel Gateway: {str(config['tunnelGateway'])}")
445514
printDebug(f"Current Routed IP: {str(currentRoutedIP)}")
446-
printDebug(f"Required Routed IP: {str(wantedRegion['servers']['wg'][0]['ip']+'/32')}")
447-
if currentGateway is not config['tunnelGateway'] or currentRoutedIP is not wantedRegion['servers']['wg'][0]['ip']:
515+
printDebug(f"Required Routed IP: {str(piaWgIp+'/32')}")
516+
if currentGateway is not config['tunnelGateway'] or currentRoutedIP is not piaWgIp:
448517
printDebug("Route update required")
449-
currentRoute['route']['network'] = wantedRegion['servers']['wg'][0]['ip']+'/32'
518+
currentRoute['route']['network'] = piaWgIp+'/32'
450519
currentRoute['route']['gateway'] = config['tunnelGateway']
451520
currentRoute['route']['disabled'] = 0
452521

@@ -469,28 +538,39 @@ def printDebug(text):
469538
sys.exit(2)
470539
printDebug(f"PIA tunnel ip now set to route over WAN gateway {config['tunnelGateway']} via static route")
471540

472-
# Get token from wanted region server - Tokens lasts 24 hours, so we can make our requests for a WG connection information and port is required
473-
# because PIA use custom certs which just have a SAN of their name eg london401, we have to put a temporary dns override in, to make it so london401 points to the meta IP
474-
override_dns(wantedRegion['servers']['meta'][0]['cn'], wantedRegion['servers']['meta'][0]['ip'])
475-
generateTokenResponse = requests.get(f"https://{wantedRegion['servers']['meta'][0]['cn']}/authv3/generateToken", auth=(config['piaUsername'], config['piaPassword']), verify=piaCA)
476-
if generateTokenResponse.status_code != 200:
477-
print("wireguardserver generateToken request failed non 200 status code - Trying to get PIA token")
478-
sys.exit(2)
479-
piaToken = json.loads(generateTokenResponse.text)['token']
480-
481-
createObject = {
482-
"pt": piaToken,
483-
"pubkey": opnsenseWGPubkey
484-
}
541+
# Get PIA token from meta server for non DIP Servers
542+
if config['piaUseDip'] == False:
543+
# Get PIA token from wanted region server - Tokens lasts 24 hours, so we can make our requests for a WG connection information and port is required
544+
# because PIA use custom certs which just have a SAN of their name eg london401, we have to put a temporary dns override in, to make it so london401 points to the meta IP
545+
override_dns(piaMetaCn, piaMetaIp)
546+
generateTokenResponse = requests.get(f"https://{piaMetaCn}/authv3/generateToken", auth=(config['piaUsername'], config['piaPassword']), verify=piaCA)
547+
if generateTokenResponse.status_code != 200:
548+
print("wireguardserver generateToken request failed non 200 status code - Trying to get PIA token")
549+
sys.exit(2)
550+
piaToken = json.loads(generateTokenResponse.text)['token']
485551

486-
printDebug("Your PIA Token, DO NOT GIVE THIS TO ANYONE")
487-
printDebug(generateTokenResponse.text)
552+
printDebug("Your PIA Token (Meta), DO NOT GIVE THIS TO ANYONE")
553+
printDebug(generateTokenResponse.text)
488554

489-
# Now we have our PIA token, we can now request our WG connection information
555+
# Now we have our PIA details, we can now request our WG connection information
490556
# because PIA use custom certs which just have a SAN of their name eg london401, we have to put a temporary dns override in, to make it so london401 points to the wg IP
491-
override_dns(wantedRegion['servers']['wg'][0]['cn'], wantedRegion['servers']['wg'][0]['ip'])
557+
override_dns(piaWgCn, piaWgIp)
492558
# Get PIA wireguard server connection information
493-
wireguardResponse = requests.get(f"https://{wantedRegion['servers']['wg'][0]['cn']}:1337/addKey", params=createObject, verify=piaCA)
559+
560+
# If we're using a DIP we need to authenicate using DIP token, otherwise used the PIA Token
561+
wireguardResponse = None
562+
if config['piaUseDip']:
563+
createObject = {
564+
"pubkey": opnsenseWGPubkey
565+
}
566+
wireguardResponse = requests.get(f"https://{piaWgCn}:1337/addKey", params=createObject, auth=(f"dedicated_ip_{config['piaDipToken']}",piaWgIp), verify=piaCA)
567+
else:
568+
createObject = {
569+
"pt": piaToken,
570+
"pubkey": opnsenseWGPubkey
571+
}
572+
wireguardResponse = requests.get(f"https://{piaWgCn}:1337/addKey", params=createObject, verify=piaCA)
573+
494574
if wireguardResponse.status_code != 200:
495575
print("wireguardserver addKey request failed non 200 status code - Trying to add instance public key to server in exchnage for connection information")
496576
sys.exit(2)
@@ -501,8 +581,8 @@ def printDebug(text):
501581

502582
# Write wireguard connection information to file, for later use.
503583
# we need to add server name as well
504-
wireguardServerInfo['server_name'] = wantedRegion['servers']['wg'][0]['cn']
505-
wireguardServerInfo['servermeta_ip'] = wantedRegion['servers']['meta'][0]['ip']
584+
wireguardServerInfo['server_name'] = piaWgCn
585+
wireguardServerInfo['servermeta_ip'] = piaMetaIp
506586
wireguardServerInfoFile = f"/tmp/wg{opnsenseWGInstance}_piaserverinfo"
507587
with open(wireguardServerInfoFile, 'w') as filetowrite:
508588
filetowrite.write(json.dumps(wireguardServerInfo))

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,12 @@ https://opnsense.lan/wg0_port.txt
9494

9595
Note: Not all server locations support port forwarding.
9696

97+
***Dedicated IP***
98+
99+
If you have purchased a Dedicated IP from PIA. Add your DIP token to `piaDipToken` in the json file, then to enable the usage simply set `piaUseDip` to `true`. Remember PIA only give you the DIP token once, so make sure you have backed up the token somewhere.
100+
101+
I have developed this functionality by reserve engineering the PIA client, at this moment in time manual connections for DIP is not offically supported by PIA.
102+
97103
***Set outgoing tunnel gateway (outgoing interface)***
98104

99105
In some deployments, people may be running dual or even triple WAN configurations, in this case due to how WireGuard is configured in FreeBSD (OPNsense), it'll route the PIA tunnel over the default WAN interface. Some people will want to change this to use another WAN interface as the gateway to route the PIA tunnel over.

0 commit comments

Comments
 (0)