Fix credential ospf logic, fix udp subnet proxy loop protection (#2315)

This commit is contained in:
KKRainbow
2026-06-07 12:40:09 +08:00
committed by GitHub
parent 793b57c2a1
commit e38b1354b3
24 changed files with 1892 additions and 703 deletions
+18 -15
View File
@@ -20,7 +20,7 @@ use session::{Location, Session};
use storage::{Storage, StorageToken};
use crate::FeatureFlags;
use crate::webhook::SharedWebhookConfig;
use crate::webhook::{ManagedNetworkConfig, SharedWebhookConfig};
use tokio::task::JoinSet;
use crate::db::{Db, UserIdInDb, entity::user_running_network_configs};
@@ -146,20 +146,7 @@ impl ClientManager {
}
pub async fn list_sessions(&self) -> Vec<StorageToken> {
let sessions = self
.client_sessions
.iter()
.map(|item| item.value().clone())
.collect::<Vec<_>>();
let mut ret: Vec<StorageToken> = vec![];
for s in sessions {
if let Some(t) = s.get_token().await {
ret.push(t);
}
}
ret
self.storage.list_clients()
}
pub fn get_session_by_machine_id(
@@ -197,6 +184,22 @@ impl ClientManager {
self.storage.list_user_clients(user_id)
}
pub async fn reconcile_managed_network_configs(
&self,
user_id: UserIdInDb,
machine_id: uuid::Uuid,
desired_configs: Vec<ManagedNetworkConfig>,
) -> anyhow::Result<()> {
session::SessionRpcService::reconcile_web_source_configs(
&self.storage,
user_id,
machine_id,
desired_configs,
)
.await?;
Ok(())
}
pub async fn get_heartbeat_requests(&self, client_url: &url::Url) -> Option<HeartbeatRequest> {
let s = self.client_sessions.get(client_url)?.clone();
s.data().read().await.req()
File diff suppressed because it is too large Load Diff
@@ -114,6 +114,20 @@ impl Storage {
.unwrap_or_default()
}
pub fn list_clients(&self) -> Vec<StorageToken> {
self.0
.user_clients_map
.iter()
.flat_map(|user_clients| {
user_clients
.value()
.iter()
.map(|info| info.value().storage_token.clone())
.collect::<Vec<_>>()
})
.collect()
}
pub fn db(&self) -> &Db {
&self.0.db
}
@@ -174,4 +188,25 @@ mod tests {
assert_eq!(storage.get_client_url_by_machine_id(2, &machine_id), None);
}
#[tokio::test]
async fn list_clients_returns_current_storage_tokens() {
let storage = Storage::new(Db::memory_db().await);
let user1_token = make_storage_token(1, uuid::Uuid::new_v4(), "tcp://127.0.0.1:1001");
let user2_token = make_storage_token(2, uuid::Uuid::new_v4(), "tcp://127.0.0.1:1002");
storage.update_client(user1_token.clone(), 10);
storage.update_client(user2_token.clone(), 20);
let tokens = storage.list_clients();
assert_eq!(tokens.len(), 2);
assert!(tokens.iter().any(|token| token.token == user1_token.token));
assert!(tokens.iter().any(|token| token.token == user2_token.token));
storage.remove_client(&user1_token);
let tokens = storage.list_clients();
assert_eq!(tokens.len(), 1);
assert_eq!(tokens[0].token, user2_token.token);
}
}
+6 -6
View File
@@ -331,7 +331,7 @@ mod tests {
(user_id, device_id),
inst_id,
network_config,
ConfigSource::Webhook,
ConfigSource::Web,
)
.await
.unwrap();
@@ -344,10 +344,10 @@ mod tests {
.unwrap();
println!("device: {}, {:?}", device_id, result2);
assert_eq!(result2.network_config, network_config_json);
assert_eq!(result2.get_network_config_source(), ConfigSource::Webhook);
assert_eq!(result2.get_network_config_source(), ConfigSource::Web);
assert_eq!(
result2.get_runtime_network_config_source(),
ConfigSource::Webhook
ConfigSource::Web
);
assert_eq!(result.create_time, result2.create_time);
@@ -373,7 +373,7 @@ mod tests {
}
#[tokio::test]
async fn test_legacy_network_config_defaults_to_user_runtime_source() {
async fn test_unknown_network_config_source_defaults_to_user_runtime_source() {
let db = Db::memory_db().await;
let user_id = 1;
let inst_id = uuid::Uuid::new_v4();
@@ -384,11 +384,11 @@ mod tests {
device_id: Set(device_id.to_string()),
network_instance_id: Set(inst_id.to_string()),
network_config: Set(serde_json::to_string(&NetworkConfig {
network_name: Some("legacy".to_string()),
network_name: Some("unknown-source".to_string()),
..Default::default()
})
.unwrap()),
source: Set("legacy".to_string()),
source: Set("unknown".to_string()),
disabled: Set(false),
create_time: Set(sqlx::types::chrono::Local::now().fixed_offset()),
update_time: Set(sqlx::types::chrono::Local::now().fixed_offset()),
@@ -48,7 +48,7 @@ impl MigrationTrait for Migration {
device_id,
network_instance_id,
network_config,
'legacy',
'user',
disabled,
create_time,
update_time
@@ -0,0 +1,42 @@
use sea_orm_migration::prelude::*;
pub struct Migration;
impl MigrationName for Migration {
fn name(&self) -> &str {
"m20260514_000004_rename_web_config_source"
}
}
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
let db = manager.get_connection();
db.execute_unprepared(
r#"
UPDATE user_running_network_configs
SET source = 'web'
WHERE source = 'webhook';
UPDATE user_running_network_configs
SET source = 'user'
WHERE source = 'legacy';
"#,
)
.await?;
Ok(())
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
let db = manager.get_connection();
db.execute_unprepared(
r#"
UPDATE user_running_network_configs
SET source = 'webhook'
WHERE source = 'web';
"#,
)
.await?;
Ok(())
}
}
+2
View File
@@ -3,6 +3,7 @@ use sea_orm_migration::prelude::*;
mod m20241029_000001_init;
mod m20260403_000002_scope_network_config_unique;
mod m20260421_000003_add_network_config_source;
mod m20260514_000004_rename_web_config_source;
pub struct Migrator;
@@ -13,6 +14,7 @@ impl MigratorTrait for Migrator {
Box::new(m20241029_000001_init::Migration),
Box::new(m20260403_000002_scope_network_config_unique::Migration),
Box::new(m20260421_000003_add_network_config_source::Migration),
Box::new(m20260514_000004_rename_web_config_source::Migration),
]
}
}
+53 -3
View File
@@ -3,6 +3,7 @@ use axum::http::StatusCode;
use axum::routing::{delete, post};
use axum::{Json, Router, extract::State, routing::get};
use axum_login::AuthUser;
use easytier::common::config::ConfigSource as RuntimeConfigSource;
use easytier::launcher::NetworkConfig;
use easytier::proto::common::Void;
use easytier::proto::{api::manage::*, web::*};
@@ -60,6 +61,7 @@ struct SaveNetworkJsonReq {
struct RunNetworkJsonReq {
config: NetworkConfig,
save: bool,
source: Option<i32>,
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
@@ -82,6 +84,17 @@ struct RemoveNetworkJsonReq {
inst_ids: Vec<uuid::Uuid>,
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
struct ManagedNetworkConfigJson {
instance_id: uuid::Uuid,
network_config: serde_json::Value,
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
struct ReconcileManagedNetworkConfigsJsonReq {
managed_network_configs: Vec<ManagedNetworkConfigJson>,
}
#[derive(Debug, serde::Deserialize, serde::Serialize)]
struct ListMachineItem {
client_url: Option<url::Url>,
@@ -130,10 +143,11 @@ impl NetworkApi {
Json(payload): Json<RunNetworkJsonReq>,
) -> Result<Json<Void>, HttpHandleError> {
client_mgr
.handle_run_network_instance(
.handle_run_network_instance_with_source(
(Self::get_user_id(&auth_session)?, machine_id),
payload.config,
payload.save,
RuntimeConfigSource::Web,
)
.await
.map_err(convert_error)?;
@@ -274,10 +288,11 @@ impl NetworkApi {
));
}
client_mgr
.handle_save_network_config(
.handle_save_network_config_with_source(
(Self::get_user_id(&auth_session)?, machine_id),
inst_id,
payload.config,
RuntimeConfigSource::Web,
)
.await
.map_err(convert_error)
@@ -302,8 +317,17 @@ impl NetworkApi {
Path((user_id, machine_id)): Path<(UserIdInDb, uuid::Uuid)>,
Json(payload): Json<RunNetworkJsonReq>,
) -> Result<Json<Void>, HttpHandleError> {
let source = payload
.source
.and_then(RuntimeConfigSource::from_rpc)
.unwrap_or(RuntimeConfigSource::Web);
client_mgr
.handle_run_network_instance((user_id, machine_id), payload.config, payload.save)
.handle_run_network_instance_with_source(
(user_id, machine_id),
payload.config,
payload.save,
source,
)
.await
.map_err(convert_error)?;
Ok(Void::default().into())
@@ -319,6 +343,31 @@ impl NetworkApi {
.map_err(convert_error)
}
async fn handle_reconcile_managed_network_configs_internal(
State(client_mgr): AppState,
Path((user_id, machine_id)): Path<(UserIdInDb, uuid::Uuid)>,
Json(payload): Json<ReconcileManagedNetworkConfigsJsonReq>,
) -> Result<Json<Void>, HttpHandleError> {
let desired = payload
.managed_network_configs
.into_iter()
.map(|item| crate::webhook::ManagedNetworkConfig {
instance_id: item.instance_id.to_string(),
network_config: item.network_config,
})
.collect();
client_mgr
.reconcile_managed_network_configs(user_id, machine_id, desired)
.await
.map_err(|err| {
(
StatusCode::INTERNAL_SERVER_ERROR,
other_error(err.to_string()).into(),
)
})?;
Ok(Void::default().into())
}
async fn handle_list_network_instance_ids_internal(
State(client_mgr): AppState,
Path((user_id, machine_id)): Path<(UserIdInDb, uuid::Uuid)>,
@@ -347,6 +396,7 @@ impl NetworkApi {
.route(
"/api/internal/users/:user-id/machines/:machine-id/networks",
post(Self::handle_run_network_instance_internal)
.put(Self::handle_reconcile_managed_network_configs_internal)
.get(Self::handle_list_network_instance_ids_internal),
)
.route(
+16 -6
View File
@@ -16,6 +16,7 @@ pub struct ProxyRpcRequest {
pub service_name: String,
pub method_name: String,
pub payload: serde_json::Value,
pub scope: Option<String>,
}
macro_rules! match_service {
@@ -35,6 +36,7 @@ async fn handle_proxy_rpc_by_session(
service_name,
method_name,
payload,
scope,
} = req;
let resp = match service_name.as_str() {
@@ -74,12 +76,20 @@ async fn handle_proxy_rpc_by_session(
payload,
session
),
"api.instance.TcpProxyRpcService" => match_service!(
easytier::proto::api::instance::TcpProxyRpcClientFactory<BaseController>,
method_name,
payload,
session
),
"api.instance.TcpProxyRpcService" => {
let client = if let Some(ref domain) = scope {
session.scoped_client_with_domain::<
easytier::proto::api::instance::TcpProxyRpcClientFactory<BaseController>,
>(domain.clone())
} else {
session.scoped_client::<
easytier::proto::api::instance::TcpProxyRpcClientFactory<BaseController>,
>()
};
client
.json_call_method(BaseController::default(), &method_name, payload)
.await
}
"api.instance.AclManageRpcService" => match_service!(
easytier::proto::api::instance::AclManageRpcClientFactory<BaseController>,
method_name,
+18 -1
View File
@@ -57,6 +57,8 @@ pub struct ValidateTokenRequest {
pub os_distribution: Option<String>,
pub web_instance_id: Option<String>,
pub web_instance_api_base_url: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub applied_config_revision: Option<String>,
}
#[derive(Debug, Deserialize)]
@@ -66,7 +68,8 @@ pub struct ValidateTokenResponse {
pub pre_approved: bool,
#[serde(default)]
pub binding_version: u64,
pub managed_network_configs: Vec<ManagedNetworkConfig>,
#[serde(default)]
pub managed_network_configs: Option<Vec<ManagedNetworkConfig>>,
pub config_revision: String,
}
@@ -184,3 +187,17 @@ impl WebhookConfig {
}
pub type SharedWebhookConfig = Arc<WebhookConfig>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn validate_token_response_allows_missing_managed_configs() {
let resp: ValidateTokenResponse =
serde_json::from_str(r#"{"valid":true,"config_revision":"rev-1"}"#).unwrap();
assert!(resp.valid);
assert_eq!(resp.config_revision, "rev-1");
assert!(resp.managed_network_configs.is_none());
}
}