diff --git a/crates/defguard/src/main.rs b/crates/defguard/src/main.rs index 3c7576a2ee..9917a783b3 100644 --- a/crates/defguard/src/main.rs +++ b/crates/defguard/src/main.rs @@ -21,6 +21,7 @@ use defguard_core::{ limits::update_counts, }, events::{ApiEvent, BidiStreamEvent, GrpcEvent, InternalEvent}, + gateway_config, grpc::{ WorkerState, gateway::{client_state::ClientMap, map::GatewayMap}, @@ -79,6 +80,10 @@ async fn main() -> Result<(), anyhow::Error> { let token = init_vpn_location(&pool, args).await?; println!("{token}"); } + Command::GatewayConfig(args) => { + let config = gateway_config(&pool, args).await?; + println!("{config:#?}"); + } } // return early diff --git a/crates/defguard_common/src/config.rs b/crates/defguard_common/src/config.rs index 2549ce610b..521a5ed17b 100644 --- a/crates/defguard_common/src/config.rs +++ b/crates/defguard_common/src/config.rs @@ -189,6 +189,8 @@ pub enum Command { about = "Add a new VPN location and return a gateway token. Used for automated setup." )] InitVpnLocation(InitVpnLocationArgs), + #[command(about = "Output the gateway gRPC configuration payload for a VPN location by ID.")] + GatewayConfig(GatewayConfigArgs), } #[derive(Args, Debug, Clone)] @@ -209,6 +211,12 @@ pub struct InitVpnLocationArgs { pub id: Option, } +#[derive(Args, Debug, Clone)] +pub struct GatewayConfigArgs { + #[arg(long)] + pub location_id: i64, +} + impl DefGuardConfig { #[must_use] pub fn new() -> Self { @@ -318,6 +326,25 @@ mod tests { DefGuardConfig::command().debug_assert(); } + #[test] + fn test_parse_gateway_config_command() { + let config = DefGuardConfig::parse_from([ + "defguard", + "--secret-key", + "1234567890123456789012345678901234567890123456789012345678901234", + "gateway-config", + "--location-id", + "42", + ]); + + assert!(matches!( + config.cmd, + Some(Command::GatewayConfig(GatewayConfigArgs { + location_id: 42 + })) + )); + } + #[test] fn test_generate_rp_id() { unsafe { diff --git a/crates/defguard_core/src/grpc/gateway/mod.rs b/crates/defguard_core/src/grpc/gateway/mod.rs index ff119fc0fc..7bdc728719 100644 --- a/crates/defguard_core/src/grpc/gateway/mod.rs +++ b/crates/defguard_core/src/grpc/gateway/mod.rs @@ -317,7 +317,7 @@ impl GatewayServer { } } -fn gen_config( +pub(crate) fn gen_config( network: &WireguardNetwork, peers: Vec, maybe_firewall_config: Option, diff --git a/crates/defguard_core/src/lib.rs b/crates/defguard_core/src/lib.rs index 0c4f17c3c4..781407404d 100644 --- a/crates/defguard_core/src/lib.rs +++ b/crates/defguard_core/src/lib.rs @@ -17,7 +17,7 @@ use db::models::{device::DeviceType, wireguard::LocationMfaMode}; use defguard_common::{ VERSION, auth::claims::{Claims, ClaimsType}, - config::{DefGuardConfig, InitVpnLocationArgs, server_config}, + config::{DefGuardConfig, GatewayConfigArgs, InitVpnLocationArgs, server_config}, db::init_db, }; use defguard_mail::Mail; @@ -146,7 +146,10 @@ use self::{ worker::{create_job, create_worker_token, job_status, list_workers, remove_worker}, }, }; -use crate::{db::models::wireguard::ServiceLocationMode, version::IncompatibleComponents}; +use crate::{ + db::models::wireguard::ServiceLocationMode, grpc::gateway::gen_config, + version::IncompatibleComponents, +}; pub mod appstate; pub mod auth; @@ -930,6 +933,51 @@ pub async fn init_vpn_location( Ok(token) } +pub async fn gateway_config( + pool: &PgPool, + args: &GatewayConfigArgs, +) -> Result { + let location_id = args.location_id; + + let mut conn = pool.acquire().await?; + + // fetch specified location + let location = match WireguardNetwork::find_by_id(&mut *conn, location_id).await { + Ok(Some(network)) => network, + Ok(None) => return Err(anyhow!("Location {location_id} not found")), + Err(err) => { + return Err(anyhow!( + "Failed to rerieve location {location_id} with error: {err}" + )); + } + }; + + // get peers + let peers = location + .get_peers(&mut *conn) + .await + .map_err(|err| anyhow!("Failed to get peers for location {location} with error: {err}"))?; + + // prepare firewall config + let maybe_firewall_config = + location + .try_get_firewall_config(&mut conn) + .await + .map_err(|err| { + anyhow!( + "Failed to prepare firewall config for location {location} with error: {err}" + ) + })?; + + // generate config + let mut config = gen_config(&location, peers, maybe_firewall_config); + + // overwrite private key just in case + config.prvkey = "REDACTED".into(); + + Ok(config) +} + pub(crate) fn is_valid_phone_number(number: &str) -> bool { PHONE_NUMBER_REGEX.is_match(number) }