Files
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

140 lines
4.5 KiB
Rust

//! Integration test for [`aura_cli::no_logs::redacting_field_formatter`].
//!
//! The production code installs the same `FormatFields` against the global subscriber via
//! [`aura_cli::no_logs::init_filtered_tracing`]. We cannot use a global subscriber inside a unit
//! test (it stays installed for the whole test binary and leaks across tests). Instead we mount
//! the same formatter on a *per-test* subscriber using the `with_default` guard, route output
//! through an in-memory writer, and assert that the redacted field values are absent while
//! non-redacted fields still appear.
use std::io::Write;
use std::sync::{Arc, Mutex};
use tracing_subscriber::fmt::MakeWriter;
/// An in-memory writer factory: each `make_writer` returns a guard that locks the shared `Vec<u8>`
/// and writes into it. Cheap enough for one-shot test setups.
#[derive(Clone, Default)]
struct BufWriter {
inner: Arc<Mutex<Vec<u8>>>,
}
impl BufWriter {
fn snapshot(&self) -> String {
let guard = self.inner.lock().unwrap();
String::from_utf8(guard.clone()).expect("utf8")
}
}
impl<'a> MakeWriter<'a> for BufWriter {
type Writer = BufWriterGuard;
fn make_writer(&'a self) -> Self::Writer {
BufWriterGuard {
inner: Arc::clone(&self.inner),
}
}
}
struct BufWriterGuard {
inner: Arc<Mutex<Vec<u8>>>,
}
impl Write for BufWriterGuard {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let mut g = self.inner.lock().unwrap();
g.extend_from_slice(buf);
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
/// Drive `tracing::info!` with one redacted and one safe field, route output through the redacting
/// formatter into a buffer, and assert the redacted value is absent while the safe value is present.
#[test]
fn no_logs_drops_peer_id_field_from_output() {
let buf = BufWriter::default();
let subscriber = tracing_subscriber::fmt()
.with_writer(buf.clone())
.with_ansi(false)
.fmt_fields(aura_cli::no_logs::redacting_field_formatter())
.finish();
tracing::subscriber::with_default(subscriber, || {
// peer_id (redacted) and bytes (kept) — the message itself ("client accepted") is fine.
tracing::info!(
peer_id = "SECRET-CLIENT-ID-12345",
bytes = 64u64,
"client accepted"
);
});
let out = buf.snapshot();
assert!(
!out.contains("SECRET-CLIENT-ID-12345"),
"redacted peer_id leaked: {out}"
);
assert!(
out.contains("bytes=64"),
"non-redacted field missing: {out}"
);
assert!(out.contains("client accepted"), "message missing: {out}");
}
/// Every spec-listed identifier is suppressed in one go.
#[test]
fn no_logs_drops_every_listed_identifier() {
let buf = BufWriter::default();
let subscriber = tracing_subscriber::fmt()
.with_writer(buf.clone())
.with_ansi(false)
.fmt_fields(aura_cli::no_logs::redacting_field_formatter())
.finish();
tracing::subscriber::with_default(subscriber, || {
tracing::info!(
peer_id = "PEERVAL",
client_ip = "CLIPVAL",
source_addr = "SRCVAL",
client_id = "CIDVAL",
local_ip = "LIPVAL",
user = "USERVAL",
id = "IDVAL",
assigned_ip = "ASSVAL",
peer = "PEERVAL2",
bytes = 42u64,
"test"
);
});
let out = buf.snapshot();
for redacted in [
"PEERVAL", "CLIPVAL", "SRCVAL", "CIDVAL", "LIPVAL", "USERVAL", "IDVAL", "ASSVAL",
"PEERVAL2",
] {
assert!(
!out.contains(redacted),
"value '{redacted}' leaked into output: {out}"
);
}
// bytes is a kept field — must still be visible.
assert!(out.contains("bytes=42"), "kept field missing: {out}");
}
/// Sanity: the unfiltered default formatter (no `fmt_fields` swap) DOES emit the peer_id value —
/// this guards against accidentally enabling redaction by default for non-`no_logs` deployments.
#[test]
fn default_formatter_keeps_peer_id() {
let buf = BufWriter::default();
let subscriber = tracing_subscriber::fmt()
.with_writer(buf.clone())
.with_ansi(false)
.finish();
tracing::subscriber::with_default(subscriber, || {
tracing::info!(peer_id = "SHOULD-APPEAR", "ev");
});
let out = buf.snapshot();
assert!(out.contains("SHOULD-APPEAR"), "default did not emit: {out}");
}