8f0cf1f017
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>
140 lines
4.5 KiB
Rust
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}");
|
|
}
|