Skip to main content

alopex_server/http/
admin.rs

1use std::net::SocketAddr;
2use std::sync::Arc;
3
4use axum::extract::{ConnectInfo, Extension};
5use axum::http::StatusCode;
6use axum::middleware;
7use axum::response::{IntoResponse, Response};
8use axum::{Json, Router};
9use serde::Serialize;
10
11use crate::ops::status::StatusReporter;
12use crate::server::ServerState;
13
14#[derive(Serialize)]
15struct StatusResponse {
16    status: &'static str,
17}
18
19pub fn router(state: Arc<ServerState>) -> Router {
20    Router::new()
21        .route("/healthz", axum::routing::get(healthz))
22        .route("/status", axum::routing::get(status))
23        .route("/metrics", axum::routing::get(metrics))
24        .layer(middleware::from_fn(allowlist_middleware))
25        .layer(axum::Extension(state))
26}
27
28async fn healthz() -> impl IntoResponse {
29    StatusCode::OK
30}
31
32async fn status() -> impl IntoResponse {
33    Json(StatusResponse { status: "ok" })
34}
35
36async fn metrics(Extension(state): Extension<Arc<ServerState>>) -> Response {
37    if !state.config.metrics_enabled {
38        return StatusCode::NOT_FOUND.into_response();
39    }
40    let reporter = StatusReporter::new(state.lifecycle_state.clone(), state.recovery_info.clone());
41    reporter.refresh_metrics(&state.metrics);
42    match state.metrics.expose_prometheus() {
43        Ok(body) => (
44            StatusCode::OK,
45            [(
46                axum::http::header::CONTENT_TYPE,
47                "text/plain; version=0.0.4",
48            )],
49            body,
50        )
51            .into_response(),
52        Err(err) => (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()).into_response(),
53    }
54}
55
56async fn allowlist_middleware<B>(
57    Extension(state): Extension<Arc<ServerState>>,
58    req: axum::http::Request<B>,
59    next: middleware::Next<B>,
60) -> Response {
61    if state.config.admin_bind.ip().is_loopback() {
62        return next.run(req).await;
63    }
64    let Some(addr) = req.extensions().get::<ConnectInfo<SocketAddr>>() else {
65        return StatusCode::FORBIDDEN.into_response();
66    };
67    let ip = addr.ip();
68    if ip.is_loopback() || state.config.admin_allowlist.contains(&ip) {
69        next.run(req).await
70    } else {
71        StatusCode::FORBIDDEN.into_response()
72    }
73}