mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-05-06 17:59:11 +00:00
feat(credential): improve credential peer routing and visibility (#1971)
- improve credential peer filtering and related route lookup behavior - expose credential peer information through CLI and API definitions - add and refine tests for credential routing and peer interactions
This commit is contained in:
@@ -372,7 +372,7 @@ enum CredentialSubCommand {
|
|||||||
},
|
},
|
||||||
/// Revoke a credential by its ID
|
/// Revoke a credential by its ID
|
||||||
Revoke {
|
Revoke {
|
||||||
#[arg(help = "credential ID (public key base64)")]
|
#[arg(help = "credential ID (UUID)")]
|
||||||
credential_id: String,
|
credential_id: String,
|
||||||
},
|
},
|
||||||
/// List all active credentials
|
/// List all active credentials
|
||||||
@@ -1440,7 +1440,7 @@ impl CommandHandler<'_> {
|
|||||||
println!();
|
println!();
|
||||||
println!("To use this credential on a new node:");
|
println!("To use this credential on a new node:");
|
||||||
println!(
|
println!(
|
||||||
" easytier-core --network-name <name> --secure-mode --credential {}",
|
" easytier-core --network-name <name> --secure-mode --credential {} -p <node-url>",
|
||||||
response.credential_secret
|
response.credential_secret
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ use crate::proto::peer_rpc::TrustedCredentialPubkey;
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
struct CredentialEntry {
|
struct CredentialEntry {
|
||||||
pubkey_bytes: Vec<u8>,
|
pubkey: String,
|
||||||
groups: Vec<String>,
|
groups: Vec<String>,
|
||||||
allow_relay: bool,
|
allow_relay: bool,
|
||||||
allowed_proxy_cidrs: Vec<String>,
|
allowed_proxy_cidrs: Vec<String>,
|
||||||
@@ -46,7 +46,8 @@ impl CredentialManager {
|
|||||||
) -> (String, String) {
|
) -> (String, String) {
|
||||||
let private = StaticSecret::random_from_rng(rand::rngs::OsRng);
|
let private = StaticSecret::random_from_rng(rand::rngs::OsRng);
|
||||||
let public = PublicKey::from(&private);
|
let public = PublicKey::from(&private);
|
||||||
let id = BASE64_STANDARD.encode(public.as_bytes());
|
let id = uuid::Uuid::new_v4().to_string();
|
||||||
|
let pubkey = BASE64_STANDARD.encode(public.as_bytes());
|
||||||
let secret = BASE64_STANDARD.encode(private.as_bytes());
|
let secret = BASE64_STANDARD.encode(private.as_bytes());
|
||||||
|
|
||||||
let now = SystemTime::now()
|
let now = SystemTime::now()
|
||||||
@@ -56,7 +57,7 @@ impl CredentialManager {
|
|||||||
let expiry_unix = now + ttl.as_secs() as i64;
|
let expiry_unix = now + ttl.as_secs() as i64;
|
||||||
|
|
||||||
let entry = CredentialEntry {
|
let entry = CredentialEntry {
|
||||||
pubkey_bytes: public.as_bytes().to_vec(),
|
pubkey,
|
||||||
groups,
|
groups,
|
||||||
allow_relay,
|
allow_relay,
|
||||||
allowed_proxy_cidrs,
|
allowed_proxy_cidrs,
|
||||||
@@ -94,12 +95,13 @@ impl CredentialManager {
|
|||||||
.values()
|
.values()
|
||||||
.filter(|e| e.expiry_unix > now)
|
.filter(|e| e.expiry_unix > now)
|
||||||
.map(|e| TrustedCredentialPubkey {
|
.map(|e| TrustedCredentialPubkey {
|
||||||
pubkey: e.pubkey_bytes.clone(),
|
pubkey: Self::decode_pubkey_b64(&e.pubkey).unwrap_or_default(),
|
||||||
groups: e.groups.clone(),
|
groups: e.groups.clone(),
|
||||||
allow_relay: e.allow_relay,
|
allow_relay: e.allow_relay,
|
||||||
expiry_unix: e.expiry_unix,
|
expiry_unix: e.expiry_unix,
|
||||||
allowed_proxy_cidrs: e.allowed_proxy_cidrs.clone(),
|
allowed_proxy_cidrs: e.allowed_proxy_cidrs.clone(),
|
||||||
})
|
})
|
||||||
|
.filter(|e| !e.pubkey.is_empty())
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,11 +111,12 @@ impl CredentialManager {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.as_secs() as i64;
|
.as_secs() as i64;
|
||||||
|
|
||||||
|
let encoded = BASE64_STANDARD.encode(pubkey);
|
||||||
self.credentials
|
self.credentials
|
||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.values()
|
.values()
|
||||||
.any(|e| e.pubkey_bytes == pubkey && e.expiry_unix > now)
|
.any(|e| e.pubkey == encoded && e.expiry_unix > now)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_credentials(&self) -> Vec<crate::proto::api::instance::CredentialInfo> {
|
pub fn list_credentials(&self) -> Vec<crate::proto::api::instance::CredentialInfo> {
|
||||||
@@ -166,6 +169,14 @@ impl CredentialManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn decode_pubkey_b64(s: &str) -> Option<Vec<u8>> {
|
||||||
|
let decoded = BASE64_STANDARD.decode(s).ok()?;
|
||||||
|
if decoded.len() != 32 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(decoded)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -184,8 +195,11 @@ mod tests {
|
|||||||
|
|
||||||
assert!(!id.is_empty());
|
assert!(!id.is_empty());
|
||||||
assert!(!secret.is_empty());
|
assert!(!secret.is_empty());
|
||||||
|
assert!(uuid::Uuid::parse_str(&id).is_ok());
|
||||||
|
|
||||||
let pubkey_bytes = BASE64_STANDARD.decode(&id).unwrap();
|
let privkey_bytes: [u8; 32] = BASE64_STANDARD.decode(&secret).unwrap().try_into().unwrap();
|
||||||
|
let private = StaticSecret::from(privkey_bytes);
|
||||||
|
let pubkey_bytes = PublicKey::from(&private).as_bytes().to_vec();
|
||||||
assert!(mgr.is_pubkey_trusted(&pubkey_bytes));
|
assert!(mgr.is_pubkey_trusted(&pubkey_bytes));
|
||||||
|
|
||||||
let trusted = mgr.get_trusted_pubkeys();
|
let trusted = mgr.get_trusted_pubkeys();
|
||||||
@@ -201,9 +215,11 @@ mod tests {
|
|||||||
fn test_expired_credential() {
|
fn test_expired_credential() {
|
||||||
let mgr = CredentialManager::new(None);
|
let mgr = CredentialManager::new(None);
|
||||||
// TTL of 0 seconds - immediately expired
|
// TTL of 0 seconds - immediately expired
|
||||||
let (id, _) = mgr.generate_credential(vec![], false, vec![], Duration::from_secs(0));
|
let (_, secret) = mgr.generate_credential(vec![], false, vec![], Duration::from_secs(0));
|
||||||
|
|
||||||
let pubkey_bytes = BASE64_STANDARD.decode(&id).unwrap();
|
let privkey_bytes: [u8; 32] = BASE64_STANDARD.decode(&secret).unwrap().try_into().unwrap();
|
||||||
|
let private = StaticSecret::from(privkey_bytes);
|
||||||
|
let pubkey_bytes = PublicKey::from(&private).as_bytes().to_vec();
|
||||||
assert!(!mgr.is_pubkey_trusted(&pubkey_bytes));
|
assert!(!mgr.is_pubkey_trusted(&pubkey_bytes));
|
||||||
assert!(mgr.get_trusted_pubkeys().is_empty());
|
assert!(mgr.get_trusted_pubkeys().is_empty());
|
||||||
}
|
}
|
||||||
@@ -233,9 +249,8 @@ mod tests {
|
|||||||
let privkey_bytes: [u8; 32] = BASE64_STANDARD.decode(&secret).unwrap().try_into().unwrap();
|
let privkey_bytes: [u8; 32] = BASE64_STANDARD.decode(&secret).unwrap().try_into().unwrap();
|
||||||
let private = StaticSecret::from(privkey_bytes);
|
let private = StaticSecret::from(privkey_bytes);
|
||||||
let derived_public = PublicKey::from(&private);
|
let derived_public = PublicKey::from(&private);
|
||||||
let derived_id = BASE64_STANDARD.encode(derived_public.as_bytes());
|
assert!(uuid::Uuid::parse_str(&id).is_ok());
|
||||||
|
assert!(mgr.is_pubkey_trusted(derived_public.as_bytes()));
|
||||||
assert_eq!(id, derived_id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -247,21 +262,35 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_multiple_credentials_independent() {
|
fn test_multiple_credentials_independent() {
|
||||||
let mgr = CredentialManager::new(None);
|
let mgr = CredentialManager::new(None);
|
||||||
let (id1, _) = mgr.generate_credential(
|
let (id1, secret1) = mgr.generate_credential(
|
||||||
vec!["group1".to_string()],
|
vec!["group1".to_string()],
|
||||||
false,
|
false,
|
||||||
vec![],
|
vec![],
|
||||||
Duration::from_secs(3600),
|
Duration::from_secs(3600),
|
||||||
);
|
);
|
||||||
let (id2, _) = mgr.generate_credential(
|
let (_id2, secret2) = mgr.generate_credential(
|
||||||
vec!["group2".to_string()],
|
vec!["group2".to_string()],
|
||||||
true,
|
true,
|
||||||
vec!["10.0.0.0/8".to_string()],
|
vec!["10.0.0.0/8".to_string()],
|
||||||
Duration::from_secs(3600),
|
Duration::from_secs(3600),
|
||||||
);
|
);
|
||||||
|
|
||||||
let pk1 = BASE64_STANDARD.decode(&id1).unwrap();
|
let sk1: [u8; 32] = BASE64_STANDARD
|
||||||
let pk2 = BASE64_STANDARD.decode(&id2).unwrap();
|
.decode(&secret1)
|
||||||
|
.unwrap()
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
let sk2: [u8; 32] = BASE64_STANDARD
|
||||||
|
.decode(&secret2)
|
||||||
|
.unwrap()
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
let pk1 = PublicKey::from(&StaticSecret::from(sk1))
|
||||||
|
.as_bytes()
|
||||||
|
.to_vec();
|
||||||
|
let pk2 = PublicKey::from(&StaticSecret::from(sk2))
|
||||||
|
.as_bytes()
|
||||||
|
.to_vec();
|
||||||
|
|
||||||
assert!(mgr.is_pubkey_trusted(&pk1));
|
assert!(mgr.is_pubkey_trusted(&pk1));
|
||||||
assert!(mgr.is_pubkey_trusted(&pk2));
|
assert!(mgr.is_pubkey_trusted(&pk2));
|
||||||
@@ -284,7 +313,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_trusted_pubkeys_include_metadata() {
|
fn test_trusted_pubkeys_include_metadata() {
|
||||||
let mgr = CredentialManager::new(None);
|
let mgr = CredentialManager::new(None);
|
||||||
let (id, _) = mgr.generate_credential(
|
let (_, secret) = mgr.generate_credential(
|
||||||
vec!["admin".to_string(), "ops".to_string()],
|
vec!["admin".to_string(), "ops".to_string()],
|
||||||
true,
|
true,
|
||||||
vec!["192.168.0.0/16".to_string(), "10.0.0.0/8".to_string()],
|
vec!["192.168.0.0/16".to_string(), "10.0.0.0/8".to_string()],
|
||||||
@@ -302,7 +331,8 @@ mod tests {
|
|||||||
);
|
);
|
||||||
assert!(tc.expiry_unix > 0);
|
assert!(tc.expiry_unix > 0);
|
||||||
|
|
||||||
let pk = BASE64_STANDARD.decode(&id).unwrap();
|
let sk: [u8; 32] = BASE64_STANDARD.decode(&secret).unwrap().try_into().unwrap();
|
||||||
|
let pk = PublicKey::from(&StaticSecret::from(sk)).as_bytes().to_vec();
|
||||||
assert_eq!(tc.pubkey, pk);
|
assert_eq!(tc.pubkey, pk);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -974,6 +974,16 @@ impl PeerManager {
|
|||||||
self.my_peer_id
|
self.my_peer_id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn close_peer(&self, peer_id: PeerId) {
|
||||||
|
if let Some(peer_map) = self.peers.upgrade() {
|
||||||
|
let _ = peer_map.close_peer(peer_id).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(foreign_client) = self.foreign_network_client.upgrade() {
|
||||||
|
let _ = foreign_client.get_peer_map().close_peer(peer_id).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn get_peer_identity_type(&self, peer_id: PeerId) -> Option<PeerIdentityType> {
|
async fn get_peer_identity_type(&self, peer_id: PeerId) -> Option<PeerIdentityType> {
|
||||||
let peer_map = self.peers.upgrade()?;
|
let peer_map = self.peers.upgrade()?;
|
||||||
peer_map.get_peer_identity_type(peer_id)
|
peer_map.get_peer_identity_type(peer_id)
|
||||||
|
|||||||
@@ -2261,6 +2261,7 @@ impl PeerRouteServiceImpl {
|
|||||||
let (untrusted, global_trusted_keys) =
|
let (untrusted, global_trusted_keys) =
|
||||||
self.synced_route_info.verify_and_update_credential_trusts();
|
self.synced_route_info.verify_and_update_credential_trusts();
|
||||||
self.global_ctx.update_trusted_keys(global_trusted_keys);
|
self.global_ctx.update_trusted_keys(global_trusted_keys);
|
||||||
|
self.disconnect_untrusted_peers(&untrusted).await;
|
||||||
untrusted_changed = !untrusted.is_empty();
|
untrusted_changed = !untrusted.is_empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2274,6 +2275,22 @@ impl PeerRouteServiceImpl {
|
|||||||
my_peer_info_updated || my_conn_info_updated || my_foreign_network_updated
|
my_peer_info_updated || my_conn_info_updated || my_foreign_network_updated
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn disconnect_untrusted_peers(&self, untrusted_peers: &[PeerId]) {
|
||||||
|
if untrusted_peers.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let interface = self.interface.lock().await;
|
||||||
|
let Some(interface) = interface.as_ref() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
for peer_id in untrusted_peers {
|
||||||
|
tracing::warn!(?peer_id, "disconnecting untrusted peer");
|
||||||
|
interface.close_peer(*peer_id).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn build_sync_request(
|
fn build_sync_request(
|
||||||
&self,
|
&self,
|
||||||
session: &SyncRouteSession,
|
session: &SyncRouteSession,
|
||||||
@@ -2904,6 +2921,7 @@ impl RouteSessionManager {
|
|||||||
session.update_dst_session_id(from_session_id);
|
session.update_dst_session_id(from_session_id);
|
||||||
|
|
||||||
let mut need_update_route_table = false;
|
let mut need_update_route_table = false;
|
||||||
|
let mut untrusted_peers = Vec::new();
|
||||||
|
|
||||||
if let Some(peer_infos) = &peer_infos {
|
if let Some(peer_infos) = &peer_infos {
|
||||||
// Step 9b: credential peers can only propagate their own route info
|
// Step 9b: credential peers can only propagate their own route info
|
||||||
@@ -3001,9 +3019,10 @@ impl RouteSessionManager {
|
|||||||
|
|
||||||
if need_update_route_table {
|
if need_update_route_table {
|
||||||
// Run credential verification and update route table
|
// Run credential verification and update route table
|
||||||
let (_untrusted, global_trusted_keys) = service_impl
|
let (untrusted, global_trusted_keys) = service_impl
|
||||||
.synced_route_info
|
.synced_route_info
|
||||||
.verify_and_update_credential_trusts();
|
.verify_and_update_credential_trusts();
|
||||||
|
untrusted_peers = untrusted;
|
||||||
// Sync trusted keys to GlobalCtx for handshake verification
|
// Sync trusted keys to GlobalCtx for handshake verification
|
||||||
service_impl
|
service_impl
|
||||||
.global_ctx
|
.global_ctx
|
||||||
@@ -3035,6 +3054,11 @@ impl RouteSessionManager {
|
|||||||
let is_initiator = session.we_are_initiator.load(Ordering::Relaxed);
|
let is_initiator = session.we_are_initiator.load(Ordering::Relaxed);
|
||||||
let session_id = session.my_session_id.load(Ordering::Relaxed);
|
let session_id = session.my_session_id.load(Ordering::Relaxed);
|
||||||
|
|
||||||
|
drop(_session_lock);
|
||||||
|
service_impl
|
||||||
|
.disconnect_untrusted_peers(&untrusted_peers)
|
||||||
|
.await;
|
||||||
|
|
||||||
self.sync_now("sync_route_info");
|
self.sync_now("sync_route_info");
|
||||||
|
|
||||||
Ok(SyncRouteInfoResponse {
|
Ok(SyncRouteInfoResponse {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ pub type ForeignNetworkRouteInfoMap =
|
|||||||
pub trait RouteInterface {
|
pub trait RouteInterface {
|
||||||
async fn list_peers(&self) -> Vec<PeerId>;
|
async fn list_peers(&self) -> Vec<PeerId>;
|
||||||
fn my_peer_id(&self) -> PeerId;
|
fn my_peer_id(&self) -> PeerId;
|
||||||
|
async fn close_peer(&self, _peer_id: PeerId) {}
|
||||||
async fn get_peer_identity_type(&self, _peer_id: PeerId) -> Option<PeerIdentityType> {
|
async fn get_peer_identity_type(&self, _peer_id: PeerId) -> Option<PeerIdentityType> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -903,6 +903,104 @@ async fn credential_revocation_removes_from_routes() {
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn credential_expiry_disconnects_from_all_admins() {
|
||||||
|
let admin_a = create_mock_peer_manager_secure("net1".to_string(), "secret".to_string()).await;
|
||||||
|
let admin_b = create_mock_peer_manager_secure("net1".to_string(), "secret".to_string()).await;
|
||||||
|
|
||||||
|
connect_peer_manager(admin_a.clone(), admin_b.clone()).await;
|
||||||
|
wait_route_appear(admin_a.clone(), admin_b.clone())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let (_cred_id, cred_secret) = admin_a
|
||||||
|
.get_global_ctx()
|
||||||
|
.get_credential_manager()
|
||||||
|
.generate_credential(vec![], false, vec![], std::time::Duration::from_secs(2));
|
||||||
|
|
||||||
|
admin_a
|
||||||
|
.get_global_ctx()
|
||||||
|
.issue_event(crate::common::global_ctx::GlobalCtxEvent::CredentialChanged);
|
||||||
|
|
||||||
|
let privkey_bytes: [u8; 32] = base64::engine::general_purpose::STANDARD
|
||||||
|
.decode(&cred_secret)
|
||||||
|
.unwrap()
|
||||||
|
.try_into()
|
||||||
|
.unwrap();
|
||||||
|
let private = x25519_dalek::StaticSecret::from(privkey_bytes);
|
||||||
|
let cred_c = create_mock_peer_manager_credential("net1".to_string(), &private).await;
|
||||||
|
let cred_c_id = cred_c.my_peer_id();
|
||||||
|
|
||||||
|
connect_peer_manager(cred_c.clone(), admin_a.clone()).await;
|
||||||
|
|
||||||
|
wait_for_condition(
|
||||||
|
|| {
|
||||||
|
let admin_b = admin_b.clone();
|
||||||
|
async move {
|
||||||
|
admin_b
|
||||||
|
.list_routes()
|
||||||
|
.await
|
||||||
|
.iter()
|
||||||
|
.any(|r| r.peer_id == cred_c_id)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Duration::from_secs(10),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
connect_peer_manager(cred_c.clone(), admin_b.clone()).await;
|
||||||
|
|
||||||
|
wait_for_condition(
|
||||||
|
|| {
|
||||||
|
let admin_b = admin_b.clone();
|
||||||
|
async move {
|
||||||
|
admin_b
|
||||||
|
.get_peer_map()
|
||||||
|
.list_peer_conns(cred_c_id)
|
||||||
|
.await
|
||||||
|
.is_some_and(|conns| !conns.is_empty())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Duration::from_secs(10),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
tokio::time::sleep(Duration::from_secs(3)).await;
|
||||||
|
admin_a
|
||||||
|
.get_global_ctx()
|
||||||
|
.issue_event(crate::common::global_ctx::GlobalCtxEvent::CredentialChanged);
|
||||||
|
|
||||||
|
wait_for_condition(
|
||||||
|
|| {
|
||||||
|
let admin_b = admin_b.clone();
|
||||||
|
async move {
|
||||||
|
!admin_b
|
||||||
|
.list_routes()
|
||||||
|
.await
|
||||||
|
.iter()
|
||||||
|
.any(|r| r.peer_id == cred_c_id)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Duration::from_secs(20),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
wait_for_condition(
|
||||||
|
|| {
|
||||||
|
let admin_b = admin_b.clone();
|
||||||
|
async move {
|
||||||
|
admin_b
|
||||||
|
.get_peer_map()
|
||||||
|
.list_peer_conns(cred_c_id)
|
||||||
|
.await
|
||||||
|
.is_none_or(|conns| conns.is_empty())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Duration::from_secs(20),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
/// Test: admin node with credential — credential node gets group assignment.
|
/// Test: admin node with credential — credential node gets group assignment.
|
||||||
/// Verify that the credential node's groups appear in the OSPF sync data.
|
/// Verify that the credential node's groups appear in the OSPF sync data.
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|||||||
@@ -303,7 +303,7 @@ message GenerateCredentialRequest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
message GenerateCredentialResponse {
|
message GenerateCredentialResponse {
|
||||||
string credential_id = 1; // public key base64
|
string credential_id = 1; // UUID
|
||||||
string credential_secret = 2; // private key base64
|
string credential_secret = 2; // private key base64
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,7 +318,7 @@ message RevokeCredentialResponse {
|
|||||||
message ListCredentialsRequest {}
|
message ListCredentialsRequest {}
|
||||||
|
|
||||||
message CredentialInfo {
|
message CredentialInfo {
|
||||||
string credential_id = 1; // public key base64
|
string credential_id = 1; // UUID
|
||||||
repeated string groups = 2;
|
repeated string groups = 2;
|
||||||
bool allow_relay = 3;
|
bool allow_relay = 3;
|
||||||
int64 expiry_unix = 4;
|
int64 expiry_unix = 4;
|
||||||
|
|||||||
@@ -708,6 +708,18 @@ async fn credential_revocation_propagates() {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
wait_for_condition(
|
||||||
|
|| async { !ping_test("ns_adm", "10.144.144.2", None).await },
|
||||||
|
Duration::from_secs(10),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
wait_for_condition(
|
||||||
|
|| async { !ping_test("ns_c1", "10.144.144.1", None).await },
|
||||||
|
Duration::from_secs(10),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
drop_insts(vec![admin_inst, cred_inst]).await;
|
drop_insts(vec![admin_inst, cred_inst]).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user