alopex_server/http/
admin.rs1use 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}