fix: refresh ACL groups and enable TCP_NODELAY for WebSocket (#2118)

* fix: refresh ACL groups and enable TCP_NODELAY for WebSocket
* add remove_peers to remove list of peer id in ospf route
* fix secure tunnel for unreliable udp tunnel
* fix(web-client): timeout secure tunnel handshake
* fix(web-server): tolerate delayed secure hello
* fix quic endpoint panic
* fix replay check
This commit is contained in:
KKRainbow
2026-04-19 10:37:39 +08:00
committed by GitHub
parent c49c56612b
commit 2db655bd6d
14 changed files with 7824 additions and 1038 deletions
+660 -74
View File
@@ -418,6 +418,32 @@ impl Debug for SyncedRouteInfo {
}
impl SyncedRouteInfo {
fn set_peer_groups(&self, peer_id: PeerId, groups: HashMap<String, Vec<u8>>) {
if groups.is_empty() {
self.group_trust_map.remove(&peer_id);
self.group_trust_map_cache.remove(&peer_id);
return;
}
let group_names = groups.keys().cloned().collect();
self.group_trust_map.insert(peer_id, groups);
self.group_trust_map_cache
.insert(peer_id, Arc::new(group_names));
}
fn get_proof_groups(&self, peer_id: PeerId) -> HashMap<String, Vec<u8>> {
self.group_trust_map
.get(&peer_id)
.map(|groups| {
groups
.iter()
.filter(|(_, proof)| !proof.is_empty())
.map(|(group, proof)| (group.clone(), proof.clone()))
.collect()
})
.unwrap_or_default()
}
fn mark_credential_peer(info: &mut RoutePeerInfo, is_credential_peer: bool) {
let mut feature_flag = info.feature_flag.unwrap_or_default();
feature_flag.is_credential_peer = is_credential_peer;
@@ -439,13 +465,38 @@ impl SyncedRouteInfo {
}
fn remove_peer(&self, peer_id: PeerId) {
tracing::warn!(?peer_id, "remove_peer from synced_route_info");
self.peer_infos.write().remove(&peer_id);
self.raw_peer_infos.remove(&peer_id);
self.conn_map.write().remove(&peer_id);
self.foreign_network.retain(|k, _| k.peer_id != peer_id);
self.group_trust_map.remove(&peer_id);
self.group_trust_map_cache.remove(&peer_id);
self.remove_peers([peer_id]);
}
fn remove_peers<I>(&self, peer_ids: I)
where
I: IntoIterator<Item = PeerId>,
{
let peer_ids: HashSet<_> = peer_ids.into_iter().collect();
if peer_ids.is_empty() {
return;
}
for peer_id in &peer_ids {
tracing::warn!(?peer_id, "remove_peer from synced_route_info");
}
{
let mut peer_infos = self.peer_infos.write();
let mut conn_map = self.conn_map.write();
for peer_id in &peer_ids {
peer_infos.remove(peer_id);
conn_map.remove(peer_id);
}
}
for peer_id in &peer_ids {
self.raw_peer_infos.remove(peer_id);
self.group_trust_map.remove(peer_id);
self.group_trust_map_cache.remove(peer_id);
}
self.foreign_network
.retain(|k, _| !peer_ids.contains(&k.peer_id));
shrink_dashmap(&self.raw_peer_infos, None);
shrink_dashmap(&self.foreign_network, None);
@@ -827,31 +878,20 @@ impl SyncedRouteInfo {
&self,
peer_infos: &[RoutePeerInfo],
local_group_declarations: &[GroupIdentity],
trust_admin_groups_without_proof: bool,
) {
let local_group_declarations = local_group_declarations
.iter()
.map(|g| (g.group_name.as_str(), g.group_secret.as_str()))
.collect::<std::collections::HashMap<&str, &str>>();
let verify_groups = |old_trusted_groups: Option<&HashMap<String, Vec<u8>>>,
info: &RoutePeerInfo|
-> HashMap<String, Vec<u8>> {
let verify_groups = |info: &RoutePeerInfo| -> HashMap<String, Vec<u8>> {
let mut trusted_groups_for_peer: HashMap<String, Vec<u8>> = HashMap::new();
for group_proof in &info.groups {
let name = &group_proof.group_name;
let proof_bytes = group_proof.group_proof.clone();
// If we already trusted this group and the proof hasn't changed, reuse it.
if old_trusted_groups
.and_then(|g| g.get(name))
.map(|old| old == &proof_bytes)
.unwrap_or(false)
{
trusted_groups_for_peer.insert(name.clone(), proof_bytes);
continue;
}
if let Some(&local_secret) =
local_group_declarations.get(group_proof.group_name.as_str())
{
@@ -867,34 +907,39 @@ impl SyncedRouteInfo {
}
}
if trust_admin_groups_without_proof && self.is_admin_peer(info) {
for group_proof in &info.groups {
trusted_groups_for_peer
.entry(group_proof.group_name.clone())
.or_default();
}
}
trusted_groups_for_peer
};
for info in peer_infos {
match self.group_trust_map.entry(info.peer_id) {
dashmap::mapref::entry::Entry::Occupied(mut entry) => {
let old_trusted_groups = entry.get().clone();
let trusted_groups_for_peer = verify_groups(Some(&old_trusted_groups), info);
let trusted_groups_for_peer = verify_groups(info);
if trusted_groups_for_peer.is_empty() {
entry.remove();
self.group_trust_map_cache.remove(&info.peer_id);
} else {
self.group_trust_map_cache.insert(
info.peer_id,
Arc::new(trusted_groups_for_peer.keys().cloned().collect()),
);
let group_names = trusted_groups_for_peer.keys().cloned().collect();
self.group_trust_map_cache
.insert(info.peer_id, Arc::new(group_names));
*entry.get_mut() = trusted_groups_for_peer;
}
}
dashmap::mapref::entry::Entry::Vacant(entry) => {
let trusted_groups_for_peer = verify_groups(None, info);
let trusted_groups_for_peer = verify_groups(info);
if !trusted_groups_for_peer.is_empty() {
self.group_trust_map_cache.insert(
info.peer_id,
Arc::new(trusted_groups_for_peer.keys().cloned().collect()),
);
let group_names = trusted_groups_for_peer.keys().cloned().collect();
self.group_trust_map_cache
.insert(info.peer_id, Arc::new(group_names));
entry.insert(trusted_groups_for_peer);
}
}
@@ -904,16 +949,12 @@ impl SyncedRouteInfo {
fn update_my_group_trusts(&self, my_peer_id: PeerId, groups: &[PeerGroupInfo]) {
let mut my_group_map = HashMap::new();
let mut my_group_names = Vec::new();
for group in groups.iter() {
my_group_map.insert(group.group_name.clone(), group.group_proof.clone());
my_group_names.push(group.group_name.clone());
}
self.group_trust_map.insert(my_peer_id, my_group_map);
self.group_trust_map_cache
.insert(my_peer_id, Arc::new(my_group_names));
self.set_peer_groups(my_peer_id, my_group_map);
}
/// Collect trusted credential pubkeys from admin nodes (network_secret holders)
@@ -1004,18 +1045,13 @@ impl SyncedRouteInfo {
continue;
}
if let Some(tc) = all_trusted.get(&info.noise_static_pubkey) {
// This peer is a credential peer, assign groups from credential declaration
if !tc.groups.is_empty() {
let mut group_map = HashMap::new();
let mut group_names = Vec::new();
for g in &tc.groups {
group_map.insert(g.clone(), Vec::new()); // no proof needed, admin-declared
group_names.push(g.clone());
}
self.group_trust_map.insert(info.peer_id, group_map);
self.group_trust_map_cache
.insert(info.peer_id, Arc::new(group_names));
// Start from proof-backed groups so credential-declared groups can coexist
// without leaving stale credential-only entries behind after refreshes.
let mut group_map = self.get_proof_groups(info.peer_id);
for g in &tc.groups {
group_map.entry(g.clone()).or_default();
}
self.set_peer_groups(info.peer_id, group_map);
}
}
@@ -1039,19 +1075,10 @@ impl SyncedRouteInfo {
// Remove untrusted peers from peer_infos so they won't appear in route graph
if !untrusted_peers.is_empty() {
drop(peer_infos); // release read lock before writing
let mut peer_infos_write = self.peer_infos.write();
for peer_id in &untrusted_peers {
tracing::warn!(?peer_id, "removing untrusted peer from route info");
peer_infos_write.remove(peer_id);
self.raw_peer_infos.remove(peer_id);
}
drop(peer_infos_write);
// Also remove from conn_map
let mut conn_map = self.conn_map.write();
for peer_id in &untrusted_peers {
conn_map.remove(peer_id);
}
self.version.inc();
self.remove_peers(untrusted_peers.iter().copied());
}
(untrusted_peers, global_trusted_keys)
@@ -2444,14 +2471,53 @@ impl PeerRouteServiceImpl {
my_peer_info_updated || my_conn_info_updated || my_foreign_network_updated
}
async fn refresh_credential_trusts_and_disconnect(&self) -> bool {
async fn refresh_acl_groups(&self) -> bool {
let my_peer_info_updated = self.update_my_peer_info();
let trust_admin_groups_without_proof = self
.global_ctx
.get_network_identity()
.network_secret
.is_none();
let peer_infos: Vec<_> = self
.synced_route_info
.peer_infos
.read()
.iter()
.map(|(_, info)| info.clone())
.collect();
self.synced_route_info.verify_and_update_group_trusts(
&peer_infos,
&self.global_ctx.get_acl_group_declarations(),
trust_admin_groups_without_proof,
);
let untrusted = self.refresh_credential_trusts();
self.disconnect_untrusted_peers(&untrusted).await;
if my_peer_info_updated || !untrusted.is_empty() {
self.update_route_table_and_cached_local_conn_bitmap();
self.update_foreign_network_owner_map();
}
if my_peer_info_updated {
self.update_peer_info_last_update();
}
my_peer_info_updated || !untrusted.is_empty()
}
fn refresh_credential_trusts(&self) -> Vec<PeerId> {
let network_identity = self.global_ctx.get_network_identity();
let network_secret = network_identity.network_secret.as_deref();
let (untrusted, global_trusted_keys) = self
.synced_route_info
.verify_and_update_credential_trusts(network_secret);
.verify_and_update_credential_trusts(network_identity.network_secret.as_deref());
self.global_ctx
.update_trusted_keys(global_trusted_keys, &network_identity.network_name);
untrusted
}
async fn refresh_credential_trusts_and_disconnect(&self) -> bool {
let untrusted = self.refresh_credential_trusts();
self.disconnect_untrusted_peers(&untrusted).await;
!untrusted.is_empty()
}
@@ -2529,9 +2595,8 @@ impl PeerRouteServiceImpl {
}
}
for p in to_remove.iter() {
self.synced_route_info.remove_peer(*p);
}
self.synced_route_info
.remove_peers(to_remove.iter().copied());
// clear expired foreign network info
let mut to_remove = Vec::new();
@@ -3197,6 +3262,11 @@ impl RouteSessionManager {
(peer_infos, raw_peer_infos.as_ref().unwrap())
};
if !pi.is_empty() {
let trust_admin_groups_without_proof = service_impl
.global_ctx
.get_network_identity()
.network_secret
.is_none();
service_impl.synced_route_info.update_peer_infos(
my_peer_id,
service_impl.my_peer_route_id,
@@ -3209,6 +3279,7 @@ impl RouteSessionManager {
.verify_and_update_group_trusts(
pi,
&service_impl.global_ctx.get_acl_group_declarations(),
trust_admin_groups_without_proof,
);
session.update_dst_saved_peer_info_version(pi, from_peer_id);
need_update_route_table = true;
@@ -3228,15 +3299,7 @@ impl RouteSessionManager {
if need_update_route_table {
// Run credential verification and update route table
let network_identity = service_impl.global_ctx.get_network_identity();
let (untrusted, global_trusted_keys) = service_impl
.synced_route_info
.verify_and_update_credential_trusts(network_identity.network_secret.as_deref());
untrusted_peers = untrusted;
// Sync trusted keys to GlobalCtx for handshake verification
service_impl
.global_ctx
.update_trusted_keys(global_trusted_keys, &network_identity.network_name);
untrusted_peers = service_impl.refresh_credential_trusts();
service_impl.update_route_table_and_cached_local_conn_bitmap();
}
@@ -3644,6 +3707,12 @@ impl Route for PeerRoute {
fn get_peer_groups(&self, peer_id: PeerId) -> Arc<Vec<String>> {
self.service_impl.get_peer_groups(peer_id)
}
async fn refresh_acl_groups(&self) {
if self.service_impl.refresh_acl_groups().await {
self.session_mgr.sync_now("refresh_acl_groups");
}
}
}
impl PeerPacketFilter for Arc<PeerRoute> {}
@@ -3665,11 +3734,14 @@ mod tests {
time::{Duration, SystemTime},
};
use super::{PeerRoute, REMOVE_DEAD_PEER_INFO_AFTER};
use super::{PeerRoute, REMOVE_DEAD_PEER_INFO_AFTER, RouteConnInfo};
use crate::{
common::{
PeerId,
global_ctx::{GlobalCtxEvent, TrustedKeySource, tests::get_mock_global_ctx},
global_ctx::{
GlobalCtxEvent, TrustedKeySource,
tests::{get_mock_global_ctx, get_mock_global_ctx_with_network},
},
},
connector::udp_hole_punch::tests::replace_stun_info_collector,
peers::{
@@ -3680,8 +3752,10 @@ mod tests {
tests::{connect_peer_manager, create_mock_peer_manager, wait_route_appear},
},
proto::{
acl::{Acl, AclV1, GroupIdentity, GroupInfo},
common::{NatType, PeerFeatureFlag},
peer_rpc::{
ForeignNetworkRouteInfoEntry, ForeignNetworkRouteInfoKey, PeerGroupInfo,
PeerIdentityType, RoutePeerInfo, RoutePeerInfos, SyncRouteInfoRequest,
TrustedCredentialPubkey, TrustedCredentialPubkeyProof,
},
@@ -4040,6 +4114,219 @@ mod tests {
);
}
#[tokio::test]
async fn credential_groups_merge_with_proof_groups_and_recompute_cleanly() {
let service_impl = PeerRouteServiceImpl::new(1, get_mock_global_ctx());
let network_secret = "sec1";
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs() as i64;
let credential_peer_id = 31;
let credential_pubkey = vec![7; 32];
let mut credential_info = RoutePeerInfo::new();
credential_info.peer_id = credential_peer_id;
credential_info.version = 1;
credential_info.noise_static_pubkey = credential_pubkey.clone();
credential_info.groups = vec![PeerGroupInfo::generate_with_proof(
"proof-group".to_string(),
"proof-secret".to_string(),
credential_peer_id,
)];
let mut admin_info = RoutePeerInfo::new();
admin_info.peer_id = 32;
admin_info.version = 1;
admin_info.feature_flag = Some(PeerFeatureFlag {
is_credential_peer: false,
..Default::default()
});
admin_info.trusted_credential_pubkeys = vec![TrustedCredentialPubkeyProof::new_signed(
TrustedCredentialPubkey {
pubkey: credential_pubkey.clone(),
groups: vec!["cred-group".to_string()],
expiry_unix: now + 600,
..Default::default()
},
network_secret,
)];
{
let mut guard = service_impl.synced_route_info.peer_infos.write();
guard.insert(admin_info.peer_id, admin_info.clone());
guard.insert(credential_peer_id, credential_info.clone());
}
service_impl
.synced_route_info
.verify_and_update_group_trusts(
&[credential_info],
&[GroupIdentity {
group_name: "proof-group".to_string(),
group_secret: "proof-secret".to_string(),
}],
false,
);
service_impl
.synced_route_info
.verify_and_update_credential_trusts(Some(network_secret));
let groups = service_impl.get_peer_groups(credential_peer_id);
assert!(groups.contains(&"proof-group".to_string()));
assert!(groups.contains(&"cred-group".to_string()));
let guard = service_impl.synced_route_info.peer_infos.write();
let admin_info = guard.get(&32).unwrap().clone();
drop(guard);
let mut updated_admin = admin_info;
updated_admin.trusted_credential_pubkeys = vec![TrustedCredentialPubkeyProof::new_signed(
TrustedCredentialPubkey {
pubkey: credential_pubkey.clone(),
groups: vec!["replacement-group".to_string()],
expiry_unix: now + 600,
..Default::default()
},
network_secret,
)];
service_impl
.synced_route_info
.peer_infos
.write()
.insert(updated_admin.peer_id, updated_admin);
service_impl
.synced_route_info
.verify_and_update_credential_trusts(Some(network_secret));
let groups = service_impl.get_peer_groups(credential_peer_id);
assert!(groups.contains(&"proof-group".to_string()));
assert!(groups.contains(&"replacement-group".to_string()));
assert!(!groups.contains(&"cred-group".to_string()));
}
#[tokio::test]
async fn remove_peers_batches_cleanup_and_version_increment() {
let service_impl = PeerRouteServiceImpl::new(1, get_mock_global_ctx());
let removed_peer_ids = [41, 42];
let retained_peer_id = 43;
{
let mut peer_infos = service_impl.synced_route_info.peer_infos.write();
let mut conn_map = service_impl.synced_route_info.conn_map.write();
for peer_id in removed_peer_ids {
let mut info = RoutePeerInfo::new();
info.peer_id = peer_id;
info.version = 1;
peer_infos.insert(peer_id, info);
conn_map.insert(peer_id, RouteConnInfo::default());
}
let mut retained_info = RoutePeerInfo::new();
retained_info.peer_id = retained_peer_id;
retained_info.version = 1;
peer_infos.insert(retained_peer_id, retained_info);
conn_map.insert(retained_peer_id, RouteConnInfo::default());
}
for peer_id in removed_peer_ids {
service_impl.synced_route_info.raw_peer_infos.insert(
peer_id,
DynamicMessage::new(RoutePeerInfo::default().descriptor()),
);
service_impl.synced_route_info.group_trust_map.insert(
peer_id,
HashMap::from([("guest".to_string(), vec![1, 2, 3])]),
);
service_impl
.synced_route_info
.group_trust_map_cache
.insert(peer_id, Arc::new(vec!["guest".to_string()]));
service_impl.synced_route_info.foreign_network.insert(
ForeignNetworkRouteInfoKey {
peer_id,
..Default::default()
},
ForeignNetworkRouteInfoEntry::default(),
);
}
service_impl.synced_route_info.foreign_network.insert(
ForeignNetworkRouteInfoKey {
peer_id: retained_peer_id,
..Default::default()
},
ForeignNetworkRouteInfoEntry::default(),
);
let initial_version = service_impl.synced_route_info.version.get();
service_impl
.synced_route_info
.remove_peers(removed_peer_ids);
assert_eq!(
service_impl.synced_route_info.version.get(),
initial_version + 1
);
for peer_id in removed_peer_ids {
assert!(
!service_impl
.synced_route_info
.peer_infos
.read()
.contains_key(&peer_id)
);
assert!(
!service_impl
.synced_route_info
.conn_map
.read()
.contains_key(&peer_id)
);
assert!(
!service_impl
.synced_route_info
.raw_peer_infos
.contains_key(&peer_id)
);
assert!(
!service_impl
.synced_route_info
.group_trust_map
.contains_key(&peer_id)
);
assert!(
!service_impl
.synced_route_info
.group_trust_map_cache
.contains_key(&peer_id)
);
assert!(
!service_impl.synced_route_info.foreign_network.contains_key(
&ForeignNetworkRouteInfoKey {
peer_id,
..Default::default()
}
)
);
}
assert!(
service_impl
.synced_route_info
.peer_infos
.read()
.contains_key(&retained_peer_id)
);
assert!(service_impl.synced_route_info.foreign_network.contains_key(
&ForeignNetworkRouteInfoKey {
peer_id: retained_peer_id,
..Default::default()
}
));
}
#[tokio::test]
async fn sync_route_info_marks_credential_sender_and_filters_entries() {
let peer_mgr = create_mock_pmgr().await;
@@ -4208,6 +4495,7 @@ mod tests {
admin_info.trusted_credential_pubkeys = vec![TrustedCredentialPubkeyProof {
credential: Some(TrustedCredentialPubkey {
pubkey: credential_pubkey.clone(),
groups: vec!["guest".to_string()],
expiry_unix: i64::MAX,
..Default::default()
}),
@@ -4237,6 +4525,11 @@ mod tests {
.trusted_credential_pubkeys
.contains_key(&credential_pubkey)
);
assert!(
service_impl
.get_peer_groups(credential_peer_id)
.contains(&"guest".to_string())
);
service_impl.clear_expired_peer().await;
@@ -4260,6 +4553,299 @@ mod tests {
.read()
.contains_key(&credential_peer_id)
);
assert!(
!service_impl
.synced_route_info
.group_trust_map_cache
.contains_key(&credential_peer_id)
);
}
#[tokio::test]
async fn refresh_acl_groups_returns_true_when_untrusted_peers_are_disconnected() {
let service_impl = PeerRouteServiceImpl::new(1, get_mock_global_ctx());
let credential_peer_id: PeerId = 10061;
let credential_pubkey = vec![8u8; 32];
let closed_peers = Arc::new(Mutex::new(Vec::new()));
*service_impl.interface.lock().await = Some(Box::new(TrackingInterface {
my_peer_id: service_impl.my_peer_id,
closed_peers: closed_peers.clone(),
}));
let mut credential_info = RoutePeerInfo::new();
credential_info.peer_id = credential_peer_id;
credential_info.version = 1;
credential_info.noise_static_pubkey = credential_pubkey.clone();
credential_info.feature_flag = Some(PeerFeatureFlag {
is_credential_peer: true,
..Default::default()
});
let self_info = RoutePeerInfo::new_updated_self(
service_impl.my_peer_id,
service_impl.my_peer_route_id,
&service_impl.global_ctx,
);
let mut self_info = self_info;
self_info.version = 1;
self_info.last_update = Some(SystemTime::now().into());
{
let mut guard = service_impl.synced_route_info.peer_infos.write();
guard.insert(service_impl.my_peer_id, self_info);
guard.insert(credential_peer_id, credential_info);
}
service_impl
.synced_route_info
.trusted_credential_pubkeys
.insert(
credential_pubkey.clone(),
TrustedCredentialPubkey {
pubkey: credential_pubkey.clone(),
expiry_unix: i64::MAX,
..Default::default()
},
);
assert!(service_impl.refresh_acl_groups().await);
assert!(closed_peers.lock().contains(&credential_peer_id));
assert!(
!service_impl
.synced_route_info
.peer_infos
.read()
.contains_key(&credential_peer_id)
);
assert!(
!service_impl
.synced_route_info
.trusted_credential_pubkeys
.contains_key(&credential_pubkey)
);
}
#[tokio::test]
async fn refresh_acl_groups_updates_local_membership_immediately() {
let peer_mgr = create_mock_pmgr().await;
let route = create_mock_route(peer_mgr.clone()).await;
let my_peer_id = peer_mgr.my_peer_id();
assert!(route.service_impl.get_peer_groups(my_peer_id).is_empty());
peer_mgr.get_global_ctx().config.set_acl(Some(Acl {
acl_v1: Some(AclV1 {
group: Some(GroupInfo {
declares: vec![GroupIdentity {
group_name: "admin".to_string(),
group_secret: "admin-secret".to_string(),
}],
members: vec!["admin".to_string()],
}),
..Default::default()
}),
}));
route.refresh_acl_groups().await;
let groups = route.service_impl.get_peer_groups(my_peer_id);
assert!(groups.contains(&"admin".to_string()));
assert_eq!(groups.len(), 1);
}
#[tokio::test]
async fn refresh_acl_groups_revalidates_cached_remote_groups() {
let peer_mgr = create_mock_pmgr().await;
let route = create_mock_route(peer_mgr.clone()).await;
let remote_peer_id = 200;
let remote_group = PeerGroupInfo::generate_with_proof(
"ops".to_string(),
"secret-v1".to_string(),
remote_peer_id,
);
peer_mgr.get_global_ctx().config.set_acl(Some(Acl {
acl_v1: Some(AclV1 {
group: Some(GroupInfo {
declares: vec![GroupIdentity {
group_name: "ops".to_string(),
group_secret: "secret-v1".to_string(),
}],
members: vec![],
}),
..Default::default()
}),
}));
let mut remote_info = RoutePeerInfo::new();
remote_info.peer_id = remote_peer_id;
remote_info.version = 1;
remote_info.groups = vec![remote_group];
route
.service_impl
.synced_route_info
.peer_infos
.write()
.insert(remote_peer_id, remote_info.clone());
route
.service_impl
.synced_route_info
.verify_and_update_group_trusts(
&[remote_info],
&peer_mgr.get_global_ctx().get_acl_group_declarations(),
false,
);
assert!(
route
.service_impl
.get_peer_groups(remote_peer_id)
.contains(&"ops".to_string())
);
peer_mgr.get_global_ctx().config.set_acl(Some(Acl {
acl_v1: Some(AclV1 {
group: Some(GroupInfo {
declares: vec![GroupIdentity {
group_name: "ops".to_string(),
group_secret: "secret-v2".to_string(),
}],
members: vec![],
}),
..Default::default()
}),
}));
route.refresh_acl_groups().await;
assert!(
route
.service_impl
.get_peer_groups(remote_peer_id)
.is_empty()
);
}
#[tokio::test]
async fn credential_verifier_trusts_admin_self_groups_from_multiple_admins() {
let service_impl = PeerRouteServiceImpl::new(
1,
get_mock_global_ctx_with_network(Some(
crate::common::config::NetworkIdentity::new_credential("net1".to_string()),
)),
);
let mut admin_a = RoutePeerInfo::new();
admin_a.peer_id = 501;
admin_a.version = 1;
admin_a.groups = vec![
PeerGroupInfo {
group_name: "ops".to_string(),
group_proof: vec![1; 32],
},
PeerGroupInfo {
group_name: "core-admin".to_string(),
group_proof: vec![2; 32],
},
];
let mut admin_b = RoutePeerInfo::new();
admin_b.peer_id = 502;
admin_b.version = 1;
admin_b.groups = vec![PeerGroupInfo {
group_name: "audit".to_string(),
group_proof: vec![3; 32],
}];
service_impl
.synced_route_info
.verify_and_update_group_trusts(&[admin_a.clone(), admin_b.clone()], &[], true);
let admin_a_groups = service_impl.get_peer_groups(admin_a.peer_id);
assert!(admin_a_groups.contains(&"ops".to_string()));
assert!(admin_a_groups.contains(&"core-admin".to_string()));
let admin_b_groups = service_impl.get_peer_groups(admin_b.peer_id);
assert!(admin_b_groups.contains(&"audit".to_string()));
}
#[tokio::test]
async fn credential_verifier_still_checks_credential_self_declared_groups() {
let service_impl = PeerRouteServiceImpl::new(
1,
get_mock_global_ctx_with_network(Some(
crate::common::config::NetworkIdentity::new_credential("net1".to_string()),
)),
);
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs() as i64;
let credential_peer_id = 601;
let credential_pubkey = vec![9; 32];
let mut admin_info = RoutePeerInfo::new();
admin_info.peer_id = 600;
admin_info.version = 1;
admin_info.trusted_credential_pubkeys = vec![TrustedCredentialPubkeyProof {
credential: Some(TrustedCredentialPubkey {
pubkey: credential_pubkey.clone(),
groups: vec!["cred-acl".to_string()],
expiry_unix: now + 600,
..Default::default()
}),
credential_hmac: vec![7; 32],
}];
let mut credential_info = RoutePeerInfo::new();
credential_info.peer_id = credential_peer_id;
credential_info.version = 1;
credential_info.noise_static_pubkey = credential_pubkey.clone();
credential_info.feature_flag = Some(PeerFeatureFlag {
is_credential_peer: true,
..Default::default()
});
credential_info.groups = vec![
PeerGroupInfo::generate_with_proof(
"proof-group".to_string(),
"proof-secret".to_string(),
credential_peer_id,
),
PeerGroupInfo::generate_with_proof(
"invalid-group".to_string(),
"wrong-secret".to_string(),
credential_peer_id,
),
];
{
let mut guard = service_impl.synced_route_info.peer_infos.write();
guard.insert(admin_info.peer_id, admin_info.clone());
guard.insert(credential_info.peer_id, credential_info.clone());
}
service_impl
.synced_route_info
.verify_and_update_group_trusts(
&[admin_info, credential_info],
&[
GroupIdentity {
group_name: "proof-group".to_string(),
group_secret: "proof-secret".to_string(),
},
GroupIdentity {
group_name: "invalid-group".to_string(),
group_secret: "actual-secret".to_string(),
},
],
true,
);
service_impl
.synced_route_info
.verify_and_update_credential_trusts(None);
let groups = service_impl.get_peer_groups(credential_peer_id);
assert!(groups.contains(&"proof-group".to_string()));
assert!(groups.contains(&"cred-acl".to_string()));
assert!(!groups.contains(&"invalid-group".to_string()));
}
#[rstest::rstest]