- Prevent 'nx9-wg version' from creating data directories by avoiding database initialization. - Create parent directories when an explicit --database path is provided. - Redact printed generated administrator passwords; announce file path or redact instead. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
151 lines
4.5 KiB
Rust
151 lines
4.5 KiB
Rust
//! Integration tests for Phase 5 Network Engine, Route Management, and nftables ruleset builder.
|
|
|
|
use chrono::Utc;
|
|
use ipnet::IpNet;
|
|
use nx9_wg_core::types::firewall::{
|
|
FirewallAction, FirewallDirection, FirewallProtocol, FirewallRule,
|
|
};
|
|
use nx9_wg_core::types::network::Route;
|
|
use nx9_wg_network::{NetworkEngine, NftablesRulesetBuilder, SimulatedNetworkEngine};
|
|
use std::net::IpAddr;
|
|
use std::str::FromStr;
|
|
use uuid::Uuid;
|
|
|
|
#[tokio::test]
|
|
async fn test_network_engine_routes_and_firewall_lifecycle() {
|
|
let engine = SimulatedNetworkEngine::new();
|
|
let now = Utc::now().naive_utc();
|
|
|
|
// 1. Sync routes
|
|
let r1 = Route {
|
|
id: Uuid::new_v4(),
|
|
network_id: None,
|
|
interface_id: None,
|
|
destination: IpNet::from_str("192.168.10.0/24").unwrap(),
|
|
gateway: Some(IpAddr::from_str("10.0.0.1").unwrap()),
|
|
interface_name: Some("wg0".to_string()),
|
|
metric: Some(100),
|
|
enabled: true,
|
|
description: None,
|
|
created_at: now,
|
|
updated_at: now,
|
|
};
|
|
|
|
let r2 = Route {
|
|
id: Uuid::new_v4(),
|
|
network_id: None,
|
|
interface_id: None,
|
|
destination: IpNet::from_str("192.168.20.0/24").unwrap(),
|
|
gateway: None,
|
|
interface_name: Some("wg0".to_string()),
|
|
metric: None,
|
|
enabled: false, // disabled route should not be synchronized
|
|
description: None,
|
|
created_at: now,
|
|
updated_at: now,
|
|
};
|
|
|
|
engine.sync_routes(&[r1, r2]).await.expect("sync routes");
|
|
|
|
// 2. Sync firewall rules & NAT
|
|
let fw1 = FirewallRule {
|
|
id: Uuid::new_v4(),
|
|
name: "Allow WireGuard Port".to_string(),
|
|
interface_id: None,
|
|
peer_id: None,
|
|
direction: FirewallDirection::In,
|
|
action: FirewallAction::Accept,
|
|
protocol: FirewallProtocol::Udp,
|
|
source: None,
|
|
destination: None,
|
|
source_port: None,
|
|
destination_port: Some(51820),
|
|
port_range: None,
|
|
priority: 1,
|
|
enabled: true,
|
|
description: None,
|
|
created_at: now,
|
|
updated_at: now,
|
|
};
|
|
|
|
let subnets = vec![
|
|
IpNet::from_str("10.0.0.0/24").unwrap(),
|
|
IpNet::from_str("fd00::/64").unwrap(),
|
|
];
|
|
|
|
engine
|
|
.sync_firewall(&[fw1], true, &subnets)
|
|
.await
|
|
.expect("sync firewall");
|
|
|
|
let ruleset = engine
|
|
.get_active_nftables_ruleset()
|
|
.await
|
|
.expect("get ruleset");
|
|
|
|
assert!(ruleset.contains("table inet nx9_wg"));
|
|
assert!(ruleset.contains("udp dport 51820 accept"));
|
|
assert!(ruleset.contains("ip saddr 10.0.0.0/24 oifname != \"wg*\" masquerade"));
|
|
assert!(ruleset.contains("ip6 saddr fd00::/64 oifname != \"wg*\" masquerade"));
|
|
|
|
// 3. IP Forwarding inspection
|
|
let status = engine.get_forwarding_status().await.expect("forwarding");
|
|
assert!(status.ipv4_enabled);
|
|
}
|
|
|
|
#[test]
|
|
fn test_nftables_builder_ordering_and_rules() {
|
|
let now = Utc::now().naive_utc();
|
|
|
|
let r_prio10 = FirewallRule {
|
|
id: Uuid::new_v4(),
|
|
name: "Low Priority Accept".to_string(),
|
|
interface_id: None,
|
|
peer_id: None,
|
|
direction: FirewallDirection::In,
|
|
action: FirewallAction::Accept,
|
|
protocol: FirewallProtocol::Tcp,
|
|
source: None,
|
|
destination: None,
|
|
source_port: None,
|
|
destination_port: Some(80),
|
|
port_range: None,
|
|
priority: 10,
|
|
enabled: true,
|
|
description: None,
|
|
created_at: now,
|
|
updated_at: now,
|
|
};
|
|
|
|
let r_prio1 = FirewallRule {
|
|
id: Uuid::new_v4(),
|
|
name: "High Priority Drop".to_string(),
|
|
interface_id: None,
|
|
peer_id: None,
|
|
direction: FirewallDirection::In,
|
|
action: FirewallAction::Drop,
|
|
protocol: FirewallProtocol::Tcp,
|
|
source: Some("1.2.3.4".to_string()),
|
|
destination: None,
|
|
source_port: None,
|
|
destination_port: Some(80),
|
|
port_range: None,
|
|
priority: 1,
|
|
enabled: true,
|
|
description: None,
|
|
created_at: now,
|
|
updated_at: now,
|
|
};
|
|
|
|
let subnets = vec![IpNet::from_str("10.0.0.0/24").unwrap()];
|
|
let ruleset = NftablesRulesetBuilder::build(&[r_prio10, r_prio1], false, &subnets);
|
|
|
|
// High priority drop (priority 1) must appear before low priority accept (priority 10)
|
|
let drop_idx = ruleset.find("1.2.3.4").expect("drop rule");
|
|
let accept_idx = ruleset.find("dport 80 accept").expect("accept rule");
|
|
assert!(
|
|
drop_idx < accept_idx,
|
|
"Higher priority rule (1) must appear before lower priority rule (10)"
|
|
);
|
|
}
|