mirror of https://github.com/wg-easy/wg-easy
16 changed files with 574 additions and 593 deletions
|
Before Width: | Height: | Size: 105 KiB |
Binary file not shown.
@ -0,0 +1,13 @@ |
|||||
|
# Note: Do not edit this file directly. |
||||
|
# Your changes will be overwritten! |
||||
|
|
||||
|
# Server |
||||
|
[Interface] |
||||
|
PrivateKey = aCjYU+CBAkIerzXXFj8hzc4ZmMDLNGrhsxqJjOwtPH0= |
||||
|
Address = 10.8.0.1/24 |
||||
|
ListenPort = 51820 |
||||
|
PreUp = |
||||
|
PostUp = iptables -t nat -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE; iptables -A INPUT -p udp -m udp --dport 51820 -j ACCEPT; iptables -A FORWARD -i wg0 -j ACCEPT; iptables -A FORWARD -o wg0 -j ACCEPT; |
||||
|
PreDown = |
||||
|
PostDown = |
||||
|
|
||||
@ -0,0 +1,8 @@ |
|||||
|
{ |
||||
|
"server": { |
||||
|
"privateKey": "aCjYU+CBAkIerzXXFj8hzc4ZmMDLNGrhsxqJjOwtPH0=", |
||||
|
"publicKey": "Gi9QI2VgCJK1ziPaE2gE/jXbiXj+Rx3mUbIq8RcHPEE=", |
||||
|
"address": "10.8.0.1" |
||||
|
}, |
||||
|
"clients": {} |
||||
|
} |
||||
@ -0,0 +1,2 @@ |
|||||
|
pub mod wireguard; |
||||
|
pub mod config; |
||||
@ -0,0 +1,231 @@ |
|||||
|
use std::{collections::HashMap, sync::Arc}; |
||||
|
|
||||
|
use chrono::{SecondsFormat, SubsecRound, Utc}; |
||||
|
|
||||
|
use futures_util::TryFutureExt; |
||||
|
use serde::{Deserialize, Serialize}; |
||||
|
|
||||
|
use tokio::{io::AsyncWriteExt, join}; |
||||
|
use uuid::Uuid; |
||||
|
|
||||
|
use crate::utils::{misc, os}; |
||||
|
|
||||
|
#[derive(Serialize, Deserialize, Clone)] |
||||
|
pub struct Settings { |
||||
|
pub interface_name: String, |
||||
|
pub release: String, |
||||
|
pub api_port: i32, |
||||
|
pub password: String, |
||||
|
pub host: String, |
||||
|
pub wg_port: i32, |
||||
|
pub mtu: i32, |
||||
|
pub persistent_keepalive: i32, |
||||
|
pub default_address: String, |
||||
|
pub default_address_base: String, |
||||
|
pub default_dns: String, |
||||
|
pub allowed_ips: String, |
||||
|
pub pre_up: String, |
||||
|
pub post_up: String, |
||||
|
pub pre_down: String, |
||||
|
pub post_down: String, |
||||
|
} |
||||
|
|
||||
|
#[derive(Serialize, Deserialize, Clone)] |
||||
|
#[serde(rename_all(serialize = "camelCase"))] |
||||
|
#[serde(rename_all(deserialize = "camelCase"))] |
||||
|
pub struct Server { |
||||
|
pub private_key: String, |
||||
|
pub public_key: String, |
||||
|
pub address: String, |
||||
|
} |
||||
|
|
||||
|
#[derive(Serialize, Deserialize, Clone)] |
||||
|
#[serde(rename_all(serialize = "camelCase"))] |
||||
|
#[serde(rename_all(deserialize = "camelCase"))] |
||||
|
pub struct Client { |
||||
|
pub uuid: Uuid, |
||||
|
pub name: String, |
||||
|
pub address: String, |
||||
|
pub private_key: String, |
||||
|
pub public_key: String, |
||||
|
pub pre_shared_key: String, |
||||
|
pub created_at: String, |
||||
|
pub updated_at: String, |
||||
|
pub enabled: bool, |
||||
|
} |
||||
|
|
||||
|
impl Client { |
||||
|
pub fn enable(&mut self) { |
||||
|
self.enabled = true; |
||||
|
self.updated_at = Utc::now() |
||||
|
.trunc_subsecs(3) |
||||
|
.to_rfc3339_opts(SecondsFormat::Millis, true) |
||||
|
} |
||||
|
pub fn disable(&mut self) { |
||||
|
self.enabled = false; |
||||
|
self.updated_at = Utc::now() |
||||
|
.trunc_subsecs(3) |
||||
|
.to_rfc3339_opts(SecondsFormat::Millis, true) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
#[derive(Serialize, Deserialize, Clone)] |
||||
|
#[serde(rename_all(serialize = "camelCase"))] |
||||
|
#[serde(rename_all(deserialize = "snake_case"))] |
||||
|
pub struct WebClient { |
||||
|
pub id: Uuid, |
||||
|
pub name: String, |
||||
|
pub enabled: bool, |
||||
|
pub address: String, |
||||
|
pub public_key: String, |
||||
|
pub created_at: String, |
||||
|
pub updated_at: String, |
||||
|
pub allowed_ips: String, |
||||
|
pub persistent_keepalive: String, |
||||
|
pub latest_handshake_at: String, |
||||
|
pub transfer_rx: i64, |
||||
|
pub transfer_tx: i64, |
||||
|
} |
||||
|
|
||||
|
#[derive(Serialize, Deserialize, Clone)] |
||||
|
#[serde(rename_all(serialize = "camelCase"))] |
||||
|
#[serde(rename_all(deserialize = "snake_case"))] |
||||
|
pub struct DumpClient { |
||||
|
pub public_key: String, |
||||
|
pub pre_shared_key: String, |
||||
|
pub endpoint: String, |
||||
|
pub allowed_ips: String, |
||||
|
pub latest_handshake_at: String, |
||||
|
pub transfer_rx: i64, |
||||
|
pub transfer_tx: i64, |
||||
|
pub persistent_keepalive: String, |
||||
|
} |
||||
|
|
||||
|
#[derive(Serialize, Deserialize, Clone)] |
||||
|
#[serde(rename_all(serialize = "camelCase"))] |
||||
|
#[serde(rename_all(deserialize = "camelCase"))] |
||||
|
pub struct Memento { |
||||
|
pub server: Server, |
||||
|
pub clients: HashMap<uuid::Uuid, Client>, |
||||
|
} |
||||
|
|
||||
|
impl Memento { |
||||
|
pub fn new(server: Server) -> Self { |
||||
|
Self { |
||||
|
server, |
||||
|
clients: HashMap::new(), |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
#[derive(Clone)] |
||||
|
pub struct Accessor { |
||||
|
memento_path: String, |
||||
|
config_path: String, |
||||
|
settings: Arc<Settings>, |
||||
|
memento: Memento, |
||||
|
} |
||||
|
|
||||
|
impl Accessor { |
||||
|
pub async fn new(memento_path: String, config_path: String, settings: Arc<Settings>) -> Self { |
||||
|
let memento_str = os::load_file(memento_path.clone()) |
||||
|
.and_then(|file| os::read_file(file)) |
||||
|
.await |
||||
|
.expect("Could not read memento"); |
||||
|
|
||||
|
let memento = if memento_str.is_empty() { |
||||
|
log::info!("Creating new WireGuard configuration..."); |
||||
|
let private_key = os::exec_sh("wg genkey").await.expect("error genkey").stdout; |
||||
|
let public_key = os::exec_sh(format!("echo {} | wg pubkey", private_key)) |
||||
|
.await |
||||
|
.expect("error pubkey") |
||||
|
.stdout; |
||||
|
|
||||
|
let address = settings.default_address.clone(); |
||||
|
let memento = Memento::new(Server { |
||||
|
private_key, |
||||
|
public_key, |
||||
|
address, |
||||
|
}); |
||||
|
log::info!("WireGuard configuration generated..."); |
||||
|
memento |
||||
|
} else { |
||||
|
serde_json::from_str(&memento_str).expect("Could not serialize memento") |
||||
|
}; |
||||
|
|
||||
|
Self { |
||||
|
config_path, |
||||
|
memento_path, |
||||
|
memento, |
||||
|
settings, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
pub async fn set(&mut self, memento: Memento) { |
||||
|
let mut ser_memento = |
||||
|
serde_json::to_string_pretty(&memento).expect("Could not serialize memento"); |
||||
|
let mut ser_config = self.format_config(&memento); |
||||
|
|
||||
|
ser_memento.push('\n'); |
||||
|
ser_config.push('\n'); |
||||
|
|
||||
|
_ = join!( |
||||
|
async { |
||||
|
let mut file = os::load_file(self.memento_path.clone()) |
||||
|
.await |
||||
|
.expect("Could not load memento"); |
||||
|
file.write(ser_memento.as_bytes()) |
||||
|
.await |
||||
|
.expect("Could not write memento"); |
||||
|
}, |
||||
|
async { |
||||
|
let mut file = os::load_file(self.config_path.clone()) |
||||
|
.await |
||||
|
.expect("Could not load config"); |
||||
|
file.write(ser_config.as_bytes()) |
||||
|
.await |
||||
|
.expect("Could not write config"); |
||||
|
} |
||||
|
); |
||||
|
|
||||
|
self.memento = memento; |
||||
|
} |
||||
|
|
||||
|
pub fn get(&self) -> Memento { |
||||
|
self.memento.clone() |
||||
|
} |
||||
|
|
||||
|
fn format_config(&self, config: &Memento) -> String { |
||||
|
let result_vec = vec![ |
||||
|
format!("# Note: Do not edit this file directly.\n"), |
||||
|
format!("# Your changes will be overwritten!\n"), |
||||
|
format!("\n"), |
||||
|
format!("# Server\n"), |
||||
|
format!("[Interface]\n"), |
||||
|
format!("PrivateKey = {}\n", config.server.private_key), |
||||
|
format!("Address = {}/24\n", config.server.address), |
||||
|
format!("ListenPort = 51820\n"), |
||||
|
format!("PreUp = {}\n", self.settings.pre_up), |
||||
|
format!("PostUp = {}\n", self.settings.post_up), |
||||
|
format!("PreDown = {}\n", self.settings.pre_down), |
||||
|
format!("PostDown = {}\n", self.settings.post_down), |
||||
|
]; |
||||
|
let mut result = misc::multi_line(&result_vec); |
||||
|
|
||||
|
for (uid, client) in &config.clients { |
||||
|
if !client.enabled { |
||||
|
continue; |
||||
|
} |
||||
|
let result_inner = vec![ |
||||
|
format!("\n"), |
||||
|
format!("# Client: {} ({})\n", client.name, uid), |
||||
|
format!("[Peer]\n"), |
||||
|
format!("PublicKey = {}\n", client.public_key), |
||||
|
format!("PresharedKey = {}\n", client.pre_shared_key), |
||||
|
format!("AllowedIPs = {}/32\n", client.address), |
||||
|
]; |
||||
|
result.push_str(misc::multi_line(&result_inner).as_str()); |
||||
|
} |
||||
|
result |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,250 @@ |
|||||
|
use std::sync::Arc; |
||||
|
|
||||
|
use chrono::{SecondsFormat, SubsecRound, Utc}; |
||||
|
|
||||
|
use uuid::Uuid; |
||||
|
|
||||
|
use crate::utils::{misc, os}; |
||||
|
|
||||
|
use super::config::{Accessor, Client, DumpClient, Settings, WebClient}; |
||||
|
|
||||
|
#[derive(Clone)] |
||||
|
pub struct WireGuard { |
||||
|
pub name: String, |
||||
|
settings: Arc<Settings>, |
||||
|
memento_accessor: Accessor, |
||||
|
} |
||||
|
|
||||
|
impl WireGuard { |
||||
|
pub async fn new(name: &str, wg_path: String, settings: Settings) -> Self { |
||||
|
let path = format!("{}{}", wg_path, name); |
||||
|
let config_path = format!("{}.conf", path); |
||||
|
let memento_path = format!("{}.json", path); |
||||
|
let name = format!("{}", name); |
||||
|
|
||||
|
let counter = Arc::new(settings); |
||||
|
|
||||
|
Self { |
||||
|
name, |
||||
|
settings: counter.clone(), |
||||
|
memento_accessor: Accessor::new(memento_path, config_path, counter).await, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
pub async fn quick_up(&self) { |
||||
|
let result = os::exec_sh(format!("wg-quick up {}", &self.name)) |
||||
|
.await |
||||
|
.expect("Error"); |
||||
|
result.log(); |
||||
|
} |
||||
|
|
||||
|
pub async fn quick_down(&self) { |
||||
|
let result = os::exec_sh(format!("wg-quick down {}", &self.name)) |
||||
|
.await |
||||
|
.expect("Error"); |
||||
|
result.log(); |
||||
|
} |
||||
|
|
||||
|
pub async fn sync_config(&self) { |
||||
|
let result = os::exec_sh(format!( |
||||
|
"wg syncconf {} <(wg-quick strip {})", |
||||
|
self.name, self.name |
||||
|
)) |
||||
|
.await |
||||
|
.expect("Error syncing"); |
||||
|
result.log(); |
||||
|
} |
||||
|
|
||||
|
pub async fn start(&mut self) { |
||||
|
let memento = self.memento_accessor.get(); |
||||
|
self.memento_accessor.set(memento).await; |
||||
|
self.quick_down().await; |
||||
|
self.quick_up().await; |
||||
|
self.sync_config().await; |
||||
|
} |
||||
|
|
||||
|
pub async fn create_client(&mut self, name: String) -> Client { |
||||
|
let private_key = os::exec_sh("wg genkey").await.expect("error genkey").stdout; |
||||
|
|
||||
|
let pub_cmd = format!("echo {} | wg pubkey", private_key); |
||||
|
let public_key = os::exec_sh(pub_cmd).await.expect("error pubkey").stdout; |
||||
|
|
||||
|
let pre_shared_key = os::exec_sh("wg genpsk").await.expect("error genpsk").stdout; |
||||
|
|
||||
|
let mut memento = self.memento_accessor.get(); |
||||
|
|
||||
|
let mut i: u8 = 2; |
||||
|
let address = loop { |
||||
|
match memento.clients.iter().find(|(_, client)| { |
||||
|
client.address == format!("{}.{}", self.settings.default_address_base, i) |
||||
|
}) { |
||||
|
Some(_) => { |
||||
|
if i == 255 { |
||||
|
break None; |
||||
|
} |
||||
|
i += 1; |
||||
|
} |
||||
|
None => break Some(format!("{}.{}", self.settings.default_address_base, i)), |
||||
|
} |
||||
|
} |
||||
|
.expect("Maximum number of clients reached"); |
||||
|
|
||||
|
let uuid = Uuid::new_v4(); |
||||
|
let now = Utc::now().trunc_subsecs(3); |
||||
|
|
||||
|
let client = Client { |
||||
|
uuid, |
||||
|
name, |
||||
|
address, |
||||
|
created_at: now.to_rfc3339_opts(SecondsFormat::Secs, true), |
||||
|
updated_at: now.to_rfc3339_opts(SecondsFormat::Secs, true), |
||||
|
enabled: true, |
||||
|
pre_shared_key, |
||||
|
private_key, |
||||
|
public_key, |
||||
|
}; |
||||
|
|
||||
|
memento.clients.insert(uuid, client.clone()); |
||||
|
|
||||
|
let client = memento.clients.get(&uuid).unwrap().clone(); |
||||
|
self.memento_accessor.set(memento).await; |
||||
|
self.sync_config().await; |
||||
|
client |
||||
|
} |
||||
|
|
||||
|
pub fn get_client(&self, client_id: Uuid) -> Client { |
||||
|
self.memento_accessor |
||||
|
.get() |
||||
|
.clients |
||||
|
.get(&client_id) |
||||
|
.expect("Could not find user") |
||||
|
.clone() |
||||
|
} |
||||
|
|
||||
|
pub async fn get_clients(&self) -> Vec<WebClient> { |
||||
|
let res = os::exec_sh("wg show wg0 dump").await.unwrap(); |
||||
|
|
||||
|
let lines: Vec<Vec<String>> = res |
||||
|
.stdout |
||||
|
.trim() |
||||
|
.split('\n') |
||||
|
.skip(1) |
||||
|
.map(|line| line.split('\t').map(|x| x.to_string()).collect()) |
||||
|
.collect(); |
||||
|
|
||||
|
let lines: Vec<DumpClient> = lines |
||||
|
.iter() |
||||
|
.map(|x| DumpClient { |
||||
|
public_key: x[0].to_string(), |
||||
|
pre_shared_key: x[1].to_string(), |
||||
|
endpoint: x[2].to_string(), |
||||
|
allowed_ips: x[3].to_string(), |
||||
|
latest_handshake_at: x[4].to_string(), |
||||
|
transfer_rx: x[5].parse::<i64>().unwrap(), |
||||
|
transfer_tx: x[6].parse::<i64>().unwrap(), |
||||
|
persistent_keepalive: x[7].to_string(), |
||||
|
}) |
||||
|
.collect(); |
||||
|
|
||||
|
let memento = self.memento_accessor.get(); |
||||
|
let clients: Vec<WebClient> = memento |
||||
|
.clients |
||||
|
.iter() |
||||
|
.map(|(uuid, value)| { |
||||
|
let uuid = uuid.to_owned(); |
||||
|
let value = value.to_owned(); |
||||
|
let client_conf; |
||||
|
|
||||
|
match lines.iter().find(|x| x.public_key.eq(&value.public_key)) { |
||||
|
Some(conf) => client_conf = conf, |
||||
|
None => return None, |
||||
|
}; |
||||
|
Some(WebClient { |
||||
|
id: uuid, |
||||
|
name: value.name, |
||||
|
enabled: value.enabled, |
||||
|
address: value.address, |
||||
|
public_key: value.public_key, |
||||
|
created_at: value.created_at, |
||||
|
updated_at: value.updated_at, |
||||
|
allowed_ips: client_conf.allowed_ips.to_string(), |
||||
|
persistent_keepalive: client_conf.persistent_keepalive.to_string(), |
||||
|
latest_handshake_at: client_conf.latest_handshake_at.to_string(), |
||||
|
transfer_rx: client_conf.transfer_rx, |
||||
|
transfer_tx: client_conf.transfer_tx, |
||||
|
}) |
||||
|
}) |
||||
|
.filter(|x| x.is_some()) |
||||
|
.map(|x| x.unwrap()) |
||||
|
.collect(); |
||||
|
|
||||
|
clients |
||||
|
} |
||||
|
|
||||
|
pub fn get_client_configuration(&self, client_id: Uuid) -> (String, String) { |
||||
|
let memento = self.memento_accessor.get(); |
||||
|
let client = self.get_client(client_id); |
||||
|
let result_vec = vec![ |
||||
|
format!("[Interface]\n"), |
||||
|
format!("PrivateKey = {}\n", client.private_key), |
||||
|
format!("Address = {}/24\n", client.address), |
||||
|
format!("DNS = {}\n", self.settings.default_dns), |
||||
|
format!("MTU = {}\n", self.settings.mtu), |
||||
|
format!("\n"), |
||||
|
format!("[Peer]\n"), |
||||
|
format!("PublicKey = {}\n", memento.server.public_key), |
||||
|
format!("PresharedKey = {}\n", client.pre_shared_key), |
||||
|
format!("AllowedIPs = {}\n", self.settings.allowed_ips), |
||||
|
format!( |
||||
|
"PersistentKeepalive = {}\n", |
||||
|
self.settings.persistent_keepalive |
||||
|
), |
||||
|
format!( |
||||
|
"Endpoint = {}:{}\n", |
||||
|
self.settings.host, self.settings.wg_port |
||||
|
), |
||||
|
]; |
||||
|
|
||||
|
(client.name.to_string(), misc::multi_line(&result_vec)) |
||||
|
} |
||||
|
|
||||
|
pub async fn delete_client(&mut self, client_id: Uuid) { |
||||
|
let mut memento = self.memento_accessor.get(); |
||||
|
if memento.clients.remove(&client_id).is_some() { |
||||
|
self.memento_accessor.set(memento).await; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
pub async fn enable_client(&mut self, client_id: Uuid) { |
||||
|
let mut memento = self.memento_accessor.get(); |
||||
|
match memento.clients.get_mut(&client_id) { |
||||
|
Some(client) => { |
||||
|
client.enable(); |
||||
|
self.memento_accessor.set(memento).await; |
||||
|
} |
||||
|
None => return, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
pub async fn disable_client(&mut self, client_id: Uuid) { |
||||
|
let mut memento = self.memento_accessor.get(); |
||||
|
match memento.clients.get_mut(&client_id) { |
||||
|
Some(client) => { |
||||
|
client.disable(); |
||||
|
self.memento_accessor.set(memento).await; |
||||
|
} |
||||
|
None => return, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
pub async fn get_client_qrcode_svg(&self, client_id: Uuid) -> String { |
||||
|
let (_, config) = self.get_client_configuration(client_id); |
||||
|
return qrcode_generator::to_svg_to_string_from_str( |
||||
|
config, |
||||
|
qrcode_generator::QrCodeEcc::Low, |
||||
|
512, |
||||
|
None::<&str>, |
||||
|
) |
||||
|
.unwrap(); |
||||
|
} |
||||
|
} |
||||
@ -1,2 +0,0 @@ |
|||||
pub mod commands; |
|
||||
pub mod config; |
|
||||
@ -1,108 +0,0 @@ |
|||||
// use log::info;
|
|
||||
|
|
||||
// use super::config::WireguardSettings;
|
|
||||
// use crate::{
|
|
||||
// utils::{os::{self}, misc},
|
|
||||
// wireguard::config::{self, WireguardMemento},
|
|
||||
// };
|
|
||||
|
|
||||
// // pub async fn wg_start(settings: WireguardSettings) {
|
|
||||
// // let (wg, quick) = get_config().await;
|
|
||||
// // sync_config().await;
|
|
||||
// // let config_str = format_config(wg, quick).await;
|
|
||||
// // os::write_to_path_unhandled("/etc/wireguard/wg0.conf", config_str).await;
|
|
||||
// // _ = os::exec_bash(format!("wg-quick down wg0")).await;
|
|
||||
// // _ = os::exec_bash(format!("wg-quick up wg0")).await;
|
|
||||
// // //todo Add error feedback if host kernel does not support wireguard
|
|
||||
// // }
|
|
||||
|
|
||||
// pub async fn get_config() -> (WireguardMemento, WireguardSettings) {
|
|
||||
// let quick = load_wireguard_settings("./quick_config.json").await;
|
|
||||
// log::info!("Loading configuration...");
|
|
||||
|
|
||||
// let wg_config_path = &mut quick.wg_path.to_owned();
|
|
||||
// wg_config_path.push_str("/wg0");
|
|
||||
|
|
||||
// let wg_config: WireguardMemento;
|
|
||||
// let (mut wg_config_file, wg_config_str) =
|
|
||||
// os::load_and_read_file_unhandled(wg_config_path).await;
|
|
||||
// if !wg_config_str.is_empty() {
|
|
||||
// wg_config = serde_json::from_str(wg_config_str.as_str()).unwrap();
|
|
||||
// } else {
|
|
||||
// info!("Creating new WireGuard configuration...");
|
|
||||
// let private_key = os::exec_bash(format!("wg genkey")).await;
|
|
||||
// let public_key = os::exec_bash(format!("echo {private_key} | wg pubkey")).await;
|
|
||||
// let address = quick.wg_default_address.repeat(1);
|
|
||||
// wg_config = WireguardMemento::new(config::Server {
|
|
||||
// private_key,
|
|
||||
// public_key,
|
|
||||
// address,
|
|
||||
// });
|
|
||||
// info!("WireGuard configuration generated...");
|
|
||||
// save_config_directly(&wg_config, &mut wg_config_file).await;
|
|
||||
// }
|
|
||||
// (wg_config, quick)
|
|
||||
// }
|
|
||||
|
|
||||
// async fn save_config_directly(config: &WireguardMemento, file: &mut tokio::fs::File) {
|
|
||||
// let wg_config_str =
|
|
||||
// serde_json::to_string_pretty(config).expect("Could not serialize wg_config");
|
|
||||
// os::write_to_file_unhandled(file, wg_config_str).await;
|
|
||||
// }
|
|
||||
|
|
||||
// async fn load_wireguard_settings(config_path: &str) -> WireguardSettings {
|
|
||||
// let (_, config_str) = os::load_and_read_file_unhandled(config_path).await;
|
|
||||
|
|
||||
// match serde_json::from_str(&config_str) {
|
|
||||
// Ok(config) => config,
|
|
||||
// Err(error) => {
|
|
||||
// log::error!(
|
|
||||
// "Could not parse settings {}: {}",
|
|
||||
// config_path,
|
|
||||
// error.to_string()
|
|
||||
// );
|
|
||||
// panic!()
|
|
||||
// }
|
|
||||
// }
|
|
||||
// }
|
|
||||
|
|
||||
// async fn sync_config() {
|
|
||||
// info!("Config syncing...");
|
|
||||
// os::exec_bash(format!("wg syncconf wg0 <(wg-quick strip wg0)")).await;
|
|
||||
// info!("Config synced...");
|
|
||||
// os::exec_bash(format!("wg syncconf wg0 <(wg-quick strip wg0)")).await;
|
|
||||
// }
|
|
||||
|
|
||||
// // async fn format_config(config: WireguardMemento, quick: WireguardSettings) -> String {
|
|
||||
// // let result_vec = vec![
|
|
||||
// // format!("# Note: Do not edit this file directly.\n"),
|
|
||||
// // format!("# Your changes will be overwritten!\n"),
|
|
||||
// // format!("\n"),
|
|
||||
// // format!("# Server\n"),
|
|
||||
// // format!("[Interface]\n"),
|
|
||||
// // format!("PrivateKey = {}\n", config.server.private_key),
|
|
||||
// // format!("Address = {}/24\n", config.server.address),
|
|
||||
// // format!("ListenPort = 51820\n"),
|
|
||||
// // format!("PreUp = {}\n", multi_line(quick.wg_pre_up)),
|
|
||||
// // format!("PostUp = {}\n", multi_line(quick.wg_post_up)),
|
|
||||
// // format!("PreDown = {}\n", multi_line(quick.wg_pre_down)),
|
|
||||
// // format!("PostDown = {}\n", multi_line(quick.wg_post_down)),
|
|
||||
// // ];
|
|
||||
// // let mut result = misc::multi_line(&result_vec);
|
|
||||
|
|
||||
// // for (uid, client) in config.clients {
|
|
||||
// // if !client.enabled {
|
|
||||
// // continue;
|
|
||||
// // }
|
|
||||
// // let result_inner = vec![
|
|
||||
// // format!("\n"),
|
|
||||
// // format!("# Client: {} ({})\n", client.name, uid),
|
|
||||
// // format!("[Peer]\n"),
|
|
||||
// // format!("PublicKey = {}\n", client.public_key),
|
|
||||
// // format!("PresharedKey = {}\n", client.pre_shared_key),
|
|
||||
// // format!("AllowedIPs = {}/32\n", client.address),
|
|
||||
// // ];
|
|
||||
// // result.push_str(multi_line(result_inner).as_str());
|
|
||||
// // }
|
|
||||
// // result
|
|
||||
// // }
|
|
||||
@ -1,430 +0,0 @@ |
|||||
use std::collections::HashMap; |
|
||||
|
|
||||
use chrono::{SecondsFormat, SubsecRound, Utc}; |
|
||||
|
|
||||
|
|
||||
use serde::{Deserialize, Serialize}; |
|
||||
use tokio::join; |
|
||||
use uuid::Uuid; |
|
||||
|
|
||||
use crate::utils::{misc, os}; |
|
||||
|
|
||||
#[derive(Serialize, Deserialize, Clone)] |
|
||||
pub struct WireguardSettings { |
|
||||
pub interface_name: String, |
|
||||
pub release: String, |
|
||||
pub api_port: i32, |
|
||||
pub password: String, |
|
||||
pub host: String, |
|
||||
pub wg_port: i32, |
|
||||
pub mtu: i32, |
|
||||
pub persistent_keepalive: i32, |
|
||||
pub default_address: String, |
|
||||
pub default_address_base: String, |
|
||||
pub default_dns: String, |
|
||||
pub allowed_ips: String, |
|
||||
pub pre_up: String, |
|
||||
pub post_up: String, |
|
||||
pub pre_down: String, |
|
||||
pub post_down: String, |
|
||||
} |
|
||||
|
|
||||
#[derive(Serialize, Deserialize)] |
|
||||
#[serde(rename_all(serialize = "camelCase"))] |
|
||||
#[serde(rename_all(deserialize = "camelCase"))] |
|
||||
pub struct WireguardMemento { |
|
||||
pub server: Server, |
|
||||
pub clients: HashMap<uuid::Uuid, Client>, |
|
||||
} |
|
||||
|
|
||||
#[derive(Serialize, Deserialize)] |
|
||||
#[serde(rename_all(serialize = "camelCase"))] |
|
||||
#[serde(rename_all(deserialize = "camelCase"))] |
|
||||
pub struct Server { |
|
||||
pub private_key: String, |
|
||||
pub public_key: String, |
|
||||
pub address: String, |
|
||||
} |
|
||||
|
|
||||
#[derive(Serialize, Deserialize, Clone)] |
|
||||
#[serde(rename_all(serialize = "camelCase"))] |
|
||||
#[serde(rename_all(deserialize = "camelCase"))] |
|
||||
pub struct Client { |
|
||||
pub uuid: Uuid, |
|
||||
pub name: String, |
|
||||
pub address: String, |
|
||||
pub private_key: String, |
|
||||
pub public_key: String, |
|
||||
pub pre_shared_key: String, |
|
||||
pub created_at: String, |
|
||||
pub updated_at: String, |
|
||||
pub enabled: bool, |
|
||||
} |
|
||||
|
|
||||
impl Client { |
|
||||
pub fn enable(&mut self) { |
|
||||
self.enabled = true; |
|
||||
self.updated_at = Utc::now() |
|
||||
.trunc_subsecs(3) |
|
||||
.to_rfc3339_opts(SecondsFormat::Millis, true) |
|
||||
} |
|
||||
pub fn disable(&mut self) { |
|
||||
self.enabled = false; |
|
||||
self.updated_at = Utc::now() |
|
||||
.trunc_subsecs(3) |
|
||||
.to_rfc3339_opts(SecondsFormat::Millis, true) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
#[derive(Serialize, Deserialize, Clone)] |
|
||||
#[serde(rename_all(serialize = "camelCase"))] |
|
||||
#[serde(rename_all(deserialize = "snake_case"))] |
|
||||
pub struct WebClient { |
|
||||
pub id: Uuid, |
|
||||
pub name: String, |
|
||||
pub enabled: bool, |
|
||||
pub address: String, |
|
||||
pub public_key: String, |
|
||||
pub created_at: String, |
|
||||
pub updated_at: String, |
|
||||
pub allowed_ips: String, |
|
||||
pub persistent_keepalive: String, |
|
||||
pub latest_handshake_at: String, |
|
||||
pub transfer_rx: i64, |
|
||||
pub transfer_tx: i64, |
|
||||
} |
|
||||
|
|
||||
#[derive(Serialize, Deserialize, Clone)] |
|
||||
#[serde(rename_all(serialize = "camelCase"))] |
|
||||
#[serde(rename_all(deserialize = "snake_case"))] |
|
||||
pub struct DumpClient { |
|
||||
pub public_key: String, |
|
||||
pub pre_shared_key: String, |
|
||||
pub endpoint: String, |
|
||||
pub allowed_ips: String, |
|
||||
pub latest_handshake_at: String, |
|
||||
pub transfer_rx: i64, |
|
||||
pub transfer_tx: i64, |
|
||||
pub persistent_keepalive: String, |
|
||||
} |
|
||||
#[derive(Clone)] |
|
||||
pub struct WireGuard { |
|
||||
pub name: String, |
|
||||
pub config_path: String, |
|
||||
pub memento_path: String, |
|
||||
pub settings: WireguardSettings, |
|
||||
} |
|
||||
|
|
||||
impl WireguardMemento { |
|
||||
pub fn new(server: Server) -> Self { |
|
||||
Self { |
|
||||
server, |
|
||||
clients: HashMap::new(), |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
impl WireGuard { |
|
||||
pub fn new(name: &str, wg_path: String, settings: WireguardSettings) -> Self { |
|
||||
let path = format!("{}{}", wg_path, name); |
|
||||
let config_path = format!("{}.conf", path); |
|
||||
let memento_path = format!("{}.json", path); |
|
||||
let name = format!("{}", name); |
|
||||
Self { |
|
||||
name, |
|
||||
settings, |
|
||||
config_path, |
|
||||
memento_path, |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
pub async fn quick_up(&self) { |
|
||||
let result = os::exec_sh(format!("wg-quick up {}", &self.name)) |
|
||||
.await |
|
||||
.expect("Error"); |
|
||||
result.log(); |
|
||||
} |
|
||||
|
|
||||
pub async fn quick_down(&self) { |
|
||||
let result = os::exec_sh(format!("wg-quick down {}", &self.name)) |
|
||||
.await |
|
||||
.expect("Error"); |
|
||||
result.log(); |
|
||||
} |
|
||||
|
|
||||
pub async fn sync_config(&self) { |
|
||||
let result = os::exec_sh(format!( |
|
||||
"wg syncconf {} <(wg-quick strip {})", |
|
||||
self.name, self.name |
|
||||
)) |
|
||||
.await |
|
||||
.expect("Error syncing"); |
|
||||
result.log(); |
|
||||
} |
|
||||
|
|
||||
pub async fn get_memento(&self) -> WireguardMemento { |
|
||||
let (_, memento_str) = os::load_and_read_file_unhandled(self.memento_path.as_str()).await; |
|
||||
if !memento_str.is_empty() { |
|
||||
serde_json::from_str::<WireguardMemento>(memento_str.as_str()).unwrap() |
|
||||
} else { |
|
||||
log::info!("Creating new WireGuard configuration..."); |
|
||||
let private_key = os::exec_sh("wg genkey").await.expect("error genkey").stdout; |
|
||||
let public_key = os::exec_sh(format!("echo {} | wg pubkey", private_key)) |
|
||||
.await |
|
||||
.expect("error pubkey") |
|
||||
.stdout; |
|
||||
|
|
||||
let address = self.settings.default_address.repeat(1); |
|
||||
let memento = WireguardMemento::new(Server { |
|
||||
private_key, |
|
||||
public_key, |
|
||||
address, |
|
||||
}); |
|
||||
log::info!("WireGuard configuration generated..."); |
|
||||
memento |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
pub async fn set_memento(&self, memento: &WireguardMemento) { |
|
||||
let data_memento = |
|
||||
serde_json::to_string_pretty(memento).expect("Could not serialize memento"); |
|
||||
let data_config = self.format_config(memento); |
|
||||
|
|
||||
join!( |
|
||||
os::write_to_path_unhandled(self.config_path.as_str(), data_config), |
|
||||
os::write_to_path_unhandled(self.memento_path.as_str(), data_memento.to_string()) |
|
||||
); |
|
||||
} |
|
||||
|
|
||||
pub async fn start(&self) { |
|
||||
let memento = self.get_memento().await; |
|
||||
self.set_memento(&memento).await; |
|
||||
self.quick_down().await; |
|
||||
self.quick_up().await; |
|
||||
self.sync_config().await; |
|
||||
} |
|
||||
|
|
||||
pub async fn create_client<'a>( |
|
||||
&'a self, |
|
||||
memento: &'a mut WireguardMemento, |
|
||||
name: String, |
|
||||
) -> &Client { |
|
||||
let private_key = os::exec_sh("wg genkey").await.expect("error genkey").stdout; |
|
||||
|
|
||||
let pub_cmd = format!("echo {} | wg pubkey", private_key); |
|
||||
let public_key = os::exec_sh(pub_cmd).await.expect("error pubkey").stdout; |
|
||||
|
|
||||
let pre_shared_key = os::exec_sh("wg genpsk").await.expect("error genpsk").stdout; |
|
||||
|
|
||||
let mut i: u8 = 2; |
|
||||
let address = loop { |
|
||||
match memento.clients.iter().find(|(_, client)| { |
|
||||
client.address == format!("{}.{}", self.settings.default_address_base, i) |
|
||||
}) { |
|
||||
Some(_) => { |
|
||||
if i == 255 { |
|
||||
break None; |
|
||||
} |
|
||||
i += 1; |
|
||||
} |
|
||||
None => break Some(format!("{}.{}", self.settings.default_address_base, i)), |
|
||||
} |
|
||||
} |
|
||||
.expect("Maximum number of clients reached"); |
|
||||
|
|
||||
let uuid = Uuid::new_v4(); |
|
||||
let now = Utc::now().trunc_subsecs(3); |
|
||||
|
|
||||
let client = Client { |
|
||||
uuid, |
|
||||
name, |
|
||||
address, |
|
||||
created_at: now.to_rfc3339_opts(SecondsFormat::Secs, true), |
|
||||
updated_at: now.to_rfc3339_opts(SecondsFormat::Secs, true), |
|
||||
enabled: true, |
|
||||
pre_shared_key, |
|
||||
private_key, |
|
||||
public_key, |
|
||||
}; |
|
||||
|
|
||||
memento.clients.insert(uuid, client.clone()); |
|
||||
|
|
||||
self.set_memento(&memento).await; |
|
||||
self.sync_config().await; |
|
||||
|
|
||||
memento.clients.get(&uuid).unwrap() |
|
||||
} |
|
||||
|
|
||||
pub fn get_client<'a>(&'a self, memento: &'a WireguardMemento, client_id: Uuid) -> &Client { |
|
||||
let client = memento |
|
||||
.clients |
|
||||
.get(&client_id) |
|
||||
.expect("Could not find user"); |
|
||||
client |
|
||||
} |
|
||||
|
|
||||
pub async fn get_clients(&self, memento: &WireguardMemento) -> Vec<WebClient> { |
|
||||
let res = os::exec_sh("wg show wg0 dump").await.unwrap(); |
|
||||
|
|
||||
let lines: Vec<Vec<String>> = res |
|
||||
.stdout |
|
||||
.trim() |
|
||||
.split('\n') |
|
||||
.skip(1) |
|
||||
.map(|line| line.split('\t').map(|x| x.to_string()).collect()) |
|
||||
.collect(); |
|
||||
|
|
||||
let lines: Vec<DumpClient> = lines |
|
||||
.iter() |
|
||||
.map(|x| DumpClient { |
|
||||
public_key: x[0].to_string(), |
|
||||
pre_shared_key: x[1].to_string(), |
|
||||
endpoint: x[2].to_string(), |
|
||||
allowed_ips: x[3].to_string(), |
|
||||
latest_handshake_at: x[4].to_string(), |
|
||||
transfer_rx: x[5].parse::<i64>().unwrap(), |
|
||||
transfer_tx: x[6].parse::<i64>().unwrap(), |
|
||||
persistent_keepalive: x[7].to_string(), |
|
||||
}) |
|
||||
.collect(); |
|
||||
|
|
||||
let clients: Vec<WebClient> = memento |
|
||||
.clients |
|
||||
.iter() |
|
||||
.map(|(uuid, value)| { |
|
||||
let uuid = uuid.to_owned(); |
|
||||
let value = value.to_owned(); |
|
||||
let client_conf; |
|
||||
|
|
||||
match lines.iter().find(|x| x.public_key.eq(&value.public_key)) { |
|
||||
Some(conf) => client_conf = conf, |
|
||||
None => return None, |
|
||||
}; |
|
||||
Some(WebClient { |
|
||||
id: uuid, |
|
||||
name: value.name, |
|
||||
enabled: value.enabled, |
|
||||
address: value.address, |
|
||||
public_key: value.public_key, |
|
||||
created_at: value.created_at, |
|
||||
updated_at: value.updated_at, |
|
||||
allowed_ips: client_conf.allowed_ips.to_string(), |
|
||||
persistent_keepalive: client_conf.persistent_keepalive.to_string(), |
|
||||
latest_handshake_at: client_conf.latest_handshake_at.to_string(), |
|
||||
transfer_rx: client_conf.transfer_rx, |
|
||||
transfer_tx: client_conf.transfer_tx, |
|
||||
}) |
|
||||
}) |
|
||||
.filter(|x| x.is_some()) |
|
||||
.map(|x| x.unwrap()) |
|
||||
.collect(); |
|
||||
|
|
||||
clients |
|
||||
} |
|
||||
|
|
||||
pub fn get_client_configuration( |
|
||||
&self, |
|
||||
memento: &WireguardMemento, |
|
||||
client_id: Uuid, |
|
||||
) -> (String, String) { |
|
||||
let client = self.get_client(memento, client_id); |
|
||||
|
|
||||
let result_vec = vec![ |
|
||||
format!("[Interface]\n"), |
|
||||
format!("PrivateKey = {}\n", client.private_key), |
|
||||
format!("Address = {}/24\n", client.address), |
|
||||
format!("DNS = {}\n", self.settings.default_dns), |
|
||||
format!("MTU = {}\n", self.settings.mtu), |
|
||||
format!("\n"), |
|
||||
format!("[Peer]\n"), |
|
||||
format!("PublicKey = {}\n", memento.server.public_key), |
|
||||
format!("PresharedKey = {}\n", client.pre_shared_key), |
|
||||
format!("AllowedIPs = {}\n", self.settings.allowed_ips), |
|
||||
format!( |
|
||||
"PersistentKeepalive = {}\n", |
|
||||
self.settings.persistent_keepalive |
|
||||
), |
|
||||
format!( |
|
||||
"Endpoint = {}:{}\n", |
|
||||
self.settings.host, self.settings.wg_port |
|
||||
), |
|
||||
]; |
|
||||
|
|
||||
(client.name.to_string(), misc::multi_line(&result_vec)) |
|
||||
} |
|
||||
|
|
||||
pub async fn delete_client(&self, memento: &mut WireguardMemento, client_id: Uuid) { |
|
||||
if memento.clients.remove(&client_id).is_some() { |
|
||||
self.set_memento(&memento).await; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
pub async fn enable_client(&self, memento: &mut WireguardMemento, client_id: Uuid) { |
|
||||
match memento.clients.get_mut(&client_id) { |
|
||||
Some(client) => { |
|
||||
client.enable(); |
|
||||
self.set_memento(&memento).await; |
|
||||
} |
|
||||
None => return, |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
pub async fn disable_client(&self, memento: &mut WireguardMemento, client_id: Uuid) { |
|
||||
match memento.clients.get_mut(&client_id) { |
|
||||
Some(client) => { |
|
||||
client.disable(); |
|
||||
self.set_memento(&memento).await; |
|
||||
} |
|
||||
None => return, |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
pub async fn get_client_qrcode_svg( |
|
||||
&self, |
|
||||
memento: &WireguardMemento, |
|
||||
client_id: Uuid, |
|
||||
) -> String { |
|
||||
let (_, config) = self.get_client_configuration(memento, client_id); |
|
||||
return qrcode_generator::to_svg_to_string_from_str( |
|
||||
config, |
|
||||
qrcode_generator::QrCodeEcc::Low, |
|
||||
512, |
|
||||
None::<&str>, |
|
||||
) |
|
||||
.unwrap(); |
|
||||
} |
|
||||
|
|
||||
fn format_config(&self, config: &WireguardMemento) -> String { |
|
||||
let result_vec = vec![ |
|
||||
format!("# Note: Do not edit this file directly.\n"), |
|
||||
format!("# Your changes will be overwritten!\n"), |
|
||||
format!("\n"), |
|
||||
format!("# Server\n"), |
|
||||
format!("[Interface]\n"), |
|
||||
format!("PrivateKey = {}\n", config.server.private_key), |
|
||||
format!("Address = {}/24\n", config.server.address), |
|
||||
format!("ListenPort = 51820\n"), |
|
||||
format!("PreUp = {}\n", self.settings.pre_up), |
|
||||
format!("PostUp = {}\n", self.settings.post_up), |
|
||||
format!("PreDown = {}\n", self.settings.pre_down), |
|
||||
format!("PostDown = {}\n", self.settings.post_down), |
|
||||
]; |
|
||||
let mut result = misc::multi_line(&result_vec); |
|
||||
|
|
||||
for (uid, client) in &config.clients { |
|
||||
if !client.enabled { |
|
||||
continue; |
|
||||
} |
|
||||
let result_inner = vec![ |
|
||||
format!("\n"), |
|
||||
format!("# Client: {} ({})\n", client.name, uid), |
|
||||
format!("[Peer]\n"), |
|
||||
format!("PublicKey = {}\n", client.public_key), |
|
||||
format!("PresharedKey = {}\n", client.pre_shared_key), |
|
||||
format!("AllowedIPs = {}/32\n", client.address), |
|
||||
]; |
|
||||
result.push_str(misc::multi_line(&result_inner).as_str()); |
|
||||
} |
|
||||
result |
|
||||
} |
|
||||
} |
|
||||
Loading…
Reference in new issue