router/src/tunnels/wireguard.rs
2021-02-07 17:10:36 -06:00

146 lines
3.4 KiB
Rust

use async_fs::File;
use async_process::{Command, Stdio};
use futures_lite::AsyncWriteExt;
use std::{net::Ipv4Addr, path::PathBuf};
#[derive(serde::Deserialize)]
pub(crate) struct NewInterface {
name: String,
address: Ipv4Addr,
port: u16,
}
#[derive(serde::Deserialize, serde::Serialize)]
pub(crate) struct Interface {
name: String,
address: Ipv4Addr,
port: u16,
private_key: String,
public_key: String,
}
#[derive(serde::Deserialize, serde::Serialize)]
pub(crate) struct Peer {
public_key: String,
address: Ipv4Addr,
}
impl Interface {
fn config(&self) -> String {
format!(
r#"[Interface]
PrivateKey = {private_key}
Address = {address}/24
ListenPort = {port}
SaveConfig = true
"#,
private_key = self.private_key,
address = self.address,
port = self.port,
)
}
}
impl Peer {
fn config(&self) -> String {
format!(
r#"[Peer]
PublicKey = {public_key}
AllowedIPs = {allowed_ip}/32
"#,
public_key = self.public_key,
allowed_ip = self.address,
)
}
}
pub(crate) async fn add_peer(interface: &Interface, peer: &Peer) -> Result<(), anyhow::Error> {
use rand::Rng;
let filename = rand::thread_rng()
.sample_iter(rand::distributions::Alphanumeric)
.take(8)
.map(char::from)
.collect::<String>();
let filename = format!("{}.conf", filename);
let mut tmp_file = PathBuf::new();
tmp_file.push("/tmp");
tmp_file.push(&filename);
let mut file = File::create(&tmp_file).await?;
file.write_all(peer.config().as_bytes()).await?;
file.flush().await?;
drop(file);
let status = Command::new("wg")
.args(&[
&"addconf".as_ref(),
&interface.name.as_ref(),
&tmp_file.as_os_str(),
])
.status()
.await?;
async_fs::remove_file(&tmp_file).await?;
if !status.success() {
return Err(anyhow::anyhow!("Failed to add a peer"));
}
Ok(())
}
pub(crate) async fn generate_config(
new_interface: NewInterface,
) -> Result<Interface, anyhow::Error> {
let output = Command::new("wg").arg("genkey").output().await?;
if !output.status.success() {
return Err(anyhow::anyhow!("Error generating private key"));
}
let private_key = String::from_utf8_lossy(&output.stdout).trim().to_string();
let child = Command::new("wg")
.arg("pubkey")
.stdin(Stdio::piped())
.spawn()?;
let output = child.output().await?;
if !output.status.success() {
return Err(anyhow::anyhow!("Error generating public key"));
}
let public_key = String::from_utf8_lossy(&output.stdout).trim().to_string();
Ok(Interface {
name: new_interface.name,
address: new_interface.address,
port: new_interface.port,
private_key,
public_key,
})
}
pub(crate) async fn apply(interface: &Interface) -> Result<(), anyhow::Error> {
let config = interface.config();
let mut file = File::create(format!("{}.conf", interface.name)).await?;
file.write_all(config.as_bytes()).await?;
file.flush().await?;
let status = Command::new("systemctl")
.args(&["enable", "--now", &interface.name])
.status()
.await?;
if !status.success() {
return Err(anyhow::anyhow!("Failed to enable wireguard service"));
}
Ok(())
}