fix: route peer allocation through selected network
This commit is contained in:
1 parent
32a325234a
commit
5599e1b5c8
3 files changed
+170
-6
No files matched your search
@@ -409,3 +409,134 @@ async fn test_list_all_peers_collection_endpoint() {
|
||||
assert_eq!(iface1_peers.len(), 1, "wg1 must return exactly 1 peer");
|
||||
assert_eq!(iface1_peers[0]["name"], "peer-charlie");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_peer_creation_allocates_from_selected_network() {
|
||||
let (app, cookie) = setup_test_app().await;
|
||||
|
||||
// Interface Network (WireGuard transport address space)
|
||||
let create_iface_req = Request::builder()
|
||||
.method("POST")
|
||||
.uri("/api/v1/interfaces")
|
||||
.header(header::COOKIE, &cookie)
|
||||
.header(header::CONTENT_TYPE, "application/json")
|
||||
.body(Body::from(
|
||||
json!({
|
||||
"name": "wg0",
|
||||
"listen_port": 51820,
|
||||
"address_v4": "10.100.0.1/24"
|
||||
})
|
||||
.to_string(),
|
||||
))
|
||||
.unwrap();
|
||||
let resp = app.clone().oneshot(create_iface_req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let iface_val: Value =
|
||||
serde_json::from_slice(&to_bytes(resp.into_body(), usize::MAX).await.unwrap()).unwrap();
|
||||
let iface_id = iface_val["id"].as_str().unwrap().to_string();
|
||||
assert_eq!(iface_val["address_v4"], "10.100.0.1/24");
|
||||
|
||||
// Subnet Network (peer allocation domain)
|
||||
let create_net_req = Request::builder()
|
||||
.method("POST")
|
||||
.uri("/api/v1/networks")
|
||||
.header(header::COOKIE, &cookie)
|
||||
.header(header::CONTENT_TYPE, "application/json")
|
||||
.body(Body::from(
|
||||
json!({
|
||||
"name": "mobile-clients",
|
||||
"cidr": "10.100.2.0/24"
|
||||
})
|
||||
.to_string(),
|
||||
))
|
||||
.unwrap();
|
||||
let resp = app.clone().oneshot(create_net_req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let net_val: Value =
|
||||
serde_json::from_slice(&to_bytes(resp.into_body(), usize::MAX).await.unwrap()).unwrap();
|
||||
let network_id = net_val["id"].as_str().unwrap();
|
||||
assert_eq!(net_val["cidr"], "10.100.2.0/24");
|
||||
|
||||
// Exact production enrollment payload: selected Subnet Network UUID as network_id.
|
||||
let selected_peer_req = Request::builder()
|
||||
.method("POST")
|
||||
.uri(format!("/api/v1/interfaces/{iface_id}/peers"))
|
||||
.header(header::COOKIE, &cookie)
|
||||
.header(header::CONTENT_TYPE, "application/json")
|
||||
.body(Body::from(
|
||||
json!({
|
||||
"name": "sunil-moto-mobile-network-01",
|
||||
"peer_type": "road_warrior",
|
||||
"profile": "full_tunnel",
|
||||
"mtu": 1280,
|
||||
"persistent_keepalive": 25,
|
||||
"dns": "1.1.1.1, 1.0.0.1",
|
||||
"allowed_ips": "0.0.0.0/0, ::/0",
|
||||
"network_id": network_id
|
||||
})
|
||||
.to_string(),
|
||||
))
|
||||
.unwrap();
|
||||
let resp = app.clone().oneshot(selected_peer_req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let selected_peer: Value =
|
||||
serde_json::from_slice(&to_bytes(resp.into_body(), usize::MAX).await.unwrap()).unwrap();
|
||||
let selected_addr = selected_peer["address_v4"].as_str().unwrap();
|
||||
assert_eq!(
|
||||
selected_addr, "10.100.2.1/32",
|
||||
"selected Network must allocate the first host of 10.100.2.0/24, got {selected_addr}"
|
||||
);
|
||||
assert!(
|
||||
selected_addr.starts_with("10.100.2."),
|
||||
"selected Network must allocate from 10.100.2.0/24, got {selected_addr}"
|
||||
);
|
||||
assert!(
|
||||
!selected_addr.starts_with("10.100.0."),
|
||||
"must not allocate from Interface Network 10.100.0.0/24 when a Subnet Network is selected, got {selected_addr}"
|
||||
);
|
||||
assert!(selected_addr.ends_with("/32"));
|
||||
|
||||
// network_id = null preserves existing fallback (Interface Network CIDR)
|
||||
let fallback_peer_req = Request::builder()
|
||||
.method("POST")
|
||||
.uri(format!("/api/v1/interfaces/{iface_id}/peers"))
|
||||
.header(header::COOKIE, &cookie)
|
||||
.header(header::CONTENT_TYPE, "application/json")
|
||||
.body(Body::from(
|
||||
json!({
|
||||
"name": "bob-fallback",
|
||||
"peer_type": "road_warrior",
|
||||
"profile": "full_tunnel",
|
||||
"network_id": null
|
||||
})
|
||||
.to_string(),
|
||||
))
|
||||
.unwrap();
|
||||
let resp = app.clone().oneshot(fallback_peer_req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let fallback_peer: Value =
|
||||
serde_json::from_slice(&to_bytes(resp.into_body(), usize::MAX).await.unwrap()).unwrap();
|
||||
let fallback_addr = fallback_peer["address_v4"].as_str().unwrap();
|
||||
assert!(
|
||||
fallback_addr.starts_with("10.100.0."),
|
||||
"network_id=null must preserve fallback allocation from Interface Network 10.100.0.0/24, got {fallback_addr}"
|
||||
);
|
||||
assert!(
|
||||
!fallback_addr.starts_with("10.100.2."),
|
||||
"network_id=null must not allocate from a Subnet Network, got {fallback_addr}"
|
||||
);
|
||||
assert!(fallback_addr.ends_with("/32"));
|
||||
|
||||
// WireGuard interface address space is unchanged
|
||||
let get_iface_req = Request::builder()
|
||||
.uri(format!("/api/v1/interfaces/{iface_id}"))
|
||||
.header(header::COOKIE, &cookie)
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
let resp = app.oneshot(get_iface_req).await.unwrap();
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let iface_after: Value =
|
||||
serde_json::from_slice(&to_bytes(resp.into_body(), usize::MAX).await.unwrap()).unwrap();
|
||||
assert_eq!(iface_after["name"], "wg0");
|
||||
assert_eq!(iface_after["address_v4"], "10.100.0.1/24");
|
||||
}
|
||||
Reference in new issue
Block a user