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::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.cluster_status_snapshot() {
43 Ok(snapshot) => state.metrics.record_cluster_status(&snapshot),
44 Err(err) => return (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()).into_response(),
45 }
46 match state.metrics.expose_prometheus() {
47 Ok(body) => (
48 StatusCode::OK,
49 [(
50 axum::http::header::CONTENT_TYPE,
51 "text/plain; version=0.0.4",
52 )],
53 body,
54 )
55 .into_response(),
56 Err(err) => (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()).into_response(),
57 }
58}
59
60async fn allowlist_middleware(
61 Extension(state): Extension<Arc<ServerState>>,
62 req: axum::extract::Request,
63 next: middleware::Next,
64) -> Response {
65 if state.config.admin_bind.ip().is_loopback() {
66 return next.run(req).await;
67 }
68 let Some(addr) = req.extensions().get::<ConnectInfo<SocketAddr>>() else {
69 return StatusCode::FORBIDDEN.into_response();
70 };
71 let ip = addr.ip();
72 if ip.is_loopback() || state.config.admin_allowlist.contains(&ip) {
73 next.run(req).await
74 } else {
75 StatusCode::FORBIDDEN.into_response()
76 }
77}