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