290 lines
8.5 KiB
Rust
290 lines
8.5 KiB
Rust
//! Tests for Network, Route, and Firewall Rule repositories.
|
|
|
|
use chrono::Utc;
|
|
use ipnet::IpNet;
|
|
use nx9_wg_core::crypto::generate_keypair;
|
|
use nx9_wg_core::types::firewall::{
|
|
FirewallAction, FirewallDirection, FirewallProtocol, FirewallRule,
|
|
};
|
|
use nx9_wg_core::types::network::{Network, Route};
|
|
use nx9_wg_core::types::wireguard::{Interface, InterfaceRole};
|
|
use nx9_wg_db::Store;
|
|
use std::net::IpAddr;
|
|
use std::str::FromStr;
|
|
use uuid::Uuid;
|
|
|
|
#[tokio::test]
|
|
async fn test_network_and_route_crud() {
|
|
let store = Store::connect_in_memory().await.expect("connect");
|
|
store.migrate().await.expect("migrate");
|
|
|
|
let now = Utc::now().naive_utc();
|
|
let net_id = Uuid::new_v4();
|
|
|
|
let net = Network {
|
|
id: net_id,
|
|
name: "Home Lab".to_string(),
|
|
cidr: IpNet::from_str("192.168.10.0/24").expect("cidr"),
|
|
enabled: true,
|
|
description: Some("Internal lab subnet".to_string()),
|
|
created_at: now,
|
|
updated_at: now,
|
|
};
|
|
|
|
store.create_network(&net).await.expect("create_network");
|
|
|
|
let fetched_net = store
|
|
.get_network(net_id)
|
|
.await
|
|
.expect("get")
|
|
.expect("found");
|
|
assert_eq!(fetched_net.name, "Home Lab");
|
|
assert_eq!(fetched_net.cidr.to_string(), "192.168.10.0/24");
|
|
assert!(fetched_net.enabled);
|
|
|
|
// Test routes
|
|
let route_id = Uuid::new_v4();
|
|
let route = Route {
|
|
id: route_id,
|
|
network_id: Some(net_id),
|
|
interface_id: None,
|
|
destination: IpNet::from_str("192.168.10.0/24").expect("dest cidr"),
|
|
gateway: Some(IpAddr::from_str("10.0.0.1").expect("gateway")),
|
|
interface_name: None,
|
|
metric: Some(100),
|
|
enabled: true,
|
|
description: Some("Lab route via wg gateway".to_string()),
|
|
created_at: now,
|
|
updated_at: now,
|
|
};
|
|
|
|
store.create_route(&route).await.expect("create_route");
|
|
|
|
let fetched_route = store
|
|
.get_route(route_id)
|
|
.await
|
|
.expect("get")
|
|
.expect("route found");
|
|
assert_eq!(fetched_route.network_id, Some(net_id));
|
|
assert_eq!(
|
|
fetched_route.gateway,
|
|
Some(IpAddr::from_str("10.0.0.1").unwrap())
|
|
);
|
|
assert_eq!(fetched_route.metric, Some(100));
|
|
|
|
// Enable/disable route
|
|
store
|
|
.set_route_enabled(route_id, false)
|
|
.await
|
|
.expect("disable");
|
|
let disabled_route = store
|
|
.get_route(route_id)
|
|
.await
|
|
.expect("get")
|
|
.expect("route");
|
|
assert!(!disabled_route.enabled);
|
|
|
|
// List routes for network
|
|
let net_routes = store
|
|
.list_routes_for_network(net_id)
|
|
.await
|
|
.expect("list net routes");
|
|
assert_eq!(net_routes.len(), 1);
|
|
|
|
// Deleting network sets route's network_id to NULL (ON DELETE SET NULL)
|
|
store.delete_network(net_id).await.expect("delete network");
|
|
let route_after_net_delete = store
|
|
.get_route(route_id)
|
|
.await
|
|
.expect("get")
|
|
.expect("route");
|
|
assert!(
|
|
route_after_net_delete.network_id.is_none(),
|
|
"network_id must be SET NULL when network is deleted"
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_firewall_rule_crud_and_priority_ordering() {
|
|
let store = Store::connect_in_memory().await.expect("connect");
|
|
store.migrate().await.expect("migrate");
|
|
|
|
let now = Utc::now().naive_utc();
|
|
let iface_id = Uuid::new_v4();
|
|
let (priv_k, pub_k) = generate_keypair();
|
|
|
|
let iface = Interface {
|
|
id: iface_id,
|
|
name: "wg0".to_string(),
|
|
role: InterfaceRole::Overlay,
|
|
private_key: priv_k,
|
|
public_key: pub_k,
|
|
listen_port: Some(51820),
|
|
address_v4: IpNet::from_str("10.0.0.1/24").unwrap(),
|
|
address_v6: None,
|
|
mtu: None,
|
|
dns: None,
|
|
enabled: true,
|
|
pre_up: None,
|
|
post_up: None,
|
|
pre_down: None,
|
|
post_down: None,
|
|
created_at: now,
|
|
updated_at: now,
|
|
};
|
|
store
|
|
.create_interface(&iface)
|
|
.await
|
|
.expect("create interface");
|
|
|
|
let rule1_id = Uuid::new_v4();
|
|
let rule1 = FirewallRule {
|
|
id: rule1_id,
|
|
name: "Allow SSH".to_string(),
|
|
interface_id: Some(iface_id),
|
|
peer_id: None,
|
|
direction: FirewallDirection::In,
|
|
action: FirewallAction::Accept,
|
|
protocol: FirewallProtocol::Tcp,
|
|
source: None,
|
|
destination: None,
|
|
source_port: None,
|
|
destination_port: Some(22),
|
|
port_range: None,
|
|
priority: 50,
|
|
enabled: true,
|
|
description: Some("SSH access".to_string()),
|
|
created_at: now,
|
|
updated_at: now,
|
|
};
|
|
|
|
let rule2_id = Uuid::new_v4();
|
|
let rule2 = FirewallRule {
|
|
id: rule2_id,
|
|
name: "Drop All Other".to_string(),
|
|
interface_id: Some(iface_id),
|
|
peer_id: None,
|
|
direction: FirewallDirection::In,
|
|
action: FirewallAction::Drop,
|
|
protocol: FirewallProtocol::Any,
|
|
source: None,
|
|
destination: None,
|
|
source_port: None,
|
|
destination_port: None,
|
|
port_range: None,
|
|
priority: 100,
|
|
enabled: true,
|
|
description: Some("Default drop".to_string()),
|
|
created_at: now,
|
|
updated_at: now,
|
|
};
|
|
|
|
store
|
|
.create_firewall_rule(&rule2)
|
|
.await
|
|
.expect("create rule2");
|
|
store
|
|
.create_firewall_rule(&rule1)
|
|
.await
|
|
.expect("create rule1");
|
|
|
|
// List rules should order by priority ASC (rule1 priority 50 comes before rule2 priority 100)
|
|
let rules = store.list_firewall_rules().await.expect("list rules");
|
|
assert_eq!(rules.len(), 2);
|
|
assert_eq!(rules[0].id, rule1_id);
|
|
assert_eq!(rules[0].priority, 50);
|
|
assert_eq!(rules[1].id, rule2_id);
|
|
assert_eq!(rules[1].priority, 100);
|
|
|
|
// List rules for interface
|
|
let iface_rules = store
|
|
.list_firewall_rules_for_interface(iface_id)
|
|
.await
|
|
.expect("list iface rules");
|
|
assert_eq!(iface_rules.len(), 2);
|
|
|
|
// Enable/disable rule
|
|
store
|
|
.set_firewall_rule_enabled(rule1_id, false)
|
|
.await
|
|
.expect("disable");
|
|
let disabled = store
|
|
.get_firewall_rule(rule1_id)
|
|
.await
|
|
.expect("get")
|
|
.expect("rule");
|
|
assert!(!disabled.enabled);
|
|
|
|
// Delete rule
|
|
store.delete_firewall_rule(rule1_id).await.expect("delete");
|
|
assert!(
|
|
store
|
|
.get_firewall_rule(rule1_id)
|
|
.await
|
|
.expect("get")
|
|
.is_none()
|
|
);
|
|
|
|
// Peer-specific rule with port range
|
|
let peer_id = Uuid::new_v4();
|
|
let peer = nx9_wg_core::types::wireguard::Peer {
|
|
id: peer_id,
|
|
interface_id: iface_id,
|
|
name: "test-peer-fw".to_string(),
|
|
peer_type: nx9_wg_core::types::wireguard::PeerType::RoadWarrior,
|
|
state: nx9_wg_core::types::wireguard::PeerState::Active,
|
|
public_key: nx9_wg_core::types::wireguard::WireGuardPublicKey::new(
|
|
"testpubkey12345678901234567890123456789012=".to_string(),
|
|
),
|
|
private_key: None,
|
|
preshared_key: None,
|
|
endpoint: None,
|
|
allowed_ips: "10.0.0.2/32".to_string(),
|
|
server_allowed_ips: None,
|
|
address_v4: Some("10.0.0.2/32".parse().unwrap()),
|
|
address_v6: None,
|
|
dns: None,
|
|
mtu: None,
|
|
persistent_keepalive: None,
|
|
profile: nx9_wg_core::types::wireguard::PeerProfile::FullTunnel,
|
|
expires_at: None,
|
|
last_handshake_at: None,
|
|
created_at: now,
|
|
updated_at: now,
|
|
};
|
|
store.create_peer(&peer).await.expect("create peer");
|
|
|
|
let peer_rule_id = Uuid::new_v4();
|
|
let peer_rule = FirewallRule {
|
|
id: peer_rule_id,
|
|
name: "Peer Port Range Rule".to_string(),
|
|
interface_id: Some(iface_id),
|
|
peer_id: Some(peer_id),
|
|
direction: FirewallDirection::In,
|
|
action: FirewallAction::Accept,
|
|
protocol: FirewallProtocol::TcpUdp,
|
|
source: None,
|
|
destination: None,
|
|
source_port: None,
|
|
destination_port: None,
|
|
port_range: Some("8000-8100".to_string()),
|
|
priority: 25,
|
|
enabled: true,
|
|
description: Some("Custom peer range".to_string()),
|
|
created_at: now,
|
|
updated_at: now,
|
|
};
|
|
store
|
|
.create_firewall_rule(&peer_rule)
|
|
.await
|
|
.expect("create peer rule");
|
|
|
|
let peer_rules = store
|
|
.list_firewall_rules_for_peer(peer_id)
|
|
.await
|
|
.expect("list peer rules");
|
|
assert_eq!(peer_rules.len(), 1);
|
|
assert_eq!(peer_rules[0].port_range.as_deref(), Some("8000-8100"));
|
|
assert_eq!(peer_rules[0].protocol, FirewallProtocol::TcpUdp);
|
|
}
|