diff options
Diffstat (limited to 'src/api/mod.rs')
| -rw-r--r-- | src/api/mod.rs | 49 |
1 files changed, 47 insertions, 2 deletions
diff --git a/src/api/mod.rs b/src/api/mod.rs index a2bd7e0..affb9ef 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -2,12 +2,15 @@ pub(crate) mod auth; mod debug; +mod document; mod log; mod peers; +mod stats; mod store; use axum::{ - Router, + Json, Router, + extract::State, http::{ HeaderValue, StatusCode, header::{CONTENT_TYPE, ETAG}, @@ -21,7 +24,7 @@ use tracing::{error, warn}; use utoipa::OpenApi; use utoipa_swagger_ui::SwaggerUi; -use crate::api::auth::{AuthState, basic_auth}; +use crate::api::auth::{AuthState, AuthenticatedUser, basic_auth}; use crate::document::store::StoreError; use crate::state::AppState; @@ -42,6 +45,9 @@ pub(crate) fn router(state: AppState, auth_state: AuthState, debug_api: bool) -> "/admin/log", get(log::get_log_filter).put(log::put_log_filter), ) + .route("/admin/stats", get(stats::get_stats)) + .route("/admin/document", get(document::get_document)) + .route("/admin/config", get(get_config)) .merge(crate::remake::router::<AppState>()); if debug_api { @@ -59,6 +65,12 @@ pub(crate) fn router(state: AppState, auth_state: AuthState, debug_api: bool) -> .with_state(state) } +/// `GET /admin/config`: the effective configuration with each value's source; +/// imported values are left out. +async fn get_config(State(state): State<AppState>) -> Json<JsonValue> { + Json((*state.config).clone()) +} + // --- shared helpers ---------------------------------------------------------- pub(crate) fn store_status(error: StoreError, context: &str) -> StatusCode { @@ -92,6 +104,34 @@ fn parse_key_path(key: &str) -> Result<Vec<String>, StatusCode> { Ok(segments) } +/// A path for a write. Protocol data (`_continuum`: remake records, members, forget +/// records, epoch metadata) is what every node acts on, so a write there acts on the +/// whole swarm: only users listed in `--protocol-writers` may, and each such write is +/// logged. Epoch metadata (and `_continuum` as a whole, which contains it) is never +/// writable: the protocol reads it at the snapshot change, so an edit only misleads +/// whoever reads it later. `403` otherwise. +fn parse_write_path( + state: &AppState, + user: &AuthenticatedUser, + key: &str, + method: &str, +) -> Result<Vec<String>, StatusCode> { + let path = parse_key_path(key)?; + if path.first().map(String::as_str) != Some(crate::remake::snapshot::META_ROOT) { + return Ok(path); + } + // `_continuum` itself, epoch metadata and the swarm ID: nobody. + let fixed = path.len() < 2 + || path[1] == crate::remake::snapshot::EPOCHS + || path[1] == crate::remake::snapshot::SWARM; + if fixed || !state.protocol_writers.contains(&user.0) { + warn!(user = %user.0, method, path = %key, "Refused a write to protocol data"); + return Err(StatusCode::FORBIDDEN); + } + warn!(user = %user.0, method, path = %key, "Protocol data written through the store API; this acts on the whole swarm"); + Ok(path) +} + fn response_for_value( value: &JsonValue, accept: Option<&HeaderValue>, @@ -153,6 +193,11 @@ fn response_for_value( peers::PeerListResponse, peers::PeerLivenessStatus, peers::EpochInfo, + crate::transport::traffic::PeerTraffic, + crate::transport::traffic::ByKind, + crate::transport::traffic::Counter, + crate::state::Incidents, + peers::ThroughputInfo, ) ), tags( |
