feat(cli): implement Wave 4 — aura binary (PKI, server/client, admin, bench)
aura-cli: clap command tree (pki init/issue-server/issue-client/revoke/list,
server, client, route add/list/remove, status, bench-crypto); TOML config with
~ expansion and split-tunnel rules -> RouteTable; JSON-over-Unix-socket admin
IPC; server/client data paths wiring transport + tunnel (TUN run needs root).
config/{server,client}.toml.example. 15 tests (pki roundtrip, config parse,
admin-socket roundtrip, loopback connection). Verified the real binary: --help,
bench-crypto, and a full CA->server->client cert workflow.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
//! `aura client`: connect to an Aura server and route host traffic through the tunnel.
|
||||
//!
|
||||
//! ## v1 data path
|
||||
//! 1. Load `client.toml`, read the `[pki]` PEM files, build [`aura_proto::ClientConfig`].
|
||||
//! 2. Build a shared [`RouteTable`] from `[tunnel.split]` (default action + direct/vpn CIDR rules);
|
||||
//! record domain rules for resolution.
|
||||
//! 3. [`AuraClient::connect`] to `[client] server_addr`, presenting `[client] sni` as the outer
|
||||
//! (mimicry) hostname.
|
||||
//! 4. Resolve any split-tunnel domain rules via [`AuraDns`] into host routes (best-effort).
|
||||
//! 5. Create the local TUN ([`AuraTun::create`]) on `[tunnel] local_ip/prefix` and run
|
||||
//! [`AuraRouter`] to bridge the TUN and the connection.
|
||||
//! 6. Start the admin IPC listener over the same shared [`RouteTable`] + [`Stats`].
|
||||
//!
|
||||
//! ## Privilege / scope notes (NOT auto-tested)
|
||||
//! * Creating the TUN ([`AuraTun::create`]) needs **root**; the live router path runs only in a
|
||||
//! privileged execution, so it is not covered by unit tests (the loopback test covers the
|
||||
//! connection path short of the TUN).
|
||||
//! * Domain resolution performs real DNS queries and so is not unit-tested either.
|
||||
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
use anyhow::Context;
|
||||
use aura_transport::AuraClient;
|
||||
use aura_tunnel::{AuraDns, AuraRouter, AuraTun};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::admin::{self, AdminState, Stats};
|
||||
use crate::config::ClientConfigFile;
|
||||
|
||||
/// Entry point for `aura client --config <PATH>` (and optional `--admin-socket`).
|
||||
pub async fn run(config_path: &Path, admin_socket: &str) -> anyhow::Result<()> {
|
||||
let cfg = ClientConfigFile::load(config_path)?;
|
||||
let server_addr = cfg.server_socket_addr()?;
|
||||
let local_ip = cfg.local_ip()?;
|
||||
let proto_cfg = cfg.to_proto()?;
|
||||
let (table, domains) = cfg.build_route_table()?;
|
||||
|
||||
tracing::info!(
|
||||
name = %cfg.client.name,
|
||||
%server_addr,
|
||||
sni = %cfg.client.sni,
|
||||
%local_ip,
|
||||
dns = ?cfg.tunnel.dns,
|
||||
mimicry_padding = cfg.mimicry.padding,
|
||||
"starting Aura client"
|
||||
);
|
||||
|
||||
// Snapshot the configured CIDR rules for the admin mirror before moving the table behind the
|
||||
// lock. (We rebuild the parsed CIDRs from the config rather than reaching into the table.)
|
||||
let cidr_mirror = collect_cidr_rules(&cfg);
|
||||
|
||||
let routes = Arc::new(RwLock::new(table));
|
||||
let stats = Arc::new(Stats::new());
|
||||
|
||||
// Connect (outer QUIC + inner Aura mutual-auth handshake).
|
||||
let conn = AuraClient::connect(server_addr, &cfg.client.sni, proto_cfg)
|
||||
.await
|
||||
.context("connecting to Aura server")?;
|
||||
let peer = conn.peer_id().map(str::to_owned);
|
||||
stats.set_peer_id(peer.clone());
|
||||
tracing::info!(peer = ?peer, "connected and authenticated to server");
|
||||
|
||||
// Resolve split-tunnel domain rules into host routes (best-effort; failures are logged).
|
||||
if !domains.is_empty() {
|
||||
match AuraDns::new(Arc::clone(&routes)).await {
|
||||
Ok(mut dns) => {
|
||||
for (domain, action) in &domains {
|
||||
match dns.resolve_and_register(domain, *action).await {
|
||||
Ok(ips) => {
|
||||
tracing::info!(
|
||||
domain,
|
||||
count = ips.len(),
|
||||
?action,
|
||||
"resolved domain rule"
|
||||
)
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::warn!(domain, error = %e, "failed to resolve domain rule")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => tracing::warn!(error = %e, "could not start resolver for domain rules"),
|
||||
}
|
||||
}
|
||||
|
||||
// Admin IPC over the shared table/stats.
|
||||
let admin_state = AdminState::new(
|
||||
Arc::clone(&routes),
|
||||
Arc::clone(&stats),
|
||||
cidr_mirror,
|
||||
domains.clone(),
|
||||
);
|
||||
let admin_path = admin_socket.to_string();
|
||||
tokio::spawn(async move {
|
||||
if let Err(e) = admin::serve(&admin_path, admin_state).await {
|
||||
tracing::error!(error = %e, "admin IPC listener exited");
|
||||
}
|
||||
});
|
||||
|
||||
// Create the TUN and run the router (needs root).
|
||||
let tun = AuraTun::create(
|
||||
&cfg.tunnel.tun_name,
|
||||
local_ip,
|
||||
cfg.tunnel.prefix,
|
||||
cfg.tunnel.mtu,
|
||||
)
|
||||
.await
|
||||
.context("creating TUN device (needs root)")?;
|
||||
tracing::info!(tun = %cfg.tunnel.tun_name, "TUN device up; routing traffic");
|
||||
|
||||
let router = AuraRouter::new(tun, routes, conn.into_dyn());
|
||||
router.run().await.context("router run loop")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Re-parse the `[tunnel.split]` CIDR rules into `(IpNetwork, RouteAction)` pairs for the admin
|
||||
/// mirror. Invalid CIDRs were already rejected by [`ClientConfigFile::build_route_table`], so this
|
||||
/// silently skips any that somehow fail to re-parse.
|
||||
fn collect_cidr_rules(
|
||||
cfg: &ClientConfigFile,
|
||||
) -> Vec<(ipnetwork::IpNetwork, aura_tunnel::RouteAction)> {
|
||||
use aura_tunnel::RouteAction;
|
||||
let mut out = Vec::new();
|
||||
for (rules, action) in [
|
||||
(&cfg.tunnel.split.direct, RouteAction::Direct),
|
||||
(&cfg.tunnel.split.vpn, RouteAction::Vpn),
|
||||
] {
|
||||
for rule in rules {
|
||||
if let Some(cidr) = &rule.cidr {
|
||||
if let Ok(net) = cidr.parse() {
|
||||
out.push((net, action));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
Reference in New Issue
Block a user