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:
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user