Files
AuraVPN/crates/aura-cli/tests/cli_server_init.rs
T
xah30 8f0cf1f017 feat(cli): automation bundle + identity-minimization features
Reduces manual setup steps and trims user-identifying data exposed by the
server/client, in the spirit of the deployment story: an operator on the
wire sees less, and the admin types fewer commands.

New CLI subcommands:
- `aura server-init`: one shot — pki init + issue-server + writes a ready
  server.toml with auto-detected egress iface; flags --enable-knock,
  --enable-cover-traffic, --no-nat, --run-as toggle the new transport
  defenses and privilege drop.
- `aura provision-client`: issues a client cert and assembles the full
  bundle (ca.crt + client.crt + client.key + client.toml in one directory)
  ready to hand over to the client device. --id is optional (defaults to
  a fresh UUIDv4, so client identities don't have to encode anything real).

Identity / log minimization:
- `aura pki issue-client --id` is now optional — UUIDv4 by default.
- `[server]/[client] no_logs = true` filters peer_id, client_ip,
  source_addr, client_id, local_ip, user, id, assigned_ip, peer field
  values through a custom tracing FormatFields layer (events still fire
  but the identifying fields are redacted before being written).
- `[client] bridges = [...]`: secondary server addresses; build_dial_targets
  shuffles them after the primary, so blocking one IP doesn't kill the
  client.
- Auto-detect egress iface in [server.nat] (via detect_default_egress_iface);
  egress_iface in config becomes optional with graceful fallback.

Config examples updated; backward-compatible (all new sections optional with
serde defaults). Workspace: 207 tests passed (+22), clippy -D warnings clean,
fmt clean. No new workspace deps.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 12:14:57 +03:00

135 lines
5.3 KiB
Rust

//! Integration tests for [`aura_cli::init::server_init`].
//!
//! Drives the in-process helper directly (no clap parsing, no binary spawn) and asserts that the
//! generated CA + server cert + server.toml exist on disk and parse cleanly. Each switch on the
//! `ServerInitOpts` flips the corresponding section in the rendered TOML.
use std::path::PathBuf;
use aura_cli::config::ServerConfigFile;
use aura_cli::init::{self, ServerInitOpts};
/// Unique temp dir for one test (no `tempfile` dependency in the workspace).
fn temp_dir(tag: &str) -> PathBuf {
let mut dir = std::env::temp_dir();
dir.push(format!(
"aura-cli-server-init-{tag}-{}-{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
std::fs::create_dir_all(&dir).expect("create temp dir");
dir
}
/// Build a baseline options struct with the temp directories pre-filled. Per-test mutations layer
/// on top of this.
fn base_opts(tag: &str) -> (ServerInitOpts, PathBuf) {
let root = temp_dir(tag);
let pki = root.join("pki");
let cfg = root.join("server.toml");
let mut opts = ServerInitOpts::new("vpn.example.com", &pki);
opts.out_config = cfg.clone();
// Force the no_nat path by default — the integration test runner may or may not have a
// detectable default route, so the per-test `egress_iface` / `no_nat` overrides are explicit.
opts.no_nat = true;
(opts, root)
}
/// Happy path: CA, server cert and server.toml all written and the TOML parses back.
#[test]
fn server_init_writes_and_parses() {
let (opts, root) = base_opts("happy");
let report = init::server_init(&opts).expect("server-init succeeds");
assert!(report.ca_cert.exists(), "ca.crt exists");
assert!(report.ca_key.exists(), "ca.key exists");
assert!(report.server_cert.exists(), "server.crt exists");
assert!(report.server_key.exists(), "server.key exists");
assert!(report.server_config.exists(), "server.toml exists");
let cfg = ServerConfigFile::load(&report.server_config).expect("server.toml parses");
assert_eq!(cfg.server.listen, "0.0.0.0:443");
assert_eq!(cfg.tunnel.pool_cidr, "10.7.0.0/24");
assert_eq!(cfg.transport.udp_port, 443);
assert_eq!(cfg.transport.quic_port, 444);
// no-nat was set in the baseline.
assert!(cfg.server.nat.is_none(), "no [server.nat] section");
// knock / cover default to disabled.
assert!(!cfg.transport.knock.enabled);
assert!(!cfg.transport.cover.enabled);
// PKI section points at the generated files.
assert_eq!(cfg.pki.ca_cert, report.ca_cert.to_string_lossy());
// Cleanup is best-effort.
let _ = std::fs::remove_dir_all(&root);
}
/// `--enable-knock` and `--enable-cover-traffic` flip the [transport.*] sections on.
#[test]
fn server_init_enables_anti_surveillance() {
let (mut opts, root) = base_opts("knock");
opts.enable_knock = true;
opts.enable_cover_traffic = true;
let report = init::server_init(&opts).expect("server-init succeeds");
let cfg = ServerConfigFile::load(&report.server_config).expect("parse");
assert!(cfg.transport.knock.enabled, "knock enabled");
assert_eq!(cfg.transport.knock.knock_secret_source, "ca_fingerprint");
assert!(cfg.transport.cover.enabled, "cover enabled");
assert_eq!(cfg.transport.cover.mean_interval_ms, 500);
let _ = std::fs::remove_dir_all(&root);
}
/// `egress_iface = "eth0"` + `no_nat = false` writes a `[server.nat]` section.
#[test]
fn server_init_writes_nat_when_egress_explicit() {
let (mut opts, root) = base_opts("nat");
opts.no_nat = false;
opts.egress_iface = Some("eth0".to_string());
let report = init::server_init(&opts).expect("server-init succeeds");
let cfg = ServerConfigFile::load(&report.server_config).expect("parse");
let nat = cfg.server.nat.expect("[server.nat] present");
assert!(nat.auto, "nat.auto = true");
assert_eq!(nat.egress_iface, "eth0");
let _ = std::fs::remove_dir_all(&root);
}
/// `run_as = "nobody"` ends up in `[server] run_as` and `no_logs` toggles parse cleanly.
#[test]
fn server_init_run_as_and_no_logs_present() {
let (mut opts, root) = base_opts("runas");
opts.run_as = Some("nobody".to_string());
let report = init::server_init(&opts).expect("server-init succeeds");
let cfg = ServerConfigFile::load(&report.server_config).expect("parse");
assert_eq!(cfg.server.run_as.as_deref(), Some("nobody"));
// `no_logs` is emitted with the default `false`.
assert!(!cfg.server.no_logs);
let _ = std::fs::remove_dir_all(&root);
}
/// Without `--force`, re-running over an existing CA errors out cleanly.
#[test]
fn server_init_refuses_to_clobber_without_force() {
let (opts, root) = base_opts("clobber");
init::server_init(&opts).expect("first run succeeds");
// Re-run should fail because the CA already exists.
let err = init::server_init(&opts).unwrap_err().to_string();
assert!(
err.contains("CA already exists") || err.contains("already exists"),
"expected overwrite refusal, got: {err}"
);
// With force the second run succeeds.
let mut forced = opts.clone();
forced.force = true;
let report = init::server_init(&forced).expect("--force overwrites");
assert!(report.ca_cert.exists());
let _ = std::fs::remove_dir_all(&root);
}