273 lines
8.5 KiB
Rust
273 lines
8.5 KiB
Rust
//! Tests for WireGuard Interface and Peer repository operations.
|
|
|
|
use chrono::Utc;
|
|
use ipnet::IpNet;
|
|
use nx9_wg_core::crypto::{generate_keypair, generate_preshared_key};
|
|
use nx9_wg_core::types::wireguard::{
|
|
Interface, InterfaceRole, Peer, PeerProfile, PeerState, PeerType, WireGuardPublicKey,
|
|
};
|
|
use nx9_wg_db::Store;
|
|
use std::str::FromStr;
|
|
use uuid::Uuid;
|
|
|
|
#[tokio::test]
|
|
async fn test_interface_and_peer_crud_and_cascade() {
|
|
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.clone(),
|
|
public_key: pub_k.clone(),
|
|
listen_port: Some(51820),
|
|
address_v4: IpNet::from_str("10.0.0.1/24").expect("valid cidr"),
|
|
address_v6: Some(IpNet::from_str("fd00::1/64").expect("valid cidr")),
|
|
mtu: Some(1420),
|
|
dns: Some("1.1.1.1, 8.8.8.8".to_string()),
|
|
enabled: true,
|
|
pre_up: None,
|
|
post_up: Some("iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE".to_string()),
|
|
pre_down: None,
|
|
post_down: None,
|
|
created_at: now,
|
|
updated_at: now,
|
|
};
|
|
|
|
store
|
|
.create_interface(&iface)
|
|
.await
|
|
.expect("create_interface");
|
|
|
|
// Lookup interface by ID and name
|
|
let fetched = store
|
|
.get_interface(iface_id)
|
|
.await
|
|
.expect("get_interface")
|
|
.expect("iface found");
|
|
assert_eq!(fetched.name, "wg0");
|
|
assert_eq!(fetched.role, InterfaceRole::Overlay);
|
|
assert_eq!(fetched.listen_port, Some(51820));
|
|
assert_eq!(fetched.address_v4.to_string(), "10.0.0.1/24");
|
|
assert_eq!(fetched.mtu, Some(1420));
|
|
|
|
let by_name = store
|
|
.get_interface_by_name("wg0")
|
|
.await
|
|
.expect("get_by_name")
|
|
.expect("found");
|
|
assert_eq!(by_name.id, iface_id);
|
|
|
|
// Reject duplicate interface name
|
|
let dup_iface = Interface {
|
|
id: Uuid::new_v4(),
|
|
name: "wg0".to_string(),
|
|
role: InterfaceRole::Overlay,
|
|
private_key: priv_k.clone(),
|
|
public_key: pub_k.clone(),
|
|
listen_port: Some(51821),
|
|
address_v4: IpNet::from_str("10.0.1.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,
|
|
};
|
|
assert!(
|
|
store.create_interface(&dup_iface).await.is_err(),
|
|
"duplicate interface name must fail"
|
|
);
|
|
|
|
// Create a peer
|
|
let peer_id = Uuid::new_v4();
|
|
let (peer_priv, peer_pub) = generate_keypair();
|
|
let psk = generate_preshared_key();
|
|
|
|
let peer = Peer {
|
|
id: peer_id,
|
|
interface_id: iface_id,
|
|
name: "phone-alice".to_string(),
|
|
peer_type: PeerType::RoadWarrior,
|
|
state: PeerState::Active,
|
|
public_key: peer_pub.clone(),
|
|
private_key: Some(peer_priv.clone()),
|
|
preshared_key: Some(psk.clone()),
|
|
endpoint: None,
|
|
allowed_ips: "10.0.0.2/32".to_string(),
|
|
server_allowed_ips: Some("10.0.0.2/32".to_string()),
|
|
address_v4: Some(IpNet::from_str("10.0.0.2/32").unwrap()),
|
|
address_v6: None,
|
|
dns: Some("10.0.0.1".to_string()),
|
|
mtu: Some(1420),
|
|
persistent_keepalive: Some(25),
|
|
profile: PeerProfile::FullTunnel,
|
|
expires_at: None,
|
|
last_handshake_at: None,
|
|
created_at: now,
|
|
updated_at: now,
|
|
};
|
|
|
|
store.create_peer(&peer).await.expect("create_peer");
|
|
|
|
// Fetch peer
|
|
let fetched_peer = store
|
|
.get_peer(peer_id)
|
|
.await
|
|
.expect("get_peer")
|
|
.expect("peer found");
|
|
assert_eq!(fetched_peer.name, "phone-alice");
|
|
assert_eq!(fetched_peer.peer_type, PeerType::RoadWarrior);
|
|
assert_eq!(fetched_peer.state, PeerState::Active);
|
|
assert_eq!(fetched_peer.profile, PeerProfile::FullTunnel);
|
|
assert_eq!(fetched_peer.allowed_ips, "10.0.0.2/32");
|
|
assert_eq!(fetched_peer.persistent_keepalive, Some(25));
|
|
|
|
// Lookup peer by name and by public key
|
|
let by_pname = store
|
|
.get_peer_by_name(iface_id, "phone-alice")
|
|
.await
|
|
.expect("by name")
|
|
.expect("found");
|
|
assert_eq!(by_pname.id, peer_id);
|
|
|
|
let by_pubk = store
|
|
.get_peer_by_public_key(iface_id, peer_pub.as_str())
|
|
.await
|
|
.expect("by pubk")
|
|
.expect("found");
|
|
assert_eq!(by_pubk.id, peer_id);
|
|
|
|
// Reject duplicate peer name on same interface
|
|
let dup_pname = Peer {
|
|
id: Uuid::new_v4(),
|
|
interface_id: iface_id,
|
|
name: "phone-alice".to_string(),
|
|
peer_type: PeerType::RoadWarrior,
|
|
state: PeerState::Active,
|
|
public_key: WireGuardPublicKey::new("different_key_123=".to_string()),
|
|
private_key: None,
|
|
preshared_key: None,
|
|
endpoint: None,
|
|
allowed_ips: "10.0.0.3/32".to_string(),
|
|
server_allowed_ips: None,
|
|
address_v4: None,
|
|
address_v6: None,
|
|
dns: None,
|
|
mtu: None,
|
|
persistent_keepalive: None,
|
|
profile: PeerProfile::SplitTunnel,
|
|
expires_at: None,
|
|
last_handshake_at: None,
|
|
created_at: now,
|
|
updated_at: now,
|
|
};
|
|
assert!(
|
|
store.create_peer(&dup_pname).await.is_err(),
|
|
"duplicate peer name on same interface must fail"
|
|
);
|
|
|
|
// Reject peer for non-existent interface (foreign key violation)
|
|
let non_existent_iface_peer = Peer {
|
|
id: Uuid::new_v4(),
|
|
interface_id: Uuid::new_v4(),
|
|
name: "orphan-peer".to_string(),
|
|
peer_type: PeerType::RoadWarrior,
|
|
state: PeerState::Active,
|
|
public_key: WireGuardPublicKey::new("orphan_key_123=".to_string()),
|
|
private_key: None,
|
|
preshared_key: None,
|
|
endpoint: None,
|
|
allowed_ips: "10.0.0.4/32".to_string(),
|
|
server_allowed_ips: None,
|
|
address_v4: None,
|
|
address_v6: None,
|
|
dns: None,
|
|
mtu: None,
|
|
persistent_keepalive: None,
|
|
profile: PeerProfile::SplitTunnel,
|
|
expires_at: None,
|
|
last_handshake_at: None,
|
|
created_at: now,
|
|
updated_at: now,
|
|
};
|
|
assert!(
|
|
store.create_peer(&non_existent_iface_peer).await.is_err(),
|
|
"peer for non-existent interface must fail foreign key constraint"
|
|
);
|
|
|
|
// Test peer state transition: active -> disabled -> revoked
|
|
store
|
|
.set_peer_state(peer_id, PeerState::Disabled)
|
|
.await
|
|
.expect("set disabled");
|
|
let disabled = store.get_peer(peer_id).await.expect("get").expect("peer");
|
|
assert_eq!(disabled.state, PeerState::Disabled);
|
|
|
|
store
|
|
.set_peer_state(peer_id, PeerState::Revoked)
|
|
.await
|
|
.expect("set revoked");
|
|
let revoked = store.get_peer(peer_id).await.expect("get").expect("peer");
|
|
assert_eq!(revoked.state, PeerState::Revoked);
|
|
|
|
// Test update_peer_handshake
|
|
let handshake_time = Utc::now().naive_utc();
|
|
store
|
|
.update_peer_handshake(peer_id, handshake_time)
|
|
.await
|
|
.expect("update handshake");
|
|
let after_hs = store.get_peer(peer_id).await.expect("get").expect("peer");
|
|
assert!(after_hs.last_handshake_at.is_some());
|
|
|
|
// Test list_peers_for_interface
|
|
let peer_list = store
|
|
.list_peers_for_interface(iface_id)
|
|
.await
|
|
.expect("list peers");
|
|
assert_eq!(peer_list.len(), 1);
|
|
|
|
// Test upstream interface with listen_port = None
|
|
let upstream_id = Uuid::new_v4();
|
|
let upstream_iface = Interface {
|
|
id: upstream_id,
|
|
name: "proton0".to_string(),
|
|
role: InterfaceRole::Upstream,
|
|
private_key: priv_k.clone(),
|
|
public_key: pub_k.clone(),
|
|
listen_port: None,
|
|
address_v4: IpNet::from_str("10.2.0.2/32").unwrap(),
|
|
address_v6: None,
|
|
mtu: Some(1420),
|
|
dns: Some("10.2.0.1".to_string()),
|
|
enabled: true,
|
|
pre_up: None,
|
|
post_up: None,
|
|
pre_down: None,
|
|
post_down: None,
|
|
created_at: now,
|
|
updated_at: now,
|
|
};
|
|
store
|
|
.create_interface(&upstream_iface)
|
|
.await
|
|
.expect("create upstream interface");
|
|
let fetched_upstream = store
|
|
.get_interface(upstream_id)
|
|
.await
|
|
.expect("get")
|
|
.expect("upstream found");
|
|
assert_eq!(fetched_upstream.name, "proton0");
|
|
assert_eq!(fetched_upstream.role, InterfaceRole::Upstream);
|
|
assert_eq!(fetched_upstream.listen_port, None);
|
|
}
|