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>
This commit is contained in:
xah30
2026-05-27 12:14:57 +03:00
parent 7d711d8938
commit 8f0cf1f017
15 changed files with 1749 additions and 23 deletions
+77
View File
@@ -114,6 +114,12 @@ pub struct ServerSection {
/// server keeps its current credentials.
#[serde(default)]
pub run_as: Option<String>,
/// When `true`, the tracing layer suppresses event fields that would identify a peer
/// (`peer_id`, `client_ip`, `source_addr`, `client_id`, `local_ip`, `user`, `id`). The event
/// itself still fires (for operational counters like rx/tx packets), but no identifier is
/// written. Default `false` (verbose). See [`crate::no_logs`].
#[serde(default)]
pub no_logs: bool,
}
/// `[server.nat]` section: v2 auto-NAT configuration. See [`crate::nat`] for the apply / rollback
@@ -191,6 +197,17 @@ pub struct ClientSection {
/// (or already non-root) the client keeps its current credentials. See [`crate::privdrop`].
#[serde(default)]
pub run_as: Option<String>,
/// When `true`, the tracing layer suppresses event fields that would identify the user
/// (`peer_id`, `client_ip`, `source_addr`, `client_id`, `local_ip`, `user`, `id`). Default
/// `false` (verbose). See [`crate::no_logs`].
#[serde(default)]
pub no_logs: bool,
/// Optional fallback server addresses tried in random order if the primary `server_addr`
/// fails on every transport. Each entry is an IP (or `IP:port`); the per-transport ports come
/// from `[transport]` as for the primary endpoint. Empty / omitted means no fallbacks.
/// See [`crate::dial_targets::build_dial_targets`].
#[serde(default)]
pub bridges: Vec<String>,
}
/// `[tunnel]` section of `client.toml`.
@@ -346,6 +363,13 @@ pub struct TransportSection {
pub masquerade: bool,
/// `[transport.masks]`: daily protocol-mask rotation knobs.
pub masks: MasksSection,
/// `[transport.knock]`: UDP port-knocking (probe-resistance) toggle. When `enabled`, the UDP
/// transport demands a 16-byte HMAC prefix on every HS datagram derived from the shared
/// `knock_secret_source`. Default `enabled = false` for backwards compat.
pub knock: KnockSection,
/// `[transport.cover]`: idle-time cover-traffic injection on the UDP transport. Default
/// `enabled = false`.
pub cover: CoverSection,
}
impl Default for TransportSection {
@@ -358,6 +382,59 @@ impl Default for TransportSection {
obfuscate: true,
masquerade: true,
masks: MasksSection::default(),
knock: KnockSection::default(),
cover: CoverSection::default(),
}
}
}
/// `[transport.knock]` section: UDP port-knocking (probe-resistance) toggle. When `enabled`, the
/// UDP transport requires a 16-byte HMAC prefix on every HS datagram derived from the shared key.
///
/// `knock_secret_source` selects how the 32-byte key is computed:
///
/// * `"ca_fingerprint"` (default): `SHA-256(CA-cert-DER)`. Both peers can compute this
/// independently from the CA they already trust — no wire coordination needed.
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct KnockSection {
/// Master switch. `false` (the default) keeps backwards compat — no knock prefix is added or
/// validated.
pub enabled: bool,
/// Selector for the shared knock key. Default `"ca_fingerprint"`.
pub knock_secret_source: String,
}
impl Default for KnockSection {
fn default() -> Self {
Self {
enabled: false,
knock_secret_source: "ca_fingerprint".to_string(),
}
}
}
/// `[transport.cover]` section: idle-time cover-traffic injection on the UDP transport. When
/// `enabled`, an established `UdpConnection` periodically injects encrypted `Ping`s if no user
/// DATA was sent in the previous interval, blurring on-wire bursts.
#[derive(Debug, Clone, Deserialize)]
#[serde(default)]
pub struct CoverSection {
/// Master switch. `false` (the default) disables cover traffic.
pub enabled: bool,
/// Mean interval, in milliseconds, between cover-traffic attempts. Default `500`.
pub mean_interval_ms: u64,
/// Uniform jitter fraction applied to `mean_interval_ms` (e.g. `0.5` gives ±50%). Clamped
/// into `[0.0, 1.0)` by the transport layer. Default `0.5`.
pub jitter: f32,
}
impl Default for CoverSection {
fn default() -> Self {
Self {
enabled: false,
mean_interval_ms: 500,
jitter: 0.5,
}
}
}