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:
@@ -191,7 +191,7 @@ async fn remove_network_instance(app: AppHandle, instance_id: String) -> Result<
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
client_manager
|
||||
.post_remove_network_instances_hook(&app, &[instance_id])
|
||||
.post_stop_network_instances_hook(&app)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
@@ -214,7 +214,7 @@ async fn update_network_config_state(
|
||||
|
||||
if disabled {
|
||||
client_manager
|
||||
.post_remove_network_instances_hook(&app, &[instance_id])
|
||||
.post_stop_network_instances_hook(&app)
|
||||
.await?;
|
||||
}
|
||||
|
||||
@@ -613,7 +613,7 @@ mod manager {
|
||||
async fn post_remove_network_instances(&self, ids: &[uuid::Uuid]) -> Result<(), String> {
|
||||
let client_manager = get_client_manager!()?;
|
||||
client_manager
|
||||
.post_remove_network_instances_hook(&self.app, ids)
|
||||
.post_remote_remove_network_instances_hook(&self.app, ids)
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -696,7 +696,9 @@ mod manager {
|
||||
self.network_configs.remove(network_inst_id);
|
||||
self.enabled_networks.remove(network_inst_id);
|
||||
}
|
||||
self.save_configs(&app)
|
||||
self.save_configs(&app)?;
|
||||
self.save_enabled_networks(&app)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn update_network_config_state(
|
||||
@@ -897,14 +899,23 @@ mod manager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) async fn post_remove_network_instances_hook(
|
||||
pub(super) async fn post_remote_remove_network_instances_hook(
|
||||
&self,
|
||||
app: &AppHandle,
|
||||
_ids: &[uuid::Uuid],
|
||||
ids: &[uuid::Uuid],
|
||||
) -> Result<(), String> {
|
||||
self.storage
|
||||
.enabled_networks
|
||||
.retain(|id| !_ids.contains(id));
|
||||
.delete_network_configs(app.clone(), ids)
|
||||
.await
|
||||
.map_err(|e| e.to_string())?;
|
||||
self.notify_vpn_stop_if_no_tun(app)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) async fn post_stop_network_instances_hook(
|
||||
&self,
|
||||
app: &AppHandle,
|
||||
) -> Result<(), String> {
|
||||
self.notify_vpn_stop_if_no_tun(app)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{fmt::Debug, str::FromStr as _, sync::Arc};
|
||||
use std::{collections::HashSet, fmt::Debug, str::FromStr as _, sync::Arc};
|
||||
|
||||
use anyhow::Context;
|
||||
use easytier::{
|
||||
@@ -399,6 +399,7 @@ impl Session {
|
||||
storage: WeakRefStorage,
|
||||
rpc_client: SessionRpcClient,
|
||||
) {
|
||||
let mut cleaned_web_managed_instances = false;
|
||||
loop {
|
||||
heartbeat_waiter = heartbeat_waiter.resubscribe();
|
||||
let req = heartbeat_waiter.recv().await;
|
||||
@@ -420,7 +421,7 @@ impl Session {
|
||||
.running_network_instances
|
||||
.iter()
|
||||
.map(|x| x.to_string())
|
||||
.collect::<Vec<_>>();
|
||||
.collect::<HashSet<_>>();
|
||||
let Some(storage) = storage.upgrade() else {
|
||||
tracing::error!("Failed to get storage");
|
||||
return;
|
||||
@@ -456,6 +457,60 @@ impl Session {
|
||||
|
||||
let mut has_failed = false;
|
||||
|
||||
if !cleaned_web_managed_instances {
|
||||
let all_local_configs = match storage
|
||||
.db
|
||||
.list_network_configs((user_id, machine_id.into()), ListNetworkProps::All)
|
||||
.await
|
||||
{
|
||||
Ok(configs) => configs,
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to list all network configs, error: {:?}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let all_inst_ids = all_local_configs
|
||||
.iter()
|
||||
.map(|cfg| cfg.network_instance_id.clone())
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
let should_be_alive_inst_ids = local_configs
|
||||
.iter()
|
||||
.map(|cfg| cfg.network_instance_id.clone())
|
||||
.collect::<HashSet<_>>();
|
||||
|
||||
let should_delete_ids = running_inst_ids
|
||||
.iter()
|
||||
.chain(all_inst_ids.iter())
|
||||
.filter(|inst_id| !should_be_alive_inst_ids.contains(*inst_id))
|
||||
.filter_map(|inst_id| uuid::Uuid::parse_str(inst_id).ok())
|
||||
.map(Into::into)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !should_delete_ids.is_empty() {
|
||||
let ret = rpc_client
|
||||
.delete_network_instance(
|
||||
BaseController::default(),
|
||||
easytier::proto::api::manage::DeleteNetworkInstanceRequest {
|
||||
inst_ids: should_delete_ids,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
tracing::info!(
|
||||
?user_id,
|
||||
"Clean non-web-managed network instances on start: {:?}, user_token: {:?}",
|
||||
ret,
|
||||
req.user_token
|
||||
);
|
||||
has_failed |= ret.is_err();
|
||||
}
|
||||
|
||||
if !has_failed {
|
||||
cleaned_web_managed_instances = true;
|
||||
}
|
||||
}
|
||||
|
||||
for c in local_configs {
|
||||
if running_inst_ids.contains(&c.network_instance_id) {
|
||||
continue;
|
||||
|
||||
@@ -148,6 +148,24 @@ impl TrustedKeyMapManager {
|
||||
|
||||
!metadata.is_expired()
|
||||
}
|
||||
|
||||
pub fn list_trusted_keys(&self, network_name: &str) -> Vec<(Vec<u8>, TrustedKeyMetadata)> {
|
||||
let Some(trusted_keys) = self
|
||||
.network_trusted_keys
|
||||
.get(network_name)
|
||||
.map(|v| v.load_full())
|
||||
else {
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
let mut items = trusted_keys
|
||||
.iter()
|
||||
.filter(|(_, metadata)| !metadata.is_expired())
|
||||
.map(|(pubkey, metadata)| (pubkey.clone(), metadata.clone()))
|
||||
.collect::<Vec<_>>();
|
||||
items.sort_by(|left, right| left.0.cmp(&right.0));
|
||||
items
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GlobalCtx {
|
||||
@@ -534,6 +552,10 @@ impl GlobalCtx {
|
||||
self.trusted_keys.remove_trusted_keys(network_name);
|
||||
}
|
||||
|
||||
pub fn list_trusted_keys(&self, network_name: &str) -> Vec<(Vec<u8>, TrustedKeyMetadata)> {
|
||||
self.trusted_keys.list_trusted_keys(network_name)
|
||||
}
|
||||
|
||||
pub fn get_acl_groups(&self, peer_id: PeerId) -> Vec<PeerGroupInfo> {
|
||||
use std::collections::HashSet;
|
||||
self.config
|
||||
|
||||
@@ -12,6 +12,8 @@ use std::{
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
use base64::prelude::BASE64_STANDARD;
|
||||
use base64::Engine as _;
|
||||
use cidr::Ipv4Inet;
|
||||
use clap::{Args, CommandFactory, Parser, Subcommand};
|
||||
use dashmap::DashMap;
|
||||
@@ -56,8 +58,8 @@ use easytier::{
|
||||
PeerManageRpcClientFactory, PortForwardManageRpc,
|
||||
PortForwardManageRpcClientFactory, RevokeCredentialRequest, ShowNodeInfoRequest,
|
||||
StatsRpc, StatsRpcClientFactory, TcpProxyEntryState, TcpProxyEntryTransportType,
|
||||
TcpProxyRpc, TcpProxyRpcClientFactory, VpnPortalInfo, VpnPortalRpc,
|
||||
VpnPortalRpcClientFactory,
|
||||
TcpProxyRpc, TcpProxyRpcClientFactory, TrustedKeySourcePb, VpnPortalInfo,
|
||||
VpnPortalRpc, VpnPortalRpcClientFactory,
|
||||
},
|
||||
logger::{
|
||||
GetLoggerConfigRequest, LogLevel, LoggerRpc, LoggerRpcClientFactory,
|
||||
@@ -193,7 +195,14 @@ enum PeerSubCommand {
|
||||
Add,
|
||||
Remove,
|
||||
List,
|
||||
ListForeign,
|
||||
ListForeign {
|
||||
#[arg(
|
||||
long,
|
||||
default_value = "false",
|
||||
help = "include trusted keys for each foreign network"
|
||||
)]
|
||||
trusted_keys: bool,
|
||||
},
|
||||
ListGlobalForeign,
|
||||
}
|
||||
|
||||
@@ -901,7 +910,10 @@ impl<'a> CommandHandler<'a> {
|
||||
.result)
|
||||
}
|
||||
|
||||
async fn fetch_foreign_networks(&self) -> Result<ForeignNetworkMap, Error> {
|
||||
async fn fetch_foreign_networks(
|
||||
&self,
|
||||
include_trusted_keys: bool,
|
||||
) -> Result<ForeignNetworkMap, Error> {
|
||||
Ok(self
|
||||
.get_peer_manager_client()
|
||||
.await?
|
||||
@@ -909,6 +921,7 @@ impl<'a> CommandHandler<'a> {
|
||||
BaseController::default(),
|
||||
ListForeignNetworkRequest {
|
||||
instance: Some(self.instance_selector.clone()),
|
||||
include_trusted_keys,
|
||||
},
|
||||
)
|
||||
.await?
|
||||
@@ -1316,9 +1329,11 @@ impl<'a> CommandHandler<'a> {
|
||||
})
|
||||
}
|
||||
|
||||
async fn handle_foreign_network_list(&self) -> Result<(), Error> {
|
||||
async fn handle_foreign_network_list(&self, include_trusted_keys: bool) -> Result<(), Error> {
|
||||
let results = self
|
||||
.collect_instance_results(|handler| Box::pin(handler.fetch_foreign_networks()))
|
||||
.collect_instance_results(|handler| {
|
||||
Box::pin(handler.fetch_foreign_networks(include_trusted_keys))
|
||||
})
|
||||
.await?;
|
||||
if self.verbose || *self.output_format == OutputFormat::Json {
|
||||
return self.print_json_results(results);
|
||||
@@ -1351,6 +1366,24 @@ impl<'a> CommandHandler<'a> {
|
||||
.join("; ")
|
||||
);
|
||||
}
|
||||
if include_trusted_keys {
|
||||
println!(" trusted_keys:");
|
||||
for trusted_key in &v.trusted_keys {
|
||||
let source = TrustedKeySourcePb::try_from(trusted_key.source)
|
||||
.map(|source| source.as_str_name())
|
||||
.unwrap_or("TRUSTED_KEY_SOURCE_PB_UNSPECIFIED");
|
||||
let expiry = trusted_key
|
||||
.expiry_unix
|
||||
.map(|value| value.to_string())
|
||||
.unwrap_or_else(|| "-".to_string());
|
||||
println!(
|
||||
" source: {}, expiry_unix: {}, pubkey: {}",
|
||||
source,
|
||||
expiry,
|
||||
BASE64_STANDARD.encode(&trusted_key.pubkey),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
@@ -2548,8 +2581,8 @@ async fn main() -> Result<(), Error> {
|
||||
Some(PeerSubCommand::List) => {
|
||||
handler.handle_peer_list().await?;
|
||||
}
|
||||
Some(PeerSubCommand::ListForeign) => {
|
||||
handler.handle_foreign_network_list().await?;
|
||||
Some(PeerSubCommand::ListForeign { trusted_keys }) => {
|
||||
handler.handle_foreign_network_list(*trusted_keys).await?;
|
||||
}
|
||||
Some(PeerSubCommand::ListGlobalForeign) => {
|
||||
handler.handle_global_foreign_network_list().await?;
|
||||
|
||||
@@ -23,7 +23,7 @@ use crate::{
|
||||
common::{
|
||||
config::{ConfigLoader, TomlConfigLoader},
|
||||
error::Error,
|
||||
global_ctx::{ArcGlobalCtx, GlobalCtx, GlobalCtxEvent, NetworkIdentity},
|
||||
global_ctx::{ArcGlobalCtx, GlobalCtx, GlobalCtxEvent, NetworkIdentity, TrustedKeySource},
|
||||
join_joinset_background, shrink_dashmap,
|
||||
stats_manager::{LabelSet, LabelType, MetricName, StatsManager},
|
||||
token_bucket::TokenBucket,
|
||||
@@ -32,7 +32,10 @@ use crate::{
|
||||
peer_center::instance::{PeerCenterInstance, PeerMapWithPeerRpcManager},
|
||||
peers::route_trait::{Route, RouteInterface},
|
||||
proto::{
|
||||
api::instance::{ForeignNetworkEntryPb, ListForeignNetworkResponse, PeerInfo},
|
||||
api::instance::{
|
||||
ForeignNetworkEntryPb, ListForeignNetworkResponse, PeerInfo, TrustedKeyInfoPb,
|
||||
TrustedKeySourcePb,
|
||||
},
|
||||
common::LimiterConfig,
|
||||
peer_rpc::{DirectConnectorRpcServer, PeerIdentityType},
|
||||
},
|
||||
@@ -627,6 +630,22 @@ impl ForeignNetworkManager {
|
||||
.is_pubkey_trusted(remote_static_pubkey, &entry.network.network_name)
|
||||
}
|
||||
|
||||
fn build_trusted_key_items(entry: &ForeignNetworkEntry) -> Vec<TrustedKeyInfoPb> {
|
||||
entry
|
||||
.global_ctx
|
||||
.list_trusted_keys(&entry.network.network_name)
|
||||
.into_iter()
|
||||
.map(|(pubkey, metadata)| TrustedKeyInfoPb {
|
||||
pubkey,
|
||||
source: match metadata.source {
|
||||
TrustedKeySource::OspfNode => TrustedKeySourcePb::OspfNode.into(),
|
||||
TrustedKeySource::OspfCredential => TrustedKeySourcePb::OspfCredential.into(),
|
||||
},
|
||||
expiry_unix: metadata.expiry_unix,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
my_peer_id: PeerId,
|
||||
global_ctx: ArcGlobalCtx,
|
||||
@@ -775,6 +794,13 @@ impl ForeignNetworkManager {
|
||||
}
|
||||
|
||||
pub async fn list_foreign_networks(&self) -> ListForeignNetworkResponse {
|
||||
self.list_foreign_networks_with_options(false).await
|
||||
}
|
||||
|
||||
pub async fn list_foreign_networks_with_options(
|
||||
&self,
|
||||
include_trusted_keys: bool,
|
||||
) -> ListForeignNetworkResponse {
|
||||
let mut ret = ListForeignNetworkResponse::default();
|
||||
let networks = self
|
||||
.data
|
||||
@@ -801,6 +827,11 @@ impl ForeignNetworkManager {
|
||||
.to_vec(),
|
||||
my_peer_id_for_this_network: item.my_peer_id,
|
||||
peers: Default::default(),
|
||||
trusted_keys: if include_trusted_keys {
|
||||
Self::build_trusted_key_items(&item)
|
||||
} else {
|
||||
Default::default()
|
||||
},
|
||||
};
|
||||
for peer in item.peer_map.list_peers() {
|
||||
let peer_info = PeerInfo {
|
||||
@@ -872,6 +903,7 @@ pub mod tests {
|
||||
create_mock_peer_manager_with_mock_stun, replace_stun_info_collector,
|
||||
},
|
||||
peers::{
|
||||
peer_conn::tests::set_secure_mode_cfg,
|
||||
peer_manager::{PeerManager, RouteAlgoType},
|
||||
tests::{connect_peer_manager, wait_route_appear},
|
||||
},
|
||||
@@ -905,6 +937,21 @@ pub mod tests {
|
||||
create_mock_peer_manager_for_foreign_network_ext(network, network).await
|
||||
}
|
||||
|
||||
pub async fn create_mock_peer_manager_for_secure_foreign_network(
|
||||
network: &str,
|
||||
) -> Arc<PeerManager> {
|
||||
let (s, _r) = create_packet_recv_chan();
|
||||
let global_ctx = get_mock_global_ctx_with_network(Some(NetworkIdentity::new(
|
||||
network.to_string(),
|
||||
network.to_string(),
|
||||
)));
|
||||
set_secure_mode_cfg(&global_ctx, true);
|
||||
let peer_mgr = Arc::new(PeerManager::new(RouteAlgoType::Ospf, global_ctx, s));
|
||||
replace_stun_info_collector(peer_mgr.clone(), NatType::Unknown);
|
||||
peer_mgr.run().await.unwrap();
|
||||
peer_mgr
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn foreign_network_basic() {
|
||||
let pm_center = create_mock_peer_manager_with_mock_stun(NatType::Unknown).await;
|
||||
@@ -935,6 +982,51 @@ pub mod tests {
|
||||
assert_eq!(2, rpc_resp.foreign_networks["net1"].peers.len());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn foreign_network_list_can_include_trusted_keys() {
|
||||
let pm_center = create_mock_peer_manager_with_mock_stun(NatType::Unknown).await;
|
||||
set_secure_mode_cfg(&pm_center.get_global_ctx(), true);
|
||||
|
||||
let pma_net1 = create_mock_peer_manager_for_secure_foreign_network("net1").await;
|
||||
let pmb_net1 = create_mock_peer_manager_for_secure_foreign_network("net1").await;
|
||||
connect_peer_manager(pma_net1.clone(), pm_center.clone()).await;
|
||||
connect_peer_manager(pmb_net1.clone(), pm_center.clone()).await;
|
||||
wait_route_appear(pma_net1.clone(), pmb_net1.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let without_trusted_keys = pm_center
|
||||
.get_foreign_network_manager()
|
||||
.list_foreign_networks()
|
||||
.await;
|
||||
assert!(without_trusted_keys.foreign_networks["net1"]
|
||||
.trusted_keys
|
||||
.is_empty());
|
||||
|
||||
let foreign_mgr = pm_center.get_foreign_network_manager();
|
||||
wait_for_condition(
|
||||
|| {
|
||||
let foreign_mgr = foreign_mgr.clone();
|
||||
async move {
|
||||
foreign_mgr
|
||||
.list_foreign_networks_with_options(true)
|
||||
.await
|
||||
.foreign_networks
|
||||
.get("net1")
|
||||
.map(|entry| !entry.trusted_keys.is_empty())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
},
|
||||
Duration::from_secs(5),
|
||||
)
|
||||
.await;
|
||||
|
||||
let with_trusted_keys = foreign_mgr.list_foreign_networks_with_options(true).await;
|
||||
assert!(!with_trusted_keys.foreign_networks["net1"]
|
||||
.trusted_keys
|
||||
.is_empty());
|
||||
}
|
||||
|
||||
async fn foreign_network_whitelist_helper(name: String) {
|
||||
let pm_center = create_mock_peer_manager_with_mock_stun(NatType::Unknown).await;
|
||||
tracing::debug!("pm_center: {:?}", pm_center.my_peer_id());
|
||||
|
||||
@@ -124,11 +124,11 @@ impl PeerManageRpc for PeerManagerRpcService {
|
||||
async fn list_foreign_network(
|
||||
&self,
|
||||
_: BaseController,
|
||||
_request: ListForeignNetworkRequest, // Accept request of type HelloRequest
|
||||
request: ListForeignNetworkRequest,
|
||||
) -> Result<ListForeignNetworkResponse, rpc_types::error::Error> {
|
||||
let reply = weak_upgrade(&self.peer_manager)?
|
||||
.get_foreign_network_manager()
|
||||
.list_foreign_networks()
|
||||
.list_foreign_networks_with_options(request.include_trusted_keys)
|
||||
.await;
|
||||
Ok(reply)
|
||||
}
|
||||
|
||||
@@ -114,12 +114,28 @@ message DumpRouteRequest { InstanceIdentifier instance = 1; }
|
||||
|
||||
message DumpRouteResponse { string result = 1; }
|
||||
|
||||
message ListForeignNetworkRequest { InstanceIdentifier instance = 1; }
|
||||
message ListForeignNetworkRequest {
|
||||
InstanceIdentifier instance = 1;
|
||||
bool include_trusted_keys = 2;
|
||||
}
|
||||
|
||||
enum TrustedKeySourcePb {
|
||||
TRUSTED_KEY_SOURCE_PB_UNSPECIFIED = 0;
|
||||
TRUSTED_KEY_SOURCE_PB_OSPF_NODE = 1;
|
||||
TRUSTED_KEY_SOURCE_PB_OSPF_CREDENTIAL = 2;
|
||||
}
|
||||
|
||||
message TrustedKeyInfoPb {
|
||||
bytes pubkey = 1;
|
||||
TrustedKeySourcePb source = 2;
|
||||
optional int64 expiry_unix = 3;
|
||||
}
|
||||
|
||||
message ForeignNetworkEntryPb {
|
||||
repeated PeerInfo peers = 1;
|
||||
bytes network_secret_digest = 2;
|
||||
uint32 my_peer_id_for_this_network = 3;
|
||||
repeated TrustedKeyInfoPb trusted_keys = 4;
|
||||
}
|
||||
|
||||
message ListForeignNetworkResponse {
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user