From 25524d529044f34fc92ee9c84ad50cd55af8b134 Mon Sep 17 00:00:00 2001 From: "Dustin C. Hatch" Date: Sun, 2 Oct 2022 17:47:25 -0500 Subject: [PATCH] routes: Add WireGuard configuration resource The * GET /wireguard/config/* resource returns the WireGuard client configuration assigned to the specified instance ID. The resource contents are stored in the Kubernetes Secret, in a data field named `wireguard-config`. The contents of this field are returned directly as a string, without any transformation. Thus, the value must be a complete, valid WireGuard configuration document. Instances will fetch and save this configuration when they first launch, to configure their access to the VPN. --- src/k8s.rs | 36 ++++++++++++++++++++++++++++++++++++ src/main.rs | 1 + src/routes/mod.rs | 1 + src/routes/wireguard.rs | 30 ++++++++++++++++++++++++++++++ 4 files changed, 68 insertions(+) create mode 100644 src/routes/wireguard.rs diff --git a/src/k8s.rs b/src/k8s.rs index caefea7..7150b66 100644 --- a/src/k8s.rs +++ b/src/k8s.rs @@ -257,6 +257,42 @@ pub async fn unassign_wireguard_config( Ok(()) } +/// Retrieve the WireGuard config assigned to the specified EC2 instance +/// +/// This function finds the first WireGuard client configuration, stored as a +/// Kubernetes Secret resource, associated with the specified EC2 instance. +/// +/// If multiple WireGuard configs are assigned to an EC2 instance, only the +/// first one returned by the Kubernetes list query is returned. +pub async fn get_wireguard_config( + instance_id: &str, +) -> Result, kube::Error> { + let client = Client::try_default().await?; + let secrets: Api = Api::default_namespaced(client); + let lp = ListParams::default() + .fields("type=dynk8s.du5t1n.me/wireguard-config") + .labels(&format!("dynk8s.du5t1n.me/ec2-instance-id={}", instance_id)); + for s in secrets.list(&lp).await? { + if let Some(data) = s.data { + match data.get("wireguard-config") { + Some(s) => match String::from_utf8(s.0.clone()) { + Ok(s) => return Ok(Some(s)), + Err(e) => { + error!("Invalid WireGuard configuration: {}", e); + } + }, + None => { + error!(concat!( + "Invalid WireGuard configuration: ", + "missing wireguard-config property" + )); + } + }; + } + } + Ok(None) +} + /// Generate and store a bootstrap token for the specified EC2 instance /// /// This function generates a new bootstrap token and stores it as a Kubernetes diff --git a/src/main.rs b/src/main.rs index 463e3cf..374ab41 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,6 +12,7 @@ fn rocket() -> _ { "/", rocket::routes![ routes::health::get_health, + routes::wireguard::get_node_wireguard, routes::sns::post_sns_notify, routes::sns::get_sns_notify, routes::sns::put_sns_notify, diff --git a/src/routes/mod.rs b/src/routes/mod.rs index 5f1f2e4..1d72d0c 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -1,3 +1,4 @@ //! Rocket route handlers pub mod health; pub mod sns; +pub mod wireguard; diff --git a/src/routes/wireguard.rs b/src/routes/wireguard.rs new file mode 100644 index 0000000..2dd72f5 --- /dev/null +++ b/src/routes/wireguard.rs @@ -0,0 +1,30 @@ +use crate::k8s::get_wireguard_config; + +#[rocket::get("/wireguard/config/")] +pub async fn get_node_wireguard(instance_id: String) -> Option { + if let Ok(Some(token)) = get_wireguard_config(&instance_id).await { + Some(token) + } else { + None + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::rocket; + use rocket::http::Status; + use rocket::local::blocking::Client; + use rocket::uri; + + #[test] + fn test_get_node_wireguard_404() { + let client = Client::tracked(rocket()).unwrap(); + let res = client + .get(uri!(get_node_wireguard( + instance_id = "i-0a1b2c3d4e5f6f7f8" + ))) + .dispatch(); + assert_eq!(res.status(), Status::NotFound); + } +}