Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
536 changes: 536 additions & 0 deletions AGENT_TUNNEL_IDENTITY_DESIGN.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions devolutions-agent/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ features = [
"Win32_Foundation",
"Win32_Storage_FileSystem",
"Win32_Security",
"Win32_System_EventLog",
"Win32_System_SystemInformation",
"Win32_System_Threading",
"Win32_Security_Cryptography",
Expand Down
1 change: 1 addition & 0 deletions devolutions-agent/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub mod log;
pub mod remote_desktop;
pub mod tunnel;
mod tunnel_helpers;
pub mod verify_tunnel;

#[cfg(windows)]
pub mod session_manager;
Expand Down
93 changes: 93 additions & 0 deletions devolutions-agent/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,31 @@ fn parse_advertise_subnets(value: &str) -> Vec<String> {
.collect()
}

/// Default verify-tunnel timeout (matches what the installer CA hardcodes).
const VERIFY_TUNNEL_DEFAULT_TIMEOUT_SECS: u64 = 10;

/// Parse `verify-tunnel` CLI arguments. Currently supports a single `--timeout
/// <secs>` flag and falls back to [`VERIFY_TUNNEL_DEFAULT_TIMEOUT_SECS`] when
/// absent.
fn parse_verify_tunnel_args(args: &[String]) -> Result<std::time::Duration> {
let mut timeout_secs = VERIFY_TUNNEL_DEFAULT_TIMEOUT_SECS;
let mut index = 0;
while index < args.len() {
match args[index].as_str() {
"--timeout" => {
let value = parse_required_value(args, &mut index, "--timeout")?;
timeout_secs = value.parse::<u64>().context("--timeout must be a positive integer (seconds)")?;
if timeout_secs == 0 {
bail!("--timeout must be > 0");
}
}
unexpected => bail!("unknown argument for verify-tunnel: {unexpected}"),
}
index += 1;
}
Ok(std::time::Duration::from_secs(timeout_secs))
}

fn parse_up_command_args(args: &[String]) -> Result<UpCommand> {
let mut gateway_url = None;
let mut enrollment_token = None;
Expand Down Expand Up @@ -258,6 +283,50 @@ fn main() {
}
});
}
"verify-tunnel" => {
let args: Vec<String> = env::args().skip(2).collect();
let timeout = match parse_verify_tunnel_args(&args) {
Ok(timeout) => timeout,
Err(error) => {
eprintln!("[ERROR] Invalid verify-tunnel arguments: {error:#}");
// Emit a structured unexpected_error triple so the installer CA still
// has something parseable on stderr.
let triple = devolutions_agent::verify_tunnel::ErrorTriple::new(
devolutions_agent::verify_tunnel::ErrorKind::UnexpectedError,
format!("verify-tunnel argument parse error: {error:#}"),
);
triple.emit_to_stderr();
std::process::exit(1);
}
};

let conf_handle = match ConfHandle::init() {
Ok(handle) => handle,
Err(error) => {
let triple = devolutions_agent::verify_tunnel::ErrorTriple::new(
devolutions_agent::verify_tunnel::ErrorKind::UnexpectedError,
format!("failed to load agent configuration: {error:#}"),
);
triple.emit_to_stderr();
std::process::exit(1);
}
};

let rt = tokio::runtime::Runtime::new().expect("failed to create tokio runtime");
let result = rt.block_on(devolutions_agent::verify_tunnel::verify_tunnel(&conf_handle, timeout));

match result {
Ok(()) => {
// Success path: nothing on stderr — installer CA only consumes stderr
// and absence of a JSON triple confirms the verification succeeded.
println!("verify-tunnel: tunnel is reachable and route-advertise round-trip ok");
}
Err(triple) => {
triple.emit_to_stderr();
std::process::exit(1);
}
}
}
"up" => {
let args: Vec<String> = env::args().skip(2).collect();
let command = match parse_up_command_args(&args) {
Expand Down Expand Up @@ -356,6 +425,30 @@ mod tests {
)
}

#[test]
fn parse_verify_tunnel_defaults_to_10s() {
let timeout = parse_verify_tunnel_args(&[]).expect("parse empty");
assert_eq!(timeout, std::time::Duration::from_secs(10));
}

#[test]
fn parse_verify_tunnel_explicit_timeout() {
let timeout = parse_verify_tunnel_args(&["--timeout".to_owned(), "30".to_owned()]).expect("parse");
assert_eq!(timeout, std::time::Duration::from_secs(30));
}

#[test]
fn parse_verify_tunnel_rejects_zero_timeout() {
let err = parse_verify_tunnel_args(&["--timeout".to_owned(), "0".to_owned()]).expect_err("expect rejection");
assert!(format!("{err:#}").contains("> 0"), "{err:#}");
}

#[test]
fn parse_verify_tunnel_rejects_unknown_flag() {
let err = parse_verify_tunnel_args(&["--bogus".to_owned()]).expect_err("expect rejection");
assert!(format!("{err:#}").contains("--bogus"), "{err:#}");
}

#[test]
fn parse_up_command_args_accepts_enrollment_string() {
let jwt = make_jwt(serde_json::json!({
Expand Down
Loading
Loading