fix(credential): trust pinned admin keys on first connect (#2528)

This commit is contained in:
KKRainbow
2026-08-26 23:59:13 +08:00
committed by GitHub
parent 81487c363a
commit 67f2270ee7
2 changed files with 82 additions and 17 deletions
+3 -13
View File
@@ -687,7 +687,8 @@ impl PeerConn {
/// | Client role | Server role | Typical credential condition | Client auth level | Server auth level | Client sees server type | Server sees client type | /// | Client role | Server role | Typical credential condition | Client auth level | Server auth level | Client sees server type | Server sees client type |
/// | --- | --- | --- | --- | --- | --- | --- | /// | --- | --- | --- | --- | --- | --- | --- |
/// | Admin | Admin | same network_secret, proof verified | NetworkSecretConfirmed | NetworkSecretConfirmed | Admin | Admin | /// | Admin | Admin | same network_secret, proof verified | NetworkSecretConfirmed | NetworkSecretConfirmed | Admin | Admin |
/// | Credential | Admin | client pubkey is trusted by admin | EncryptedUnauthenticated | PeerVerified | Admin | Credential | /// | Credential | Admin | admin key is pinned and client key is trusted | PeerVerified | PeerVerified | Admin | Credential |
/// | Credential | Admin | client pubkey is trusted, admin key is not pinned | EncryptedUnauthenticated | PeerVerified | Admin | Credential |
/// | Credential | Admin | client pubkey is unknown | handshake may fail | handshake reject | unknown | unknown | /// | Credential | Admin | client pubkey is unknown | handshake may fail | handshake reject | unknown | unknown |
/// | Admin | SharedNode | pinned key match | PeerVerified | EncryptedUnauthenticated | SharedNode | Admin | /// | Admin | SharedNode | pinned key match | PeerVerified | EncryptedUnauthenticated | SharedNode | Admin |
/// | Admin | SharedNode | local has no pinned key requirement | EncryptedUnauthenticated | EncryptedUnauthenticated | SharedNode | Admin | /// | Admin | SharedNode | local has no pinned key requirement | EncryptedUnauthenticated | EncryptedUnauthenticated | SharedNode | Admin |
@@ -696,8 +697,7 @@ impl PeerConn {
/// ///
/// Logic (in priority order): /// Logic (in priority order):
/// 1. **NetworkSecretConfirmed**: proof verification succeeds /// 1. **NetworkSecretConfirmed**: proof verification succeeds
/// 2. **PeerVerified**: pinned_pubkey matches and is in trusted list /// 2. **PeerVerified**: pinned_pubkey matches
/// (if no network_secret, pinned_pubkey must be in trusted list)
/// 3. **PeerVerified**: pubkey is in trusted list /// 3. **PeerVerified**: pubkey is in trusted list
/// 4. **EncryptedUnauthenticated**: initiator without network_secret /// 4. **EncryptedUnauthenticated**: initiator without network_secret
/// 5. **Reject**: none of the above /// 5. **Reject**: none of the above
@@ -727,16 +727,6 @@ impl PeerConn {
"pinned remote static pubkey mismatch".to_owned(), "pinned remote static pubkey mismatch".to_owned(),
)); ));
} }
// If no network_secret, pinned key must be in trusted list
if !has_network_secret
&& !self
.context
.is_pubkey_trusted(remote_pubkey, remote_network_name)
{
return Err(Error::WaitRespError(
"pinned pubkey not in trusted list".to_owned(),
));
}
return Ok(SecureAuthLevel::PeerVerified); return Ok(SecureAuthLevel::PeerVerified);
} }
+79 -4
View File
@@ -12,7 +12,7 @@ use easytier_core::process_runtime::CoreProcessRuntime;
use crate::{ use crate::{
common::{ common::{
config::{ConfigLoader, NetworkIdentity, TomlConfigLoader}, config::{ConfigLoader, NetworkIdentity, PeerConfig, TomlConfigLoader},
global_ctx::GlobalCtxEvent, global_ctx::GlobalCtxEvent,
}, },
instance::test_instance::TestInstance as Instance, instance::test_instance::TestInstance as Instance,
@@ -763,17 +763,24 @@ async fn wait_stable_single_visible_peer_on_admins(
} }
} }
/// Test 1: Basic credential node connectivity /// Test 1: Basic credential node connectivity with and without an admin key pin
/// Topology: Admin ← Credential /// Topology: Admin ← Credential
/// Verifies that a credential node can connect to an admin node and appears in routes /// Verifies that a credential node can connect to an admin node and appears in routes
#[rstest]
#[case(false)]
#[case(true)]
#[tokio::test] #[tokio::test]
#[serial_test::serial] #[serial_test::serial]
async fn credential_basic_connectivity() { async fn credential_basic_connectivity(#[case] pin_admin: bool) {
prepare_credential_network(); prepare_credential_network();
let process_runtime = CoreProcessRuntime::new(); let process_runtime = CoreProcessRuntime::new();
// Create admin node // Create admin node
let admin_config = create_admin_config("admin", Some("ns_adm"), "10.144.144.1", "fd00::1/64"); let admin_config = create_admin_config("admin", Some("ns_adm"), "10.144.144.1", "fd00::1/64");
let admin_public_key = admin_config
.get_secure_mode()
.and_then(|config| config.local_public_key)
.unwrap();
let mut admin_inst = Instance::new_with_process_runtime(admin_config, process_runtime.clone()); let mut admin_inst = Instance::new_with_process_runtime(admin_config, process_runtime.clone());
admin_inst.run().await.unwrap(); admin_inst.run().await.unwrap();
@@ -786,11 +793,19 @@ async fn credential_basic_connectivity() {
"fd00::2/64", "fd00::2/64",
) )
.await; .await;
if pin_admin {
cred_config.set_peers(vec![PeerConfig {
uri: "tcp://10.1.1.1:11010".parse().unwrap(),
peer_public_key: Some(admin_public_key),
}]);
}
let mut cred_inst = Instance::new_with_process_runtime(cred_config, process_runtime.clone()); let mut cred_inst = Instance::new_with_process_runtime(cred_config, process_runtime.clone());
cred_inst.run().await.unwrap(); cred_inst.run().await.unwrap();
// Credential connects to admin // Credential connects to admin
cred_inst.add_connector_url("tcp://10.1.1.1:11010".parse().unwrap()); if !pin_admin {
cred_inst.add_connector_url("tcp://10.1.1.1:11010".parse().unwrap());
}
let cred_peer_id = cred_inst.peer_id(); let cred_peer_id = cred_inst.peer_id();
let admin_peer_id = admin_inst.peer_id(); let admin_peer_id = admin_inst.peer_id();
@@ -853,6 +868,66 @@ async fn credential_basic_connectivity() {
drop_insts(vec![admin_inst, cred_inst]).await; drop_insts(vec![admin_inst, cred_inst]).await;
} }
/// A credential node must reject an admin whose Noise key does not match its pin.
#[tokio::test]
#[serial_test::serial]
async fn credential_rejects_incorrect_admin_pin() {
prepare_credential_network();
let process_runtime = CoreProcessRuntime::new();
let admin_config = create_admin_config("admin", Some("ns_adm"), "10.144.144.1", "fd00::1/64");
let admin_public_key = admin_config
.get_secure_mode()
.and_then(|config| config.local_public_key)
.unwrap();
let mut admin_inst = Instance::new_with_process_runtime(admin_config, process_runtime.clone());
admin_inst.run().await.unwrap();
let cred_config = create_credential_config(
&admin_inst,
"cred",
Some("ns_c1"),
"10.144.144.2",
"fd00::2/64",
)
.await;
let incorrect_admin_public_key = generate_secure_mode_config().local_public_key.unwrap();
assert_ne!(incorrect_admin_public_key, admin_public_key);
cred_config.set_peers(vec![PeerConfig {
uri: "tcp://10.1.1.1:11010".parse().unwrap(),
peer_public_key: Some(incorrect_admin_public_key),
}]);
let mut cred_inst = Instance::new_with_process_runtime(cred_config, process_runtime.clone());
cred_inst.run().await.unwrap();
let admin_peer_id = admin_inst.peer_id();
let cred_peer_id = cred_inst.peer_id();
for _ in 0..5 {
let admin_peers = admin_inst.get_core_instance().connected_peers().await;
let cred_peers = cred_inst.get_core_instance().connected_peers().await;
let admin_routes = admin_inst.get_core_instance().route_snapshots().await;
let cred_routes = cred_inst.get_core_instance().route_snapshots().await;
assert!(!admin_peers.contains(&cred_peer_id));
assert!(!cred_peers.contains(&admin_peer_id));
assert!(
!admin_routes
.iter()
.any(|route| route.peer_id == cred_peer_id)
);
assert!(
!cred_routes
.iter()
.any(|route| route.peer_id == admin_peer_id)
);
tokio::time::sleep(Duration::from_secs(1)).await;
}
drop_insts(vec![admin_inst, cred_inst]).await;
}
/// Test 5-6: Credential relay capability with allow_relay parameter /// Test 5-6: Credential relay capability with allow_relay parameter
/// Topology: Admin ← Credential_A, Admin ← Credential_B, Admin ← Credential_C(listener, allow_relay) /// Topology: Admin ← Credential_A, Admin ← Credential_B, Admin ← Credential_C(listener, allow_relay)
/// Verifies routing behavior based on allow_relay flag: /// Verifies routing behavior based on allow_relay flag: