Merge remote-tracking branch 'origin/pr/399' into aether-rust-pioneer

# Conflicts:
#	crates/aether-data/src/lifecycle/bootstrap/postgres.rs
#	crates/aether-data/src/lifecycle/migrate/tests.rs
This commit is contained in:
fawney19
2026-05-08 02:52:13 +08:00
34 changed files with 2212 additions and 100 deletions

View File

@@ -1,9 +1,16 @@
use super::super::internal;
use crate::admin_api;
use crate::control::GatewayPublicRequestContext;
use crate::audit::attach_admin_audit_event;
use crate::control::{
validate_management_token_admin_route_permission, GatewayPublicRequestContext,
};
use crate::{AppState, GatewayError};
use axum::body::{Body, Bytes};
use axum::http::Response;
use axum::http::{self, Response};
use axum::response::IntoResponse;
use axum::Json;
use serde_json::json;
use tracing::warn;
pub(super) async fn maybe_build_local_internal_proxy_response(
state: &AppState,
@@ -34,6 +41,10 @@ pub(super) async fn maybe_build_local_admin_proxy_response(
if decision.admin_principal.is_none() {
return Ok(None);
}
if let Some(response) = maybe_build_management_token_permission_denied_response(request_context)
{
return Ok(Some(response));
}
admin_api::maybe_build_local_admin_response(admin_api::AdminRouteRequest::new(
state,
@@ -42,3 +53,46 @@ pub(super) async fn maybe_build_local_admin_proxy_response(
))
.await
}
fn maybe_build_management_token_permission_denied_response(
request_context: &GatewayPublicRequestContext,
) -> Option<Response<Body>> {
let decision = request_context.control_decision.as_ref()?;
let admin_principal = decision.admin_principal.as_ref()?;
let token_id = admin_principal.management_token_id.as_deref()?;
let denied = validate_management_token_admin_route_permission(
&request_context.request_method,
decision,
admin_principal.management_token_permissions.as_deref(),
)
.err()?;
warn!(
trace_id = %request_context.trace_id,
admin_management_token_id = %token_id,
route_family = decision.route_family.as_deref().unwrap_or("unknown"),
route_kind = decision.route_kind.as_deref().unwrap_or("unknown"),
required_permission = %denied.required_permission,
"management token permission denied"
);
let mut response = (
http::StatusCode::FORBIDDEN,
Json(json!({
"detail": "management token permission denied",
"required_permission": denied.required_permission,
"route_family": decision.route_family.as_deref(),
"route_kind": decision.route_kind.as_deref(),
"request_path": request_context.request_path,
})),
)
.into_response();
attach_admin_audit_event(
&mut response,
"admin_management_token_permission_denied",
"permission_denied",
"management_token_permission",
token_id,
);
Some(response)
}

View File

@@ -31,9 +31,9 @@ use crate::constants::{
TUNNEL_AFFINITY_OWNER_INSTANCE_HEADER,
};
use crate::control::{
allows_control_execute_emergency, maybe_execute_via_control, request_model_local_rejection,
should_buffer_request_for_local_auth, trusted_auth_local_rejection, GatewayControlDecision,
GatewayPublicRequestContext,
allows_control_execute_emergency, management_token_permission_keys_from_value,
maybe_execute_via_control, request_model_local_rejection, should_buffer_request_for_local_auth,
trusted_auth_local_rejection, GatewayControlDecision, GatewayPublicRequestContext,
};
use crate::executor::{
beautify_local_execution_client_error_message, build_local_execution_runtime_miss_context,
@@ -229,12 +229,27 @@ async fn maybe_promote_management_token_admin_principal(
if !user.is_active || user.is_deleted || !user.role.eq_ignore_ascii_case("admin") {
return Ok(());
}
let management_token_permissions = match management_token_permission_keys_from_value(
token_with_user.token.permissions.as_ref(),
) {
Ok(value) => value,
Err(err) => {
warn!(
trace_id = %trace_id,
token_id = %token_with_user.token.id,
error = %err,
"gateway rejected management token with invalid permissions"
);
return Ok(());
}
};
decision.admin_principal = Some(crate::control::GatewayAdminPrincipalContext {
user_id: user.id.clone(),
user_role: user.role.clone(),
session_id: None,
management_token_id: Some(token_with_user.token.id.clone()),
management_token_permissions,
});
let remote_ip = remote_addr.ip().to_string();