fix: foreign credential handling and trusted key visibility (#1993)

* fix foreign credential handling
* allow list foreign network trusted keys
* fix(gui): delete removed config-server networks
* fix(web): reset managed instances on first sync
This commit is contained in:
KKRainbow
2026-03-16 22:19:31 +08:00
committed by GitHub
parent e6ac31fb20
commit 8922e7b991
8 changed files with 457 additions and 56 deletions
+205 -33
View File
@@ -193,6 +193,128 @@ fn create_shared_config(
config
}
fn create_generated_credential_config(
admin_inst: &Instance,
inst_name: &str,
ns: Option<&str>,
ipv4: &str,
ipv6: &str,
) -> (TomlConfigLoader, String) {
use base64::Engine as _;
let (cred_id, cred_secret) = admin_inst
.get_global_ctx()
.get_credential_manager()
.generate_credential(vec![], false, vec![], Duration::from_secs(3600));
let privkey_bytes: [u8; 32] = base64::prelude::BASE64_STANDARD
.decode(&cred_secret)
.unwrap()
.try_into()
.unwrap();
let private = x25519_dalek::StaticSecret::from(privkey_bytes);
let config = TomlConfigLoader::default();
config.set_inst_name(inst_name.to_owned());
config.set_netns(ns.map(|s| s.to_owned()));
config.set_ipv4(Some(ipv4.parse().unwrap()));
config.set_ipv6(Some(ipv6.parse().unwrap()));
config.set_listeners(vec![]);
config.set_network_identity(NetworkIdentity::new_credential(
admin_inst
.get_global_ctx()
.get_network_identity()
.network_name
.clone(),
));
config.set_secure_mode(Some(generate_secure_mode_config_with_key(&private)));
(config, cred_id)
}
async fn wait_ping_reachability(src_ns: &str, dst_ip: &str, reachable: bool, timeout: Duration) {
wait_for_condition(
|| async {
let ping_result = ping_test(src_ns, dst_ip, None).await;
if reachable {
ping_result
} else {
!ping_result
}
},
timeout,
)
.await;
}
async fn wait_route_presence_on_admins(
admin_a_inst: &Instance,
admin_c_inst: &Instance,
peer_id: u32,
should_exist: bool,
timeout: Duration,
) {
wait_for_condition(
|| async {
let admin_a_routes = admin_a_inst.get_peer_manager().list_routes().await;
let admin_c_routes = admin_c_inst.get_peer_manager().list_routes().await;
let admin_a_has = admin_a_routes.iter().any(|r| r.peer_id == peer_id);
let admin_c_has = admin_c_routes.iter().any(|r| r.peer_id == peer_id);
if should_exist {
admin_a_has || admin_c_has
} else {
!admin_a_has && !admin_c_has
}
},
timeout,
)
.await;
}
async fn assert_shared_visibility_stable(
admin_a_inst: &Instance,
admin_c_inst: &Instance,
peer_id: u32,
peer_ip: &str,
should_exist: bool,
label: &str,
) {
for _ in 0..5 {
let admin_a_routes = admin_a_inst.get_peer_manager().list_routes().await;
let admin_c_routes = admin_c_inst.get_peer_manager().list_routes().await;
let admin_a_has = admin_a_routes.iter().any(|r| r.peer_id == peer_id);
let admin_c_has = admin_c_routes.iter().any(|r| r.peer_id == peer_id);
if should_exist {
assert!(
admin_a_has || admin_c_has,
"{} should exist via shared path but routes are admin_a={:?} admin_c={:?}",
label,
admin_a_routes.iter().map(|r| r.peer_id).collect::<Vec<_>>(),
admin_c_routes.iter().map(|r| r.peer_id).collect::<Vec<_>>()
);
} else {
assert!(
!admin_a_has && !admin_c_has,
"{} should be absent via shared path but routes are admin_a={:?} admin_c={:?}",
label,
admin_a_routes.iter().map(|r| r.peer_id).collect::<Vec<_>>(),
admin_c_routes.iter().map(|r| r.peer_id).collect::<Vec<_>>()
);
}
let ping_from_admin_a = ping_test("ns_adm", peer_ip, None).await;
let ping_from_admin_c = ping_test("ns_c3", peer_ip, None).await;
if should_exist {
assert!(ping_from_admin_a, "admin_a should reach {}", label);
assert!(ping_from_admin_c, "admin_c should reach {}", label);
} else {
assert!(!ping_from_admin_a, "admin_a should not reach {}", label);
assert!(!ping_from_admin_c, "admin_c should not reach {}", label);
}
tokio::time::sleep(Duration::from_secs(1)).await;
}
}
/// Test 1: Basic credential node connectivity
/// Topology: Admin ← Credential
/// Verifies that a credential node can connect to an admin node and appears in routes
@@ -834,9 +956,10 @@ async fn credential_unknown_rejected() {
/// Regression test: an unknown credential must still be rejected when it first connects via a
/// shared node. If this fails, the shared path is incorrectly admitting the node into the target
/// network's route domain.
#[rstest::rstest]
#[tokio::test]
#[serial_test::serial]
async fn credential_unknown_via_shared_rejected() {
async fn credential_unknown_via_shared_rejected(#[values(true, false)] test_revoke: bool) {
prepare_credential_network();
let admin_a_config =
@@ -877,18 +1000,32 @@ async fn credential_unknown_via_shared_rejected() {
)
.await;
let unknown_config = create_unknown_credential_config(
admin_a_inst
.get_global_ctx()
.get_network_identity()
.network_name
.clone(),
"unknown_d",
Some("ns_c2"),
"10.144.144.5",
"fd00::5/64",
);
let mut unknown_inst = Instance::new(unknown_config);
let (credential_config, credential_id) = if test_revoke {
let (config, cred_id) = create_generated_credential_config(
&admin_a_inst,
"cred_d",
Some("ns_c2"),
"10.144.144.5",
"fd00::5/64",
);
(config, Some(cred_id))
} else {
(
create_unknown_credential_config(
admin_a_inst
.get_global_ctx()
.get_network_identity()
.network_name
.clone(),
"unknown_d",
Some("ns_c2"),
"10.144.144.5",
"fd00::5/64",
),
None,
)
};
let mut unknown_inst = Instance::new(credential_config);
unknown_inst.run().await.unwrap();
unknown_inst
@@ -901,30 +1038,65 @@ async fn credential_unknown_via_shared_rejected() {
println!("unknown_peer_id: {:?}", unknown_peer_id);
for _ in 0..5 {
let admin_a_routes = admin_a_inst.get_peer_manager().list_routes().await;
let admin_c_routes = admin_c_inst.get_peer_manager().list_routes().await;
if test_revoke {
wait_route_presence_on_admins(
&admin_a_inst,
&admin_c_inst,
unknown_peer_id,
true,
Duration::from_secs(30),
)
.await;
wait_ping_reachability("ns_adm", "10.144.144.5", true, Duration::from_secs(20)).await;
wait_ping_reachability("ns_c3", "10.144.144.5", true, Duration::from_secs(20)).await;
assert!(
!admin_a_routes.iter().any(|r| r.peer_id == unknown_peer_id),
"unknown credential unexpectedly appeared in admin_a routes via shared path: {:?}",
admin_a_routes.iter().map(|r| r.peer_id).collect::<Vec<_>>()
);
assert!(
!admin_c_routes.iter().any(|r| r.peer_id == unknown_peer_id),
"unknown credential unexpectedly appeared in admin_c routes via shared path: {:?}",
admin_c_routes.iter().map(|r| r.peer_id).collect::<Vec<_>>()
);
assert!(
!ping_test("ns_adm", "10.144.144.5", None).await,
"admin_a unexpectedly reached unknown credential via shared path"
);
assert!(
!ping_test("ns_c3", "10.144.144.5", None).await,
"admin_c unexpectedly reached unknown credential via shared path"
admin_a_inst
.get_global_ctx()
.get_credential_manager()
.revoke_credential(credential_id.as_ref().unwrap()),
"credential should be revoked successfully"
);
admin_a_inst
.get_global_ctx()
.issue_event(GlobalCtxEvent::CredentialChanged);
tokio::time::sleep(Duration::from_secs(1)).await;
wait_route_presence_on_admins(
&admin_a_inst,
&admin_c_inst,
unknown_peer_id,
false,
Duration::from_secs(30),
)
.await;
wait_ping_reachability("ns_adm", "10.144.144.5", false, Duration::from_secs(5)).await;
wait_ping_reachability("ns_c3", "10.144.144.5", false, Duration::from_secs(5)).await;
unknown_inst
.get_conn_manager()
.add_connector(TcpTunnelConnector::new(
"tcp://10.1.1.2:11010".parse().unwrap(),
));
assert_shared_visibility_stable(
&admin_a_inst,
&admin_c_inst,
unknown_peer_id,
"10.144.144.5",
false,
"revoked credential",
)
.await;
} else {
assert_shared_visibility_stable(
&admin_a_inst,
&admin_c_inst,
unknown_peer_id,
"10.144.144.5",
false,
"unknown credential",
)
.await;
}
println!("drop all");