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,576 @@
|
||||
//! TOML configuration for the Aura server and client (project §9).
|
||||
//!
|
||||
//! This module defines the serde structs that mirror the `server.toml` / `client.toml` schemas,
|
||||
//! plus the glue that turns them into the runtime types the libraries expect:
|
||||
//!
|
||||
//! * [`expand_tilde`] expands a leading `~` in any path field to the user's home directory (read
|
||||
//! from `$HOME`, falling back to `$USERPROFILE` on Windows).
|
||||
//! * [`ServerConfigFile::load`] / [`ClientConfigFile::load`] read and parse a config file.
|
||||
//! * [`ServerConfigFile::to_proto`] / [`ClientConfigFile::to_proto`] read the PEM files named in
|
||||
//! the `[pki]` table and build [`aura_proto::ServerConfig`] / [`aura_proto::ClientConfig`].
|
||||
//! * [`ClientConfigFile::build_route_table`] turns `[tunnel.split]` into a [`RouteTable`] (CIDR
|
||||
//! rules applied directly; domain rules recorded for later DNS resolution).
|
||||
|
||||
use std::fs;
|
||||
use std::net::SocketAddr;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use anyhow::{anyhow, Context};
|
||||
use aura_tunnel::{RouteAction, RouteTable};
|
||||
use ipnetwork::IpNetwork;
|
||||
use serde::Deserialize;
|
||||
|
||||
// ---- server.toml ----------------------------------------------------------------------------
|
||||
|
||||
/// Top-level `server.toml` document.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct ServerConfigFile {
|
||||
/// `[server]` section: identity and listen socket.
|
||||
pub server: ServerSection,
|
||||
/// `[pki]` section: CA + leaf cert/key file paths.
|
||||
pub pki: PkiSection,
|
||||
/// `[tunnel]` section: address pool, MTU, DNS.
|
||||
pub tunnel: ServerTunnelSection,
|
||||
/// `[mimicry]` section: outer-TLS camouflage knobs.
|
||||
#[serde(default)]
|
||||
pub mimicry: ServerMimicrySection,
|
||||
}
|
||||
|
||||
/// `[server]` section.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct ServerSection {
|
||||
/// Human-readable server name (also the inner-handshake server identity).
|
||||
pub name: String,
|
||||
/// UDP socket to listen on, e.g. `"0.0.0.0:443"`.
|
||||
#[serde(default = "default_listen")]
|
||||
pub listen: String,
|
||||
/// Number of accept workers (advisory in v1).
|
||||
#[serde(default = "default_workers")]
|
||||
pub workers: usize,
|
||||
}
|
||||
|
||||
/// `[tunnel]` section of `server.toml`.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct ServerTunnelSection {
|
||||
/// CIDR of the address pool handed to clients (single shared TUN in v1).
|
||||
pub pool_cidr: String,
|
||||
/// MTU of the server-side TUN.
|
||||
#[serde(default = "default_mtu")]
|
||||
pub mtu: u16,
|
||||
/// DNS server advertised to clients (informational in v1).
|
||||
#[serde(default)]
|
||||
pub dns: Option<String>,
|
||||
}
|
||||
|
||||
/// `[mimicry]` section of `server.toml`.
|
||||
#[derive(Debug, Clone, Default, Deserialize)]
|
||||
pub struct ServerMimicrySection {
|
||||
/// SNI the server expects / presents for outer-TLS camouflage.
|
||||
#[serde(default)]
|
||||
pub sni: Option<String>,
|
||||
/// Whether to enable traffic padding.
|
||||
#[serde(default)]
|
||||
pub padding: bool,
|
||||
}
|
||||
|
||||
// ---- client.toml ----------------------------------------------------------------------------
|
||||
|
||||
/// Top-level `client.toml` document.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct ClientConfigFile {
|
||||
/// `[client]` section: identity and server address.
|
||||
pub client: ClientSection,
|
||||
/// `[pki]` section: CA + leaf cert/key file paths.
|
||||
pub pki: PkiSection,
|
||||
/// `[tunnel]` section: TUN device, addressing, split-tunnel rules.
|
||||
pub tunnel: ClientTunnelSection,
|
||||
/// `[mimicry]` section: outer-TLS camouflage knobs.
|
||||
#[serde(default)]
|
||||
pub mimicry: ClientMimicrySection,
|
||||
}
|
||||
|
||||
/// `[client]` section.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct ClientSection {
|
||||
/// Human-readable client name / id.
|
||||
pub name: String,
|
||||
/// Server UDP socket address, e.g. `"203.0.113.10:443"`.
|
||||
pub server_addr: String,
|
||||
/// Outer-TLS SNI (camouflage hostname) presented to the server.
|
||||
pub sni: String,
|
||||
}
|
||||
|
||||
/// `[tunnel]` section of `client.toml`.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct ClientTunnelSection {
|
||||
/// Requested TUN interface name (advisory on macOS, where the kernel assigns `utunN`).
|
||||
#[serde(default = "default_tun_name")]
|
||||
pub tun_name: String,
|
||||
/// Local IP address assigned to the TUN device.
|
||||
pub local_ip: String,
|
||||
/// Prefix length for the TUN address.
|
||||
#[serde(default = "default_prefix")]
|
||||
pub prefix: u8,
|
||||
/// MTU of the TUN device.
|
||||
#[serde(default = "default_mtu")]
|
||||
pub mtu: u16,
|
||||
/// DNS server used by the tunnel resolver (informational; the system resolver is used in v1).
|
||||
#[serde(default)]
|
||||
pub dns: Option<String>,
|
||||
/// `[tunnel.split]` split-tunnel configuration.
|
||||
#[serde(default)]
|
||||
pub split: SplitSection,
|
||||
}
|
||||
|
||||
/// `[tunnel.split]` section: default action plus direct/vpn override rules.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct SplitSection {
|
||||
/// Default action when no rule matches: `"VPN"` or `"DIRECT"` (case-insensitive).
|
||||
#[serde(default = "default_split_default")]
|
||||
pub default: String,
|
||||
/// Rules forcing matching destinations to egress directly.
|
||||
#[serde(default)]
|
||||
pub direct: Vec<SplitRule>,
|
||||
/// Rules forcing matching destinations through the VPN.
|
||||
#[serde(default)]
|
||||
pub vpn: Vec<SplitRule>,
|
||||
}
|
||||
|
||||
impl Default for SplitSection {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
default: default_split_default(),
|
||||
direct: Vec::new(),
|
||||
vpn: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A single split-tunnel rule: exactly one of `cidr` or `domain`.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct SplitRule {
|
||||
/// A CIDR, e.g. `"192.168.0.0/16"`. Mutually exclusive with `domain`.
|
||||
#[serde(default)]
|
||||
pub cidr: Option<String>,
|
||||
/// A domain, e.g. `"example.com"`. Mutually exclusive with `cidr`.
|
||||
#[serde(default)]
|
||||
pub domain: Option<String>,
|
||||
}
|
||||
|
||||
/// `[mimicry]` section of `client.toml`.
|
||||
#[derive(Debug, Clone, Default, Deserialize)]
|
||||
pub struct ClientMimicrySection {
|
||||
/// Whether to enable traffic padding.
|
||||
#[serde(default)]
|
||||
pub padding: bool,
|
||||
}
|
||||
|
||||
// ---- shared sections ------------------------------------------------------------------------
|
||||
|
||||
/// `[pki]` section shared by both config files: paths to CA cert + this peer's leaf cert/key.
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
pub struct PkiSection {
|
||||
/// Path to the CA certificate PEM (trust anchor).
|
||||
pub ca_cert: String,
|
||||
/// Path to this peer's leaf certificate PEM.
|
||||
pub cert: String,
|
||||
/// Path to this peer's PKCS#8 private key PEM.
|
||||
pub key: String,
|
||||
}
|
||||
|
||||
// ---- defaults -------------------------------------------------------------------------------
|
||||
|
||||
fn default_listen() -> String {
|
||||
"0.0.0.0:443".to_string()
|
||||
}
|
||||
fn default_workers() -> usize {
|
||||
1
|
||||
}
|
||||
fn default_mtu() -> u16 {
|
||||
1420
|
||||
}
|
||||
fn default_prefix() -> u8 {
|
||||
24
|
||||
}
|
||||
fn default_tun_name() -> String {
|
||||
"aura0".to_string()
|
||||
}
|
||||
fn default_split_default() -> String {
|
||||
"VPN".to_string()
|
||||
}
|
||||
|
||||
// ---- ~ expansion ----------------------------------------------------------------------------
|
||||
|
||||
/// Expand a leading `~` (or `~/...`) in a path to the user's home directory.
|
||||
///
|
||||
/// The home directory is read from `$HOME` (Unix) or `$USERPROFILE` (Windows). A path that does
|
||||
/// not begin with `~` is returned unchanged. A bare `~` expands to the home directory itself.
|
||||
pub fn expand_tilde(path: &str) -> PathBuf {
|
||||
if path == "~" {
|
||||
if let Some(home) = home_dir() {
|
||||
return home;
|
||||
}
|
||||
return PathBuf::from(path);
|
||||
}
|
||||
if let Some(rest) = path.strip_prefix("~/") {
|
||||
if let Some(home) = home_dir() {
|
||||
return home.join(rest);
|
||||
}
|
||||
}
|
||||
PathBuf::from(path)
|
||||
}
|
||||
|
||||
/// Best-effort home directory from the environment (no extra crate dependency).
|
||||
fn home_dir() -> Option<PathBuf> {
|
||||
std::env::var_os("HOME")
|
||||
.or_else(|| std::env::var_os("USERPROFILE"))
|
||||
.map(PathBuf::from)
|
||||
.filter(|p| !p.as_os_str().is_empty())
|
||||
}
|
||||
|
||||
/// Read a PEM (or any text) file whose path may begin with `~`.
|
||||
fn read_pem(path: &str) -> anyhow::Result<String> {
|
||||
let resolved = expand_tilde(path);
|
||||
fs::read_to_string(&resolved)
|
||||
.with_context(|| format!("reading PEM file {}", resolved.display()))
|
||||
}
|
||||
|
||||
// ---- loading + conversion -------------------------------------------------------------------
|
||||
|
||||
impl ServerConfigFile {
|
||||
/// Parse a `server.toml` document from a string.
|
||||
pub fn parse(text: &str) -> anyhow::Result<Self> {
|
||||
toml::from_str(text).context("parsing server.toml")
|
||||
}
|
||||
|
||||
/// Load and parse a `server.toml` file (the path itself may begin with `~`).
|
||||
pub fn load(path: &Path) -> anyhow::Result<Self> {
|
||||
let text = fs::read_to_string(path)
|
||||
.with_context(|| format!("reading server config {}", path.display()))?;
|
||||
Self::parse(&text)
|
||||
}
|
||||
|
||||
/// Parse the `[server] listen` address into a [`SocketAddr`].
|
||||
pub fn listen_addr(&self) -> anyhow::Result<SocketAddr> {
|
||||
self.server
|
||||
.listen
|
||||
.parse()
|
||||
.with_context(|| format!("invalid [server] listen '{}'", self.server.listen))
|
||||
}
|
||||
|
||||
/// The server-side TUN address parsed from the `[tunnel] pool_cidr` (the network address).
|
||||
pub fn pool_network(&self) -> anyhow::Result<IpNetwork> {
|
||||
self.tunnel
|
||||
.pool_cidr
|
||||
.parse()
|
||||
.with_context(|| format!("invalid [tunnel] pool_cidr '{}'", self.tunnel.pool_cidr))
|
||||
}
|
||||
|
||||
/// Read the `[pki]` PEM files and build an [`aura_proto::ServerConfig`].
|
||||
pub fn to_proto(&self) -> anyhow::Result<aura_proto::ServerConfig> {
|
||||
Ok(aura_proto::ServerConfig {
|
||||
ca_cert_pem: read_pem(&self.pki.ca_cert)?,
|
||||
server_cert_pem: read_pem(&self.pki.cert)?,
|
||||
server_key_pem: read_pem(&self.pki.key)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ClientConfigFile {
|
||||
/// Parse a `client.toml` document from a string.
|
||||
pub fn parse(text: &str) -> anyhow::Result<Self> {
|
||||
toml::from_str(text).context("parsing client.toml")
|
||||
}
|
||||
|
||||
/// Load and parse a `client.toml` file (the path itself may begin with `~`).
|
||||
pub fn load(path: &Path) -> anyhow::Result<Self> {
|
||||
let text = fs::read_to_string(path)
|
||||
.with_context(|| format!("reading client config {}", path.display()))?;
|
||||
Self::parse(&text)
|
||||
}
|
||||
|
||||
/// Parse the `[client] server_addr` into a [`SocketAddr`].
|
||||
pub fn server_socket_addr(&self) -> anyhow::Result<SocketAddr> {
|
||||
self.client
|
||||
.server_addr
|
||||
.parse()
|
||||
.with_context(|| format!("invalid [client] server_addr '{}'", self.client.server_addr))
|
||||
}
|
||||
|
||||
/// Parse the `[tunnel] local_ip` into an [`std::net::IpAddr`].
|
||||
pub fn local_ip(&self) -> anyhow::Result<std::net::IpAddr> {
|
||||
self.tunnel
|
||||
.local_ip
|
||||
.parse()
|
||||
.with_context(|| format!("invalid [tunnel] local_ip '{}'", self.tunnel.local_ip))
|
||||
}
|
||||
|
||||
/// Read the `[pki]` PEM files and build an [`aura_proto::ClientConfig`].
|
||||
///
|
||||
/// The inner-handshake `server_name` is taken from `[client] sni` so the SAN verified against
|
||||
/// the server certificate matches the camouflage hostname; deployments that separate the two
|
||||
/// can extend this later.
|
||||
pub fn to_proto(&self) -> anyhow::Result<aura_proto::ClientConfig> {
|
||||
Ok(aura_proto::ClientConfig {
|
||||
ca_cert_pem: read_pem(&self.pki.ca_cert)?,
|
||||
client_cert_pem: read_pem(&self.pki.cert)?,
|
||||
client_key_pem: read_pem(&self.pki.key)?,
|
||||
server_name: self.client.sni.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Build a [`RouteTable`] from `[tunnel.split]`.
|
||||
///
|
||||
/// CIDR rules are applied directly. Domain rules are recorded via [`RouteTable::add_domain`]
|
||||
/// (they only become matchable once [`aura_tunnel::AuraDns::resolve_and_register`] resolves
|
||||
/// them into host routes). Returns the table plus the list of `(domain, action)` pairs the
|
||||
/// caller should resolve.
|
||||
pub fn build_route_table(&self) -> anyhow::Result<(RouteTable, Vec<(String, RouteAction)>)> {
|
||||
let default = parse_action(&self.tunnel.split.default)?;
|
||||
let mut table = RouteTable::new(default);
|
||||
let mut domains = Vec::new();
|
||||
|
||||
for (rules, action) in [
|
||||
(&self.tunnel.split.direct, RouteAction::Direct),
|
||||
(&self.tunnel.split.vpn, RouteAction::Vpn),
|
||||
] {
|
||||
for rule in rules {
|
||||
match (&rule.cidr, &rule.domain) {
|
||||
(Some(cidr), None) => {
|
||||
let net: IpNetwork = cidr
|
||||
.parse()
|
||||
.with_context(|| format!("invalid split-tunnel cidr '{cidr}'"))?;
|
||||
table.add_cidr(net, action);
|
||||
}
|
||||
(None, Some(domain)) => {
|
||||
table.add_domain(domain, action);
|
||||
domains.push((domain.clone(), action));
|
||||
}
|
||||
(Some(_), Some(_)) => {
|
||||
return Err(anyhow!(
|
||||
"split-tunnel rule has both 'cidr' and 'domain'; specify exactly one"
|
||||
));
|
||||
}
|
||||
(None, None) => {
|
||||
return Err(anyhow!(
|
||||
"split-tunnel rule has neither 'cidr' nor 'domain'; specify exactly one"
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok((table, domains))
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a `"VPN"` / `"DIRECT"` action string (case-insensitive) into a [`RouteAction`].
|
||||
pub fn parse_action(s: &str) -> anyhow::Result<RouteAction> {
|
||||
match s.trim().to_ascii_lowercase().as_str() {
|
||||
"vpn" => Ok(RouteAction::Vpn),
|
||||
"direct" => Ok(RouteAction::Direct),
|
||||
other => Err(anyhow!(
|
||||
"invalid route action '{other}' (expected 'vpn' or 'direct')"
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::net::IpAddr;
|
||||
|
||||
const SERVER_TOML: &str = r#"
|
||||
[server]
|
||||
name = "edge-1"
|
||||
listen = "0.0.0.0:8443"
|
||||
workers = 4
|
||||
|
||||
[pki]
|
||||
ca_cert = "/etc/aura/ca.crt"
|
||||
cert = "/etc/aura/server.crt"
|
||||
key = "/etc/aura/server.key"
|
||||
|
||||
[tunnel]
|
||||
pool_cidr = "10.7.0.0/24"
|
||||
mtu = 1380
|
||||
dns = "10.7.0.1"
|
||||
|
||||
[mimicry]
|
||||
sni = "cdn.example.com"
|
||||
padding = true
|
||||
"#;
|
||||
|
||||
const CLIENT_TOML: &str = r#"
|
||||
[client]
|
||||
name = "laptop"
|
||||
server_addr = "203.0.113.10:8443"
|
||||
sni = "cdn.example.com"
|
||||
|
||||
[pki]
|
||||
ca_cert = "~/.aura/ca.crt"
|
||||
cert = "~/.aura/client.crt"
|
||||
key = "~/.aura/client.key"
|
||||
|
||||
[tunnel]
|
||||
tun_name = "aura0"
|
||||
local_ip = "10.7.0.2"
|
||||
prefix = 24
|
||||
mtu = 1380
|
||||
|
||||
[tunnel.split]
|
||||
default = "VPN"
|
||||
|
||||
[[tunnel.split.direct]]
|
||||
cidr = "192.168.0.0/16"
|
||||
|
||||
[[tunnel.split.direct]]
|
||||
cidr = "10.0.0.0/8"
|
||||
|
||||
[[tunnel.split.direct]]
|
||||
domain = "intranet.example.com"
|
||||
|
||||
[[tunnel.split.vpn]]
|
||||
cidr = "10.7.0.0/24"
|
||||
|
||||
[mimicry]
|
||||
padding = false
|
||||
"#;
|
||||
|
||||
#[test]
|
||||
fn parses_server_toml() {
|
||||
let cfg = ServerConfigFile::parse(SERVER_TOML).expect("parse server.toml");
|
||||
assert_eq!(cfg.server.name, "edge-1");
|
||||
assert_eq!(cfg.server.workers, 4);
|
||||
assert_eq!(cfg.listen_addr().unwrap().port(), 8443);
|
||||
assert_eq!(cfg.tunnel.mtu, 1380);
|
||||
assert_eq!(cfg.tunnel.pool_cidr, "10.7.0.0/24");
|
||||
assert!(cfg.mimicry.padding);
|
||||
assert_eq!(cfg.mimicry.sni.as_deref(), Some("cdn.example.com"));
|
||||
assert_eq!(cfg.pki.ca_cert, "/etc/aura/ca.crt");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn server_toml_defaults() {
|
||||
let minimal = r#"
|
||||
[server]
|
||||
name = "edge"
|
||||
[pki]
|
||||
ca_cert = "a"
|
||||
cert = "b"
|
||||
key = "c"
|
||||
[tunnel]
|
||||
pool_cidr = "10.7.0.0/24"
|
||||
"#;
|
||||
let cfg = ServerConfigFile::parse(minimal).expect("parse minimal server.toml");
|
||||
assert_eq!(cfg.server.listen, "0.0.0.0:443");
|
||||
assert_eq!(cfg.server.workers, 1);
|
||||
assert_eq!(cfg.tunnel.mtu, 1420);
|
||||
assert!(!cfg.mimicry.padding);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_client_toml() {
|
||||
let cfg = ClientConfigFile::parse(CLIENT_TOML).expect("parse client.toml");
|
||||
assert_eq!(cfg.client.name, "laptop");
|
||||
assert_eq!(cfg.server_socket_addr().unwrap().port(), 8443);
|
||||
assert_eq!(cfg.client.sni, "cdn.example.com");
|
||||
assert_eq!(
|
||||
cfg.local_ip().unwrap(),
|
||||
"10.7.0.2".parse::<IpAddr>().unwrap()
|
||||
);
|
||||
assert_eq!(cfg.tunnel.prefix, 24);
|
||||
assert_eq!(cfg.tunnel.split.direct.len(), 3);
|
||||
assert_eq!(cfg.tunnel.split.vpn.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn builds_route_table_from_split() {
|
||||
let cfg = ClientConfigFile::parse(CLIENT_TOML).expect("parse client.toml");
|
||||
let (table, domains) = cfg.build_route_table().expect("build route table");
|
||||
|
||||
// Default is VPN.
|
||||
assert_eq!(table.default_action(), RouteAction::Vpn);
|
||||
// 192.168.x and 10.x are Direct...
|
||||
assert_eq!(
|
||||
table.classify("192.168.1.1".parse().unwrap()),
|
||||
RouteAction::Direct
|
||||
);
|
||||
assert_eq!(
|
||||
table.classify("10.1.2.3".parse().unwrap()),
|
||||
RouteAction::Direct
|
||||
);
|
||||
// ...but the more-specific 10.7.0.0/24 VPN rule wins inside 10.0.0.0/8.
|
||||
assert_eq!(
|
||||
table.classify("10.7.0.9".parse().unwrap()),
|
||||
RouteAction::Vpn
|
||||
);
|
||||
// An address matching no rule falls back to the default (VPN).
|
||||
assert_eq!(table.classify("8.8.8.8".parse().unwrap()), RouteAction::Vpn);
|
||||
// The domain rule was recorded for later resolution.
|
||||
assert_eq!(domains.len(), 1);
|
||||
assert_eq!(domains[0].0, "intranet.example.com");
|
||||
assert_eq!(domains[0].1, RouteAction::Direct);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expand_tilde_uses_home() {
|
||||
std::env::set_var("HOME", "/home/tester");
|
||||
assert_eq!(
|
||||
expand_tilde("~/.aura/ca.crt"),
|
||||
PathBuf::from("/home/tester/.aura/ca.crt")
|
||||
);
|
||||
assert_eq!(expand_tilde("~"), PathBuf::from("/home/tester"));
|
||||
assert_eq!(expand_tilde("/abs/path"), PathBuf::from("/abs/path"));
|
||||
assert_eq!(expand_tilde("rel/path"), PathBuf::from("rel/path"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shipped_example_configs_parse() {
|
||||
// The example files live at <workspace>/config/. CARGO_MANIFEST_DIR points at the crate
|
||||
// (crates/aura-cli), so go up two levels.
|
||||
let root = Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent()
|
||||
.and_then(Path::parent)
|
||||
.expect("workspace root");
|
||||
let server = std::fs::read_to_string(root.join("config/server.toml.example"))
|
||||
.expect("read server.toml.example");
|
||||
let client = std::fs::read_to_string(root.join("config/client.toml.example"))
|
||||
.expect("read client.toml.example");
|
||||
|
||||
let s = ServerConfigFile::parse(&server).expect("server.toml.example parses");
|
||||
assert!(s.listen_addr().is_ok());
|
||||
assert!(s.pool_network().is_ok());
|
||||
|
||||
let c = ClientConfigFile::parse(&client).expect("client.toml.example parses");
|
||||
assert!(c.server_socket_addr().is_ok());
|
||||
assert!(c.local_ip().is_ok());
|
||||
let (table, domains) = c
|
||||
.build_route_table()
|
||||
.expect("example split builds a route table");
|
||||
assert_eq!(table.default_action(), RouteAction::Vpn);
|
||||
assert!(!domains.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_rule_with_both_cidr_and_domain() {
|
||||
let bad = r#"
|
||||
[client]
|
||||
name = "x"
|
||||
server_addr = "1.2.3.4:443"
|
||||
sni = "a"
|
||||
[pki]
|
||||
ca_cert = "a"
|
||||
cert = "b"
|
||||
key = "c"
|
||||
[tunnel]
|
||||
local_ip = "10.7.0.2"
|
||||
[tunnel.split]
|
||||
default = "VPN"
|
||||
[[tunnel.split.direct]]
|
||||
cidr = "10.0.0.0/8"
|
||||
domain = "x.example.com"
|
||||
"#;
|
||||
let cfg = ClientConfigFile::parse(bad).expect("parse");
|
||||
assert!(cfg.build_route_table().is_err());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user