Start on proper wireguard support

This commit is contained in:
asonix 2020-09-20 16:26:40 -05:00
parent 5e41063680
commit ca718ec30e
5 changed files with 412 additions and 161 deletions

244
Cargo.lock generated
View file

@ -115,16 +115,39 @@ dependencies = [
[[package]]
name = "async-executor"
version = "0.1.2"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90f47c78ea98277cb1f5e6f60ba4fc762f5eafe9f6511bc2f7dfd8b75c225650"
checksum = "d373d78ded7d0b3fa8039375718cde0aace493f2e34fb60f51cbf567562ca801"
dependencies = [
"async-io 0.1.11",
"futures-lite 0.1.11",
"multitask",
"parking 1.0.6",
"scoped-tls",
"waker-fn",
"async-task 4.0.0",
"concurrent-queue",
"fastrand",
"futures-lite",
"once_cell",
"vec-arena",
]
[[package]]
name = "async-fs"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3572236ba37147ca2b674a0bd5afd20aec0cd925ab125ab6fad6543960f9002"
dependencies = [
"blocking",
"futures-lite",
]
[[package]]
name = "async-global-executor"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffd4f132a18f3fe7329c7b907047684f1b06174a900c559b661b2da8bb9cad5f"
dependencies = [
"async-executor",
"async-io",
"futures-lite",
"num_cpus",
"once_cell",
]
[[package]]
@ -145,43 +168,20 @@ dependencies = [
[[package]]
name = "async-io"
version = "0.1.11"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ae22a338d28c75b53702b66f77979062cb29675db376d99e451af4fa79dedb3"
checksum = "64c629684e697f58c0e99e5e2d84a840e3b336afbcfdbac7b44c3b1e222c2fd8"
dependencies = [
"cfg-if",
"concurrent-queue",
"futures-lite 0.1.11",
"libc",
"once_cell",
"parking 2.0.0",
"polling 0.1.9",
"socket2",
"vec-arena 0.5.2",
"wepoll-sys-stjepang",
"winapi",
]
[[package]]
name = "async-io"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38628c78a34f111c5a6b98fc87dfc056cd1590b61afe748b145be4623c56d194"
dependencies = [
"cfg-if",
"concurrent-queue",
"fastrand",
"futures-lite 1.4.0",
"libc",
"futures-lite",
"log",
"nb-connect",
"once_cell",
"parking 2.0.0",
"polling 1.0.1",
"socket2",
"vec-arena 1.0.0",
"parking",
"polling",
"vec-arena",
"waker-fn",
"wepoll-sys-stjepang",
"winapi",
]
[[package]]
@ -199,11 +199,11 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bb915df28b8309139bd9c9c700d84c20e5c21385d05378caa84912332d0f6a1"
dependencies = [
"async-io 1.1.0",
"blocking 1.0.0",
"async-io",
"blocking",
"cfg-if",
"event-listener",
"futures-lite 1.4.0",
"futures-lite",
"once_cell",
"signal-hook",
"winapi",
@ -246,21 +246,21 @@ dependencies = [
[[package]]
name = "async-std"
version = "1.6.3"
version = "1.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c8da367da62b8ff2313c406c9ac091c1b31d67a165becdd2de380d846260f7"
checksum = "3c92085acfce8b32e5b261d0b59b8f3309aee69fea421ea3f271f8b93225754f"
dependencies = [
"async-executor",
"async-io 0.1.11",
"async-global-executor",
"async-io",
"async-mutex",
"async-task",
"blocking 0.5.2",
"async-task 3.0.0",
"blocking",
"crossbeam-utils",
"futures-channel",
"futures-core",
"futures-io",
"futures-lite 0.1.11",
"futures-timer",
"futures-lite",
"gloo-timers",
"kv-log-macro",
"log",
"memchr",
@ -278,6 +278,12 @@ version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c17772156ef2829aadc587461c7753af20b7e8db1529bc66855add962a3b35d3"
[[package]]
name = "async-task"
version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c37ba09c1b5185eb9897a5cef32770031f58fa92d9a5f79eb50cae5030b39c1"
[[package]]
name = "async-trait"
version = "0.1.40"
@ -397,19 +403,6 @@ dependencies = [
"generic-array",
]
[[package]]
name = "blocking"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea5800d29218fea137b0880387e5948694a23c93fcdde157006966693a865c7c"
dependencies = [
"async-channel",
"atomic-waker",
"futures-lite 0.1.11",
"once_cell",
"waker-fn",
]
[[package]]
name = "blocking"
version = "1.0.0"
@ -419,7 +412,7 @@ dependencies = [
"async-channel",
"atomic-waker",
"fastrand",
"futures-lite 1.4.0",
"futures-lite",
"once_cell",
"waker-fn",
]
@ -471,9 +464,9 @@ checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba"
[[package]]
name = "cc"
version = "1.0.59"
version = "1.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66120af515773fb005778dc07c261bd201ec8ce50bd6e7144c927753fe013381"
checksum = "ef611cc68ff783f18535d77ddd080185275713d852c4f5cbb6122c462a7a825c"
[[package]]
name = "cfg-if"
@ -552,7 +545,7 @@ dependencies = [
"percent-encoding",
"rand",
"sha2",
"time 0.2.19",
"time 0.2.20",
"version_check",
]
@ -647,9 +640,9 @@ checksum = "134951f4028bdadb9b84baf4232681efbf277da25144b9b0ad65df75946c422b"
[[package]]
name = "either"
version = "1.6.0"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd56b59865bce947ac5958779cfa508f6c3b9497cc762b7e24a12d11ccde2c4f"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]]
name = "error-chain"
@ -722,30 +715,15 @@ checksum = "de27142b013a8e869c14957e6d2edeef89e97c289e69d042ee3a49acd8b51789"
[[package]]
name = "futures-lite"
version = "0.1.11"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97999970129b808f0ccba93211201d431fcc12d7e1ffae03a61b5cedd1a7ced2"
checksum = "5b77e08e656f472d8ea84c472fa8b0a7a917883048e1cf2d4e34a323cd0aaf63"
dependencies = [
"fastrand",
"futures-core",
"futures-io",
"memchr",
"parking 2.0.0",
"pin-project-lite",
"waker-fn",
]
[[package]]
name = "futures-lite"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba659a67bd5ac00896b31e1f4095174134a31e448893d73256f1d51b81abbd62"
dependencies = [
"fastrand",
"futures-core",
"futures-io",
"memchr",
"parking 2.0.0",
"parking",
"pin-project-lite",
"waker-fn",
]
@ -771,16 +749,6 @@ dependencies = [
"once_cell",
]
[[package]]
name = "futures-timer"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c"
dependencies = [
"gloo-timers",
"send_wrapper",
]
[[package]]
name = "futures-util"
version = "0.3.5"
@ -928,9 +896,12 @@ checksum = "6854dd77ddc4f9ba1a448f487e27843583d407648150426a30c2ea3a2c39490a"
[[package]]
name = "instant"
version = "0.1.6"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b141fdc7836c525d4d594027d318c84161ca17aaf8113ab1f81ab93ae897485"
checksum = "63312a18f7ea8760cdd0a7c5aac1a619752a246b833545e3e36d1f81f7cd9e66"
dependencies = [
"cfg-if",
]
[[package]]
name = "itertools"
@ -1050,9 +1021,9 @@ checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
[[package]]
name = "memoffset"
version = "0.5.5"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c198b026e1bbf08a937e94c6c60f9ec4a2267f5b0d2eec9c1b21b061ce2be55f"
checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa"
dependencies = [
"autocfg",
]
@ -1074,14 +1045,13 @@ dependencies = [
]
[[package]]
name = "multitask"
version = "0.2.0"
name = "nb-connect"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c09c35271e7dcdb5f709779111f2c8e8ab8e06c1b587c1c6a9e179d865aaa5b4"
checksum = "e847c76b390f44529c2071ef06d0b52fbb4bdb04cc8987a5cfa63954c000abca"
dependencies = [
"async-task",
"concurrent-queue",
"fastrand",
"libc",
"winapi",
]
[[package]]
@ -1168,12 +1138,6 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "parking"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6cb300f271742d4a2a66c01b6b2fa0c83dfebd2e0bf11addb879a3547b4ed87c"
[[package]]
name = "parking"
version = "2.0.0"
@ -1246,22 +1210,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "polling"
version = "0.1.9"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fffa183f6bd5f1a8a3e1f60ce2f8d5621e350eed84a62d6daaa5b9d1aaf6fbd"
dependencies = [
"cfg-if",
"libc",
"log",
"wepoll-sys-stjepang",
"winapi",
]
[[package]]
name = "polling"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0835fa5f9af34c170eb38638ae6bc88e1b11ecdd0b968c9d9de8e343450385eb"
checksum = "e0720e0b9ea9d52451cf29d3413ba8a9303f8815d9d9653ef70e03ff73e65566"
dependencies = [
"cfg-if",
"libc",
@ -1392,13 +1343,14 @@ name = "router"
version = "0.1.0"
dependencies = [
"anyhow",
"async-fs",
"async-process",
"async-trait",
"base64",
"bcrypt",
"blocking 1.0.0",
"blocking",
"config",
"futures-lite 1.4.0",
"futures-lite",
"mime",
"once_cell",
"rand",
@ -1468,12 +1420,6 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
name = "scoped-tls"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
[[package]]
name = "scopeguard"
version = "1.1.0"
@ -1495,12 +1441,6 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "send_wrapper"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0"
[[package]]
name = "serde"
version = "0.8.23"
@ -1684,18 +1624,6 @@ version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbee7696b84bbf3d89a1c2eccff0850e3047ed46bfcd2e92c29a2d074d57e252"
[[package]]
name = "socket2"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1fa70dc5c8104ec096f4fe7ede7a221d35ae13dcd19ba1ad9a81d2cab9a1c44"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"winapi",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
@ -1846,9 +1774,9 @@ dependencies = [
[[package]]
name = "time"
version = "0.2.19"
version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80c1a1fd93112fc50b11c43a1def21f926be3c18884fad676ea879572da070a1"
checksum = "0d4953c513c9bf1b97e9cdd83f11d60c4b0a83462880a360d80d96953a953fee"
dependencies = [
"const_fn",
"libc",
@ -1949,12 +1877,6 @@ dependencies = [
"serde 1.0.116",
]
[[package]]
name = "vec-arena"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cb18268690309760d59ee1a9b21132c126ba384f374c59a94db4bc03adeb561"
[[package]]
name = "vec-arena"
version = "1.0.0"

View file

@ -9,6 +9,7 @@ build = "src/build.rs"
[dependencies]
anyhow = "1.0"
async-fs = "1.3.0"
async-process = "1.0.0"
async-trait = "0.1.40"
base64 = "0.12.3"

View file

@ -15,12 +15,14 @@ mod middleware;
mod rules;
mod session;
mod startup;
mod tunnels;
use self::{
middleware::{AuthMiddleware, UserInfo},
rules::Rule,
startup::{Config, Interfaces, ServerState},
templates::statics::StaticFile,
tunnels::{NewPeer, NewTunnel},
};
static CONFIG: Lazy<Config> = Lazy::new(|| Config::read().unwrap());
@ -81,6 +83,41 @@ async fn delete_rule(req: tide::Request<()>) -> tide::Result {
Ok(to_rules_page())
}
async fn tunnels_page(_: tide::Request<()>) -> tide::Result {
Ok(tide::Response::builder(200).build())
}
async fn save_tunnel(mut req: tide::Request<()>) -> tide::Result {
let body_string = req.body_string().await?;
let new_tunnel: NewTunnel = QS.deserialize_str(&body_string)?;
let tunnel = tunnels::create(new_tunnel).await?;
// TODO: add firewall rule for tunnel
tunnels::save(&DB, &tunnel).await?;
Ok(to_tunnels_page())
}
async fn peers_page(_: tide::Request<()>) -> tide::Result {
Ok(tide::Response::builder(200).build())
}
async fn save_peer(mut req: tide::Request<()>) -> tide::Result {
let body_string = req.body_string().await?;
let new_peer: NewPeer = QS.deserialize_str(&body_string)?;
let peer = tunnels::add_peer(&DB, new_peer).await?;
let _ = peer;
// TODO: add firewall rule for peer
Ok(to_tunnels_page())
}
#[derive(serde::Deserialize)]
struct LoginForm {
username: String,
@ -227,6 +264,10 @@ fn to_rules_page() -> tide::Response {
to_custom("/rules")
}
fn to_tunnels_page() -> tide::Response {
to_custom("/tunnnels")
}
async fn statics(req: tide::Request<()>) -> tide::Result {
let file: String = req.param("file")?;
@ -261,6 +302,15 @@ fn main() -> Result<(), anyhow::Error> {
app.at("/static/:file").get(statics);
app.at("/login").get(login_page).post(login);
app.at("/account").get(account_page).post(update_account);
app.at("/tunnels")
.with(AuthMiddleware)
.get(tunnels_page)
.post(save_tunnel)
.nest({
let mut app = tide::new();
app.at("/:id/peers").get(peers_page).post(save_peer);
app
});
app.at("/rules")
.with(AuthMiddleware)
.get(rules_page)

134
src/tunnels/mod.rs Normal file
View file

@ -0,0 +1,134 @@
use once_cell::sync::OnceCell;
use sled::{Db, Tree};
static TUNNELS_TREE: OnceCell<Tree> = OnceCell::new();
fn tunnels_tree(db: &Db) -> &'static Tree {
TUNNELS_TREE.get_or_init(|| db.open_tree("tunnels").unwrap())
}
mod wireguard;
#[derive(serde::Deserialize)]
#[serde(tag = "type")]
pub(crate) enum NewTunnel {
Wireguard(wireguard::NewInterface),
}
#[derive(serde::Deserialize)]
pub(crate) struct NewPeer {
tunnel_id: String,
peer: NewPeerKind,
}
#[derive(serde::Deserialize)]
#[serde(tag = "type")]
pub(crate) enum NewPeerKind {
Wireguard(wireguard::Peer),
}
#[derive(serde::Deserialize, serde::Serialize)]
#[serde(tag = "type")]
pub(crate) enum Tunnel {
Wireguard(wireguard::Interface),
}
#[derive(serde::Deserialize, serde::Serialize)]
pub(crate) struct Peer {
tunnel_id: String,
peer: PeerKind,
}
#[derive(serde::Deserialize, serde::Serialize)]
#[serde(tag = "type")]
pub(crate) enum PeerKind {
Wireguard(wireguard::Peer),
}
pub(crate) async fn add_peer(db: &Db, new_peer: NewPeer) -> Result<Peer, anyhow::Error> {
let tree = tunnels_tree(db);
let tunnel_bytes = tree
.get(new_peer.tunnel_id.as_bytes())?
.ok_or_else(|| anyhow::anyhow!("Missing tunnel"))?;
let tunnel: Tunnel = serde_json::from_slice(&tunnel_bytes)?;
let peer = match (new_peer.peer, tunnel) {
(NewPeerKind::Wireguard(peer), Tunnel::Wireguard(interface)) => {
wireguard::add_peer(&interface, &peer).await?;
Peer {
tunnel_id: new_peer.tunnel_id,
peer: PeerKind::Wireguard(peer),
}
} // _ => return Err(anyhow::anyhow!("Peer kind mismatch")),
};
let v = serde_json::to_vec(&peer)?;
loop {
let id = db.generate_id()?;
let peer_id = peer_id(&peer.tunnel_id, id);
if tree
.compare_and_swap(
peer_id.as_bytes(),
None as Option<Vec<u8>>,
Some(v.as_slice()),
)?
.is_ok()
{
break;
}
}
tree.flush_async().await?;
Ok(peer)
}
pub(crate) async fn create(new_tunnel: NewTunnel) -> Result<Tunnel, anyhow::Error> {
match new_tunnel {
NewTunnel::Wireguard(new_interface) => {
let interface = wireguard::generate_config(new_interface).await?;
wireguard::apply(&interface).await?;
Ok(Tunnel::Wireguard(interface))
}
}
}
pub(crate) async fn save(db: &Db, tunnel: &Tunnel) -> Result<(), anyhow::Error> {
let tree = tunnels_tree(db);
let v = serde_json::to_vec(tunnel)?;
loop {
let id = db.generate_id()?;
let tunnel_id = tunnel_id(id);
if tree
.compare_and_swap(
tunnel_id.as_bytes(),
None as Option<Vec<u8>>,
Some(v.as_slice()),
)?
.is_ok()
{
break;
}
}
tree.flush_async().await?;
Ok(())
}
fn tunnel_id(id: u64) -> String {
format!("tunnel-{}", id)
}
fn peer_id(tunnel_id: &str, id: u64) -> String {
format!("{}/peers/{}", tunnel_id, id)
}

144
src/tunnels/wireguard.rs Normal file
View file

@ -0,0 +1,144 @@
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)
.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(())
}