mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-05-07 02:09:06 +00:00
multi_fix: harden peer/session handling, tighten foreign-network trust, and improve web client metadata (#1999)
* machine-id should be scoped unbder same user-id * feat: report device os metadata to console * fix sync root key cause packet loss * fix tun packet not invalid * fix faketcp cause lat jitter * fix some packet not decrypt * fix peer info patch, improve performance of update self info * fix foreign credential identity mismatch handling
This commit is contained in:
@@ -175,27 +175,15 @@ impl ClientManager {
|
||||
.map(|item| item.value().clone())
|
||||
}
|
||||
|
||||
/// Find a session by machine_id regardless of user_id.
|
||||
pub fn get_session_by_machine_id_global(
|
||||
pub async fn disconnect_session_by_machine_id(
|
||||
&self,
|
||||
user_id: UserIdInDb,
|
||||
machine_id: &uuid::Uuid,
|
||||
) -> Option<Arc<Session>> {
|
||||
self.storage
|
||||
.get_client_url_by_machine_id_global(machine_id)
|
||||
.and_then(|url| {
|
||||
self.client_sessions
|
||||
.get(&url)
|
||||
.map(|item| item.value().clone())
|
||||
})
|
||||
}
|
||||
|
||||
/// Get user_id associated with a machine_id.
|
||||
pub fn get_user_id_by_machine_id_global(&self, machine_id: &uuid::Uuid) -> Option<UserIdInDb> {
|
||||
self.storage.get_user_id_by_machine_id_global(machine_id)
|
||||
}
|
||||
|
||||
pub async fn disconnect_session_by_machine_id_global(&self, machine_id: &uuid::Uuid) -> bool {
|
||||
let Some(client_url) = self.storage.get_client_url_by_machine_id_global(machine_id) else {
|
||||
) -> bool {
|
||||
let Some(client_url) = self
|
||||
.storage
|
||||
.get_client_url_by_machine_id(user_id, machine_id)
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
let Some((_, session)) = self.client_sessions.remove(&client_url) else {
|
||||
|
||||
@@ -88,12 +88,16 @@ impl Drop for SessionData {
|
||||
if self.webhook_config.is_enabled() {
|
||||
let webhook = self.webhook_config.clone();
|
||||
let machine_id = token.machine_id.to_string();
|
||||
let user_id = Some(token.user_id);
|
||||
let token_value = token.token.clone();
|
||||
let web_instance_id = webhook.web_instance_id.clone();
|
||||
let binding_version = self.binding_version;
|
||||
tokio::spawn(async move {
|
||||
webhook
|
||||
.notify_node_disconnected(&crate::webhook::NodeDisconnectedRequest {
|
||||
machine_id,
|
||||
token: token_value,
|
||||
user_id,
|
||||
web_instance_id,
|
||||
binding_version,
|
||||
})
|
||||
@@ -190,6 +194,9 @@ impl SessionRpcService {
|
||||
machine_id: machine_id.to_string(),
|
||||
hostname: req.hostname.clone(),
|
||||
version: req.easytier_version.clone(),
|
||||
os_type: req.device_os.as_ref().map(|info| info.os_type.clone()),
|
||||
os_version: req.device_os.as_ref().map(|info| info.version.clone()),
|
||||
os_distribution: req.device_os.as_ref().map(|info| info.distribution.clone()),
|
||||
web_instance_id: data.webhook_config.web_instance_id.clone(),
|
||||
web_instance_api_base_url: data.webhook_config.web_instance_api_base_url.clone(),
|
||||
};
|
||||
@@ -283,8 +290,12 @@ impl SessionRpcService {
|
||||
let connect_req = crate::webhook::NodeConnectedRequest {
|
||||
machine_id: machine_id.to_string(),
|
||||
token: req.user_token.clone(),
|
||||
user_id: Some(user_id),
|
||||
hostname: req.hostname.clone(),
|
||||
version: req.easytier_version.clone(),
|
||||
os_type: req.device_os.as_ref().map(|info| info.os_type.clone()),
|
||||
os_version: req.device_os.as_ref().map(|info| info.version.clone()),
|
||||
os_distribution: req.device_os.as_ref().map(|info| info.distribution.clone()),
|
||||
web_instance_id: webhook.web_instance_id.clone(),
|
||||
binding_version,
|
||||
};
|
||||
|
||||
@@ -22,7 +22,6 @@ struct ClientInfo {
|
||||
#[derive(Debug)]
|
||||
pub struct StorageInner {
|
||||
user_clients_map: DashMap<UserIdInDb, DashMap<uuid::Uuid, ClientInfo>>,
|
||||
global_machine_map: DashMap<uuid::Uuid, ClientInfo>,
|
||||
pub db: Db,
|
||||
}
|
||||
|
||||
@@ -42,7 +41,6 @@ impl Storage {
|
||||
pub fn new(db: Db) -> Self {
|
||||
Storage(Arc::new(StorageInner {
|
||||
user_clients_map: DashMap::new(),
|
||||
global_machine_map: DashMap::new(),
|
||||
db,
|
||||
}))
|
||||
}
|
||||
@@ -75,13 +73,10 @@ impl Storage {
|
||||
storage_token: stoken.clone(),
|
||||
report_time,
|
||||
};
|
||||
|
||||
Self::update_client_info_map(&inner, &client_info);
|
||||
Self::update_client_info_map(&self.0.global_machine_map, &client_info);
|
||||
}
|
||||
|
||||
pub fn remove_client(&self, stoken: &StorageToken) {
|
||||
Self::remove_client_info_map(&self.0.global_machine_map, stoken);
|
||||
self.0
|
||||
.user_clients_map
|
||||
.remove_if(&stoken.user_id, |_, set| {
|
||||
@@ -106,22 +101,6 @@ impl Storage {
|
||||
})
|
||||
}
|
||||
|
||||
/// Find client_url by machine_id across all users.
|
||||
pub fn get_client_url_by_machine_id_global(&self, machine_id: &uuid::Uuid) -> Option<url::Url> {
|
||||
self.0
|
||||
.global_machine_map
|
||||
.get(machine_id)
|
||||
.map(|info| info.storage_token.client_url.clone())
|
||||
}
|
||||
|
||||
/// Find user_id by machine_id across all users.
|
||||
pub fn get_user_id_by_machine_id_global(&self, machine_id: &uuid::Uuid) -> Option<UserIdInDb> {
|
||||
self.0
|
||||
.global_machine_map
|
||||
.get(machine_id)
|
||||
.map(|info| info.storage_token.user_id)
|
||||
}
|
||||
|
||||
pub fn list_user_clients(&self, user_id: UserIdInDb) -> Vec<url::Url> {
|
||||
self.0
|
||||
.user_clients_map
|
||||
@@ -164,38 +143,35 @@ mod tests {
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn global_machine_index_uses_latest_report_and_ignores_stale_removal() {
|
||||
async fn machine_id_is_scoped_within_each_user() {
|
||||
let storage = Storage::new(Db::memory_db().await);
|
||||
let machine_id = uuid::Uuid::new_v4();
|
||||
|
||||
let old_token = make_storage_token(1, machine_id, "tcp://127.0.0.1:1001");
|
||||
let new_token = make_storage_token(1, machine_id, "tcp://127.0.0.1:1002");
|
||||
let user1_token = make_storage_token(1, machine_id, "tcp://127.0.0.1:1001");
|
||||
let user2_token = make_storage_token(2, machine_id, "tcp://127.0.0.1:1002");
|
||||
|
||||
storage.update_client(old_token.clone(), 10);
|
||||
storage.update_client(new_token.clone(), 20);
|
||||
storage.update_client(user1_token.clone(), 10);
|
||||
storage.update_client(user2_token.clone(), 20);
|
||||
|
||||
assert_eq!(
|
||||
storage.get_client_url_by_machine_id_global(&machine_id),
|
||||
Some(new_token.client_url.clone())
|
||||
storage.get_client_url_by_machine_id(1, &machine_id),
|
||||
Some(user1_token.client_url.clone())
|
||||
);
|
||||
assert_eq!(
|
||||
storage.get_user_id_by_machine_id_global(&machine_id),
|
||||
Some(1)
|
||||
storage.get_client_url_by_machine_id(2, &machine_id),
|
||||
Some(user2_token.client_url.clone())
|
||||
);
|
||||
|
||||
storage.remove_client(&old_token);
|
||||
storage.remove_client(&user1_token);
|
||||
|
||||
assert_eq!(storage.get_client_url_by_machine_id(1, &machine_id), None);
|
||||
assert_eq!(
|
||||
storage.get_client_url_by_machine_id_global(&machine_id),
|
||||
Some(new_token.client_url.clone())
|
||||
storage.get_client_url_by_machine_id(2, &machine_id),
|
||||
Some(user2_token.client_url.clone())
|
||||
);
|
||||
|
||||
storage.remove_client(&new_token);
|
||||
storage.remove_client(&user2_token);
|
||||
|
||||
assert_eq!(
|
||||
storage.get_client_url_by_machine_id_global(&machine_id),
|
||||
None
|
||||
);
|
||||
assert_eq!(storage.get_user_id_by_machine_id_global(&machine_id), None);
|
||||
assert_eq!(storage.get_client_url_by_machine_id(2, &machine_id), None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ use users::{AuthSession, Backend};
|
||||
|
||||
use crate::client_manager::storage::StorageToken;
|
||||
use crate::client_manager::ClientManager;
|
||||
use crate::db::Db;
|
||||
use crate::db::{Db, UserIdInDb};
|
||||
use crate::webhook::SharedWebhookConfig;
|
||||
use crate::FeatureFlags;
|
||||
|
||||
@@ -252,7 +252,7 @@ impl RestfulServer {
|
||||
get(Self::handle_list_all_sessions_internal),
|
||||
)
|
||||
.route(
|
||||
"/api/internal/sessions/:machine-id",
|
||||
"/api/internal/users/:user-id/sessions/:machine-id",
|
||||
delete(Self::handle_disconnect_session_internal),
|
||||
)
|
||||
.merge(NetworkApi::build_route_internal())
|
||||
@@ -315,11 +315,11 @@ impl RestfulServer {
|
||||
}
|
||||
|
||||
async fn handle_disconnect_session_internal(
|
||||
Path(machine_id): Path<uuid::Uuid>,
|
||||
Path((user_id, machine_id)): Path<(UserIdInDb, uuid::Uuid)>,
|
||||
State(client_mgr): AppState,
|
||||
) -> Result<StatusCode, HttpHandleError> {
|
||||
if client_mgr
|
||||
.disconnect_session_by_machine_id_global(&machine_id)
|
||||
.disconnect_session_by_machine_id(user_id, &machine_id)
|
||||
.await
|
||||
{
|
||||
Ok(StatusCode::NO_CONTENT)
|
||||
|
||||
@@ -299,10 +299,9 @@ impl NetworkApi {
|
||||
|
||||
async fn handle_run_network_instance_internal(
|
||||
State(client_mgr): AppState,
|
||||
Path(machine_id): Path<uuid::Uuid>,
|
||||
Path((user_id, machine_id)): Path<(UserIdInDb, uuid::Uuid)>,
|
||||
Json(payload): Json<RunNetworkJsonReq>,
|
||||
) -> Result<Json<Void>, HttpHandleError> {
|
||||
let user_id = Self::get_user_id_from_machine(&client_mgr, &machine_id)?;
|
||||
client_mgr
|
||||
.handle_run_network_instance((user_id, machine_id), payload.config, payload.save)
|
||||
.await
|
||||
@@ -312,9 +311,8 @@ impl NetworkApi {
|
||||
|
||||
async fn handle_remove_network_instance_internal(
|
||||
State(client_mgr): AppState,
|
||||
Path((machine_id, inst_id)): Path<(uuid::Uuid, uuid::Uuid)>,
|
||||
Path((user_id, machine_id, inst_id)): Path<(UserIdInDb, uuid::Uuid, uuid::Uuid)>,
|
||||
) -> Result<(), HttpHandleError> {
|
||||
let user_id = Self::get_user_id_from_machine(&client_mgr, &machine_id)?;
|
||||
client_mgr
|
||||
.handle_remove_network_instances((user_id, machine_id), vec![inst_id])
|
||||
.await
|
||||
@@ -323,9 +321,8 @@ impl NetworkApi {
|
||||
|
||||
async fn handle_list_network_instance_ids_internal(
|
||||
State(client_mgr): AppState,
|
||||
Path(machine_id): Path<uuid::Uuid>,
|
||||
Path((user_id, machine_id)): Path<(UserIdInDb, uuid::Uuid)>,
|
||||
) -> Result<Json<ListNetworkInstanceIdsJsonResp>, HttpHandleError> {
|
||||
let user_id = Self::get_user_id_from_machine(&client_mgr, &machine_id)?;
|
||||
Ok(client_mgr
|
||||
.handle_list_network_instance_ids((user_id, machine_id))
|
||||
.await
|
||||
@@ -335,10 +332,9 @@ impl NetworkApi {
|
||||
|
||||
async fn handle_collect_network_info_internal(
|
||||
State(client_mgr): AppState,
|
||||
Path(machine_id): Path<uuid::Uuid>,
|
||||
Path((user_id, machine_id)): Path<(UserIdInDb, uuid::Uuid)>,
|
||||
Json(payload): Json<CollectNetworkInfoJsonReq>,
|
||||
) -> Result<Json<CollectNetworkInfoResponse>, HttpHandleError> {
|
||||
let user_id = Self::get_user_id_from_machine(&client_mgr, &machine_id)?;
|
||||
Ok(client_mgr
|
||||
.handle_collect_network_info((user_id, machine_id), payload.inst_ids)
|
||||
.await
|
||||
@@ -346,32 +342,19 @@ impl NetworkApi {
|
||||
.into())
|
||||
}
|
||||
|
||||
/// Look up user_id from a machine's active session token.
|
||||
fn get_user_id_from_machine(
|
||||
client_mgr: &AppStateInner,
|
||||
machine_id: &uuid::Uuid,
|
||||
) -> Result<UserIdInDb, HttpHandleError> {
|
||||
client_mgr
|
||||
.get_user_id_by_machine_id_global(machine_id)
|
||||
.ok_or((
|
||||
StatusCode::NOT_FOUND,
|
||||
other_error("Machine not found").into(),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn build_route_internal() -> Router<AppStateInner> {
|
||||
Router::new()
|
||||
.route(
|
||||
"/api/internal/machines/:machine-id/networks",
|
||||
"/api/internal/users/:user-id/machines/:machine-id/networks",
|
||||
post(Self::handle_run_network_instance_internal)
|
||||
.get(Self::handle_list_network_instance_ids_internal),
|
||||
)
|
||||
.route(
|
||||
"/api/internal/machines/:machine-id/networks/:inst-id",
|
||||
"/api/internal/users/:user-id/machines/:machine-id/networks/:inst-id",
|
||||
delete(Self::handle_remove_network_instance_internal),
|
||||
)
|
||||
.route(
|
||||
"/api/internal/machines/:machine-id/networks/info",
|
||||
"/api/internal/users/:user-id/machines/:machine-id/networks/info",
|
||||
get(Self::handle_collect_network_info_internal),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ use axum::{
|
||||
use axum_login::AuthUser as _;
|
||||
use easytier::proto::rpc_types::controller::BaseController;
|
||||
|
||||
use crate::db::UserIdInDb;
|
||||
|
||||
use super::{other_error, AppState, HttpHandleError};
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
@@ -162,11 +164,11 @@ pub fn router() -> Router<super::AppStateInner> {
|
||||
/// Internal proxy-rpc handler: no AuthSession, resolves the active session by machine_id.
|
||||
pub async fn handle_proxy_rpc_internal(
|
||||
State(client_mgr): AppState,
|
||||
Path(machine_id): Path<uuid::Uuid>,
|
||||
Path((user_id, machine_id)): Path<(UserIdInDb, uuid::Uuid)>,
|
||||
Json(req): Json<ProxyRpcRequest>,
|
||||
) -> Result<Json<serde_json::Value>, HttpHandleError> {
|
||||
let session = client_mgr
|
||||
.get_session_by_machine_id_global(&machine_id)
|
||||
.get_session_by_machine_id(user_id, &machine_id)
|
||||
.ok_or((
|
||||
StatusCode::NOT_FOUND,
|
||||
other_error("Session not found").into(),
|
||||
@@ -176,7 +178,7 @@ pub async fn handle_proxy_rpc_internal(
|
||||
|
||||
pub fn router_internal() -> Router<super::AppStateInner> {
|
||||
Router::new().route(
|
||||
"/api/internal/machines/:machine-id/proxy-rpc",
|
||||
"/api/internal/users/:user-id/machines/:machine-id/proxy-rpc",
|
||||
post(handle_proxy_rpc_internal),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -51,6 +51,9 @@ pub struct ValidateTokenRequest {
|
||||
pub machine_id: String,
|
||||
pub hostname: String,
|
||||
pub version: String,
|
||||
pub os_type: Option<String>,
|
||||
pub os_version: Option<String>,
|
||||
pub os_distribution: Option<String>,
|
||||
pub web_instance_id: Option<String>,
|
||||
pub web_instance_api_base_url: Option<String>,
|
||||
}
|
||||
@@ -69,8 +72,12 @@ pub struct ValidateTokenResponse {
|
||||
pub struct NodeConnectedRequest {
|
||||
pub machine_id: String,
|
||||
pub token: String,
|
||||
pub user_id: Option<i32>,
|
||||
pub hostname: String,
|
||||
pub version: String,
|
||||
pub os_type: Option<String>,
|
||||
pub os_version: Option<String>,
|
||||
pub os_distribution: Option<String>,
|
||||
pub web_instance_id: Option<String>,
|
||||
pub binding_version: Option<u64>,
|
||||
}
|
||||
@@ -78,6 +85,8 @@ pub struct NodeConnectedRequest {
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct NodeDisconnectedRequest {
|
||||
pub machine_id: String,
|
||||
pub token: String,
|
||||
pub user_id: Option<i32>,
|
||||
pub web_instance_id: Option<String>,
|
||||
pub binding_version: Option<u64>,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user