-
Notifications
You must be signed in to change notification settings - Fork 46
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #27 from jamesmcm/azirevpn
Add support for AzireVPN
- Loading branch information
Showing
11 changed files
with
306 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
/target | ||
Cargo.lock | ||
dialoguer |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
[package] | ||
name = "vopono" | ||
description = "Launch applications via VPN tunnels using temporary network namespaces" | ||
version = "0.4.0" | ||
version = "0.4.1" | ||
authors = ["James McMurray <[email protected]>"] | ||
edition = "2018" | ||
license = "GPL-3.0-or-later" | ||
|
@@ -19,7 +19,7 @@ pretty_env_logger = "0.4" | |
clap = "2" | ||
which = "4" | ||
users = "0.10" | ||
nix = "0.17" | ||
nix = "0.18" | ||
serde = {version = "1", features = ["derive", "std"]} | ||
csv = "1" | ||
dialoguer = "0.6" | ||
|
@@ -32,11 +32,11 @@ chrono = "0.4" | |
compound_duration = "1" | ||
ipnet = {version = "2", features = ["serde"]} | ||
reqwest = {default-features = false, version = "0.10", features = ["blocking", "json", "rustls-tls"]} | ||
sysinfo = "0.14" | ||
base64 = "0.12" | ||
x25519-dalek = "0.6" | ||
strum = "0.18" | ||
strum_macros = "0.18" | ||
sysinfo = "0.15" | ||
base64 = "0.13" | ||
x25519-dalek = "1" | ||
strum = "0.19" | ||
strum_macros = "0.19" | ||
zip = "0.5" | ||
maplit = "1" | ||
webbrowser = "0.5" | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
mod openvpn; | ||
mod wireguard; | ||
|
||
use super::{ConfigurationChoice, OpenVpnProvider, Provider, WireguardProvider}; | ||
use crate::vpn::Protocol; | ||
use crate::wireguard::{de_socketaddr, de_vec_ipaddr, de_vec_ipnet}; | ||
use dialoguer::{Input, Password}; | ||
use ipnet::IpNet; | ||
use serde::Deserialize; | ||
use std::net::IpAddr; | ||
|
||
// AzireVPN details: https://www.azirevpn.com/docs/servers | ||
|
||
pub struct AzireVPN {} | ||
|
||
impl AzireVPN { | ||
fn server_aliases(&self) -> &[&str] { | ||
&[ | ||
"ca1", "dk1", "fr1", "de1", "it1", "es1", "nl1", "no1", "ro1", "se1", "se2", "ch1", | ||
"th1", "us1", "us2", "uk1", | ||
] | ||
} | ||
} | ||
impl Provider for AzireVPN { | ||
fn alias(&self) -> String { | ||
"azire".to_string() | ||
} | ||
fn default_protocol(&self) -> Protocol { | ||
Protocol::Wireguard | ||
} | ||
} | ||
|
||
#[derive(Deserialize, Debug, Clone)] | ||
struct ConnectResponse { | ||
status: String, | ||
data: WgResponse, | ||
} | ||
|
||
#[derive(Deserialize, Debug, Clone)] | ||
struct WgResponse { | ||
#[serde(alias = "DNS", deserialize_with = "de_vec_ipaddr")] | ||
dns: Vec<IpAddr>, | ||
#[serde(alias = "Address", deserialize_with = "de_vec_ipnet")] | ||
address: Vec<IpNet>, | ||
#[serde(alias = "PublicKey")] | ||
public_key: String, | ||
#[serde(alias = "Endpoint", deserialize_with = "de_socketaddr")] | ||
endpoint: std::net::SocketAddr, | ||
} | ||
|
||
impl AzireVPN { | ||
fn request_userpass(&self) -> anyhow::Result<(String, String)> { | ||
let username = Input::<String>::new() | ||
.with_prompt("AzireVPN username") | ||
.interact()?; | ||
let username = username.trim(); | ||
let password = Password::new() | ||
.with_prompt("AzireVPN password") | ||
.interact()?; | ||
let password = password.trim(); | ||
Ok((username.to_string(), password.to_string())) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
use super::AzireVPN; | ||
use super::{ConfigurationChoice, OpenVpnProvider}; | ||
use crate::util::delete_all_files_in_dir; | ||
use crate::vpn::OpenVpnProtocol; | ||
use log::{debug, info}; | ||
use std::fs::create_dir_all; | ||
use std::fs::File; | ||
use std::io::Write; | ||
use std::net::{IpAddr, Ipv4Addr}; | ||
use std::path::PathBuf; | ||
|
||
impl OpenVpnProvider for AzireVPN { | ||
// AzireVPN details: https://www.azirevpn.com/docs/servers | ||
// TODO: Add IPv6 DNS | ||
fn provider_dns(&self) -> Option<Vec<IpAddr>> { | ||
Some(vec![ | ||
IpAddr::V4(Ipv4Addr::new(91, 231, 153, 2)), | ||
IpAddr::V4(Ipv4Addr::new(192, 211, 0, 2)), | ||
]) | ||
} | ||
|
||
fn prompt_for_auth(&self) -> anyhow::Result<(String, String)> { | ||
self.request_userpass() | ||
} | ||
|
||
fn auth_file_path(&self) -> anyhow::Result<PathBuf> { | ||
Ok(self.openvpn_dir()?.join("auth.txt")) | ||
} | ||
|
||
fn create_openvpn_config(&self) -> anyhow::Result<()> { | ||
let protocol = OpenVpnProtocol::choose_one()?; | ||
// TODO: Allow port selection, TLS version selection | ||
let openvpn_dir = self.openvpn_dir()?; | ||
let country_map = crate::util::country_map::code_to_country_map(); | ||
create_dir_all(&openvpn_dir)?; | ||
delete_all_files_in_dir(&openvpn_dir)?; | ||
for alias in self.server_aliases() { | ||
let url = format!("https://www.azirevpn.com/cfg/openvpn/generate?country={}&os=linux-cli&nat=1&port=random&protocol={}&tls=gcm&keys=0", alias, protocol); | ||
let file = reqwest::blocking::get(&url)?.bytes()?; | ||
|
||
let file_contents = std::str::from_utf8(&file)?; | ||
let file_contents = file_contents | ||
.split('\n') | ||
.filter(|&x| !(x.starts_with("up ") || x.starts_with("down "))) | ||
.collect::<Vec<&str>>() | ||
.join("\n"); | ||
|
||
let country = country_map | ||
.get(&alias[0..2]) | ||
.expect("Could not map country to name"); | ||
let filename = format!("{}-{}.ovpn", country, alias); | ||
debug!("Writing file: {}", filename); | ||
let mut outfile = | ||
File::create(openvpn_dir.join(filename.to_lowercase().replace(' ', "_")))?; | ||
write!(outfile, "{}", file_contents)?; | ||
} | ||
|
||
// Write OpenVPN credentials file | ||
let (user, pass) = self.prompt_for_auth()?; | ||
let mut outfile = File::create(self.auth_file_path()?)?; | ||
write!(outfile, "{}\n{}", user, pass)?; | ||
info!( | ||
"AzireVPN OpenVPN config written to {}", | ||
openvpn_dir.display() | ||
); | ||
Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
use super::AzireVPN; | ||
use super::{ConnectResponse, WgResponse, WireguardProvider}; | ||
use crate::util::country_map::code_to_country_map; | ||
use crate::util::delete_all_files_in_dir; | ||
use crate::util::wireguard::{generate_keypair, WgKey}; | ||
use crate::wireguard::{WireguardConfig, WireguardInterface, WireguardPeer}; | ||
use ipnet::IpNet; | ||
use log::{debug, info}; | ||
use regex::Regex; | ||
use reqwest::blocking::Client; | ||
use std::fs::create_dir_all; | ||
use std::io::Write; | ||
use std::str::FromStr; | ||
|
||
impl WireguardProvider for AzireVPN { | ||
fn create_wireguard_config(&self) -> anyhow::Result<()> { | ||
let wireguard_dir = self.wireguard_dir()?; | ||
create_dir_all(&wireguard_dir)?; | ||
delete_all_files_in_dir(&wireguard_dir)?; | ||
|
||
let client = Client::new(); | ||
|
||
// TODO: Hardcoded list, can this be retrieved from the API? | ||
let aliases = self.server_aliases(); | ||
let country_map = code_to_country_map(); | ||
let (username, password) = self.request_userpass()?; | ||
let keypair: WgKey = generate_keypair()?; | ||
debug!("Chosen keypair: {:?}", keypair); | ||
|
||
let mut peers: Vec<(String, WgResponse)> = vec![]; | ||
for alias in aliases { | ||
let response = client | ||
.post(reqwest::Url::parse(&format!( | ||
"https://api.azirevpn.com/v1/wireguard/connect/{}", | ||
alias | ||
))?) | ||
.form(&[ | ||
("username", &username), | ||
("password", &password), | ||
("pubkey", &keypair.public), | ||
]) | ||
.send()?; | ||
debug!("Response: {:?}", response); | ||
let response: ConnectResponse = response.json()?; | ||
|
||
peers.push((alias.to_string(), response.data)); | ||
} | ||
|
||
// TODO: Allow custom port - need to check AzireVPN's restrictions | ||
// let port = 51820; | ||
|
||
let allowed_ips = vec![IpNet::from_str("0.0.0.0/0")?, IpNet::from_str("::0/0")?]; | ||
|
||
// TODO: avoid hacky regex for TOML -> wireguard config conversion | ||
let re = Regex::new(r"=\s\[(?P<value>[^\]]+)\]")?; | ||
for (alias, wg_peer) in peers { | ||
let interface = WireguardInterface { | ||
private_key: keypair.private.clone(), | ||
address: wg_peer.address, | ||
dns: wg_peer.dns, | ||
}; | ||
|
||
let wireguard_peer = WireguardPeer { | ||
public_key: wg_peer.public_key.clone(), | ||
allowed_ips: allowed_ips.clone(), | ||
endpoint: wg_peer.endpoint, | ||
}; | ||
|
||
let wireguard_conf = WireguardConfig { | ||
interface: interface.clone(), | ||
peer: wireguard_peer, | ||
}; | ||
|
||
let country = country_map | ||
.get(&alias[0..2]) | ||
.expect("Could not map country code"); | ||
|
||
let path = wireguard_dir.join(format!("{}-{}.conf", country, alias)); | ||
|
||
let mut toml = toml::to_string(&wireguard_conf)?; | ||
toml.retain(|c| c != '"'); | ||
let toml = toml.replace(", ", ","); | ||
let toml = re.replace_all(&toml, "= $value").to_string(); | ||
// Create file, write TOML | ||
{ | ||
let mut f = std::fs::File::create(path)?; | ||
write!(f, "{}", toml)?; | ||
} | ||
} | ||
|
||
info!( | ||
"AzireVPN Wireguard config written to {}", | ||
wireguard_dir.display() | ||
); | ||
|
||
Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.