Files
EasyTier/easytier-core/src/instance/management.rs
T
KKRainbow 0b27ac2885 feat(credentials): support managed credential synchronization (#2490)
* feat(credentials): support managed credential synchronization

Allow managed callers to upsert credentials with an exact ID, secret,
permissions, reuse policy, and expiry.

Return non-secret attributes plus a public-key fingerprint so callers can
verify relay credential consistency.

Persist imported credentials atomically and preserve identity and expiry
across restarts.

* fix(credentials): make managed upserts durable

Write the candidate credential snapshot before committing it to memory.
Propagate storage failures so controllers can retry instead of observing
false convergence.

Cover a transient storage failure to verify that memory stays unchanged
and the retry persists the credential.

* fix(credentials): atomically replace stored snapshots

Define CredentialStorage::store as an atomic replacement boundary and
use atomic-write-file in the management adapter. This keeps the last
committed credential JSON readable when a replacement fails.

Cover replacement of an existing credential snapshot and keep the
dependency scoped to the management feature.
2026-08-10 23:20:36 +08:00

229 lines
6.8 KiB
Rust

use std::{any::Any, net::IpAddr, sync::Arc};
use url::Url;
use crate::{
config::peers::AclWhitelistSnapshot,
connectivity::manual::ManualConnectorSnapshot,
foundation::stats::MetricSnapshot,
peers::{
conn::peer_conn::PeerConnId,
credential_manager::{
CredentialCreateOptions, CredentialInfo, CredentialUpsertOptions, GeneratedCredential,
},
peer_manager::PeerSnapshot,
},
};
use super::{CoreInstance, CoreInstanceHost, CorePacketPlane};
impl<H> CoreInstance<H>
where
H: CoreInstanceHost,
{
pub fn instance_id(&self) -> uuid::Uuid {
self.peer_manager.instance_id()
}
pub fn instance_name(&self) -> &str {
&self.instance_name
}
pub fn add_connector(&self, url: Url) -> anyhow::Result<()> {
self.manual.add_connector(url)
}
pub fn remove_connector(&self, url: &Url) -> bool {
self.manual.remove_connector(url)
}
pub fn clear_connectors(&self) {
self.manual.clear_connectors();
}
pub fn list_connectors(&self) -> Vec<ManualConnectorSnapshot> {
self.manual.list_connectors()
}
pub fn running_listeners(&self) -> Vec<Url> {
self.running_listeners.running_listeners()
}
pub fn peer_id(&self) -> crate::config::PeerId {
self.peer_manager.my_peer_id()
}
pub fn packet_plane(&self) -> Arc<CorePacketPlane> {
self.packet_plane.clone()
}
/// Recovers the concrete Host runtime Adapter for host-specific integration.
pub fn runtime_host<T: super::InstanceRuntimeHost>(&self) -> Option<&T> {
let runtime_host: &dyn Any = self.instance_runtime.as_ref();
runtime_host.downcast_ref()
}
pub fn attach_tun_fd(&self, fd: i32) -> anyhow::Result<()> {
self.instance_runtime.attach_tun_fd(fd)
}
pub fn latest_error(&self) -> Option<String> {
self.latest_error.read().clone()
}
pub fn is_ready(&self) -> bool {
self.state() == super::CoreInstanceState::Running
}
pub fn management_events(&self) -> Vec<String> {
self.instance_runtime.management_events()
}
pub fn global_peer_map_snapshot(&self) -> crate::proto::peer_rpc::GetGlobalPeerMapResponse {
self.peer_center.global_peer_map_snapshot()
}
pub async fn peer_snapshots(&self) -> Vec<PeerSnapshot> {
self.peer_manager.list_peer_snapshots().await
}
pub async fn node_snapshot(&self) -> crate::peers::peer_manager::NodeSnapshot {
let mut snapshot = self
.peer_manager
.node_snapshot(self.running_listeners())
.await;
snapshot.ip_list = self
.direct
.local_address_observations_with_stun(&snapshot.stun_info)
.await;
snapshot
}
pub async fn route_snapshots(&self) -> Vec<crate::proto::core_peer::peer::Route> {
self.peer_manager.list_route_snapshots().await
}
pub async fn dump_route(&self) -> String {
self.peer_manager.dump_route().await
}
pub async fn local_public_ipv6_info(
&self,
) -> crate::proto::core_peer::peer::ListPublicIpv6InfoResponse {
self.peer_manager.local_public_ipv6_info().await
}
pub async fn foreign_network_route_infos(
&self,
) -> crate::proto::peer_rpc::RouteForeignNetworkInfos {
self.peer_manager.foreign_network_route_infos().await
}
pub async fn foreign_network_snapshots(
&self,
include_trusted_keys: bool,
) -> std::collections::HashMap<String, crate::peers::foreign_network::ForeignNetworkEntryInfo>
{
self.peer_manager
.list_foreign_network_infos(include_trusted_keys)
.await
}
pub async fn foreign_network_route_summary(
&self,
) -> crate::proto::peer_rpc::RouteForeignNetworkSummary {
self.peer_manager.foreign_network_route_summary().await
}
pub fn acl_stats(&self) -> crate::proto::acl::AclStats {
self.peer_manager.acl_stats()
}
pub fn acl_whitelist_snapshot(&self) -> AclWhitelistSnapshot {
let config = self.runtime_config.snapshot();
AclWhitelistSnapshot::from(&config.services.acl)
}
pub fn generate_credential(
&self,
options: CredentialCreateOptions,
) -> anyhow::Result<GeneratedCredential> {
if !self.peer_manager.can_manage_credentials() {
anyhow::bail!("only admin nodes (with network_secret) can generate credentials");
}
if options.ttl.is_zero() {
anyhow::bail!("ttl_seconds must be positive");
}
let generated = self
.peer_manager
.credential_manager()
.generate_credential_with_options(
options.groups,
options.allow_relay,
options.allowed_proxy_cidrs,
options.ttl,
options.credential_id,
options.reusable,
);
self.peer_manager.notify_credential_changed();
Ok(generated)
}
pub fn revoke_credential(&self, credential_id: &str) -> anyhow::Result<bool> {
if !self.peer_manager.can_manage_credentials() {
anyhow::bail!("only admin nodes (with network_secret) can revoke credentials");
}
let revoked = self
.peer_manager
.credential_manager()
.revoke_credential(credential_id);
if revoked {
self.peer_manager.notify_credential_changed();
}
Ok(revoked)
}
pub fn upsert_credential(&self, options: CredentialUpsertOptions) -> anyhow::Result<bool> {
if !self.peer_manager.can_manage_credentials() {
anyhow::bail!("only admin nodes (with network_secret) can import credentials");
}
let changed = self
.peer_manager
.credential_manager()
.upsert_credential(options)
.map_err(anyhow::Error::msg)?;
if changed {
self.peer_manager.notify_credential_changed();
}
Ok(changed)
}
pub fn credential_snapshots(&self) -> Vec<CredentialInfo> {
self.peer_manager.credential_manager().list_credentials()
}
pub fn metric_snapshots(&self) -> Vec<MetricSnapshot> {
self.peer_manager.stats_manager().get_all_metrics()
}
pub fn prometheus_metrics(&self) -> String {
self.peer_manager.stats_manager().export_prometheus()
}
pub async fn close_peer_conn(
&self,
peer_id: crate::config::PeerId,
conn_id: &PeerConnId,
) -> Result<(), crate::peers::error::Error> {
self.peer_manager.close_peer_conn(peer_id, conn_id).await
}
pub async fn update_exit_nodes(&self, exit_nodes: Vec<IpAddr>) {
self.peer_manager.update_exit_nodes(exit_nodes).await;
}
pub async fn refresh_acl_groups(&self) {
self.peer_manager.get_route().refresh_acl_groups().await;
}
}