micromegas/servers/
firehose_common.rs1use axum::body::Body;
13use axum::extract::Request;
14use axum::http::{HeaderMap, HeaderValue, StatusCode, header};
15use axum::middleware::Next;
16use axum::response::Response;
17use chrono::Utc;
18use micromegas_auth::client_ip::resolve_client_ip;
19use micromegas_auth::types::{AuthProvider, HttpRequestParts, ProviderUnavailable, RequestParts};
20use micromegas_tracing::prelude::*;
21use std::sync::Arc;
22
23pub(crate) const HEADER_ACCESS_KEY: &str = "X-Amz-Firehose-Access-Key";
24pub(crate) const HEADER_REQUEST_ID: &str = "X-Amz-Firehose-Request-Id";
25
26#[derive(serde::Serialize)]
29struct FirehoseResponseBody<'a> {
30 #[serde(rename = "requestId")]
31 request_id: &'a str,
32 timestamp: i64,
33 #[serde(rename = "errorMessage", skip_serializing_if = "Option::is_none")]
34 error_message: Option<&'a str>,
35}
36
37pub(crate) fn firehose_response(
38 status: StatusCode,
39 request_id: &str,
40 error_message: Option<&str>,
41) -> Response {
42 let body = FirehoseResponseBody {
43 request_id,
44 timestamp: Utc::now().timestamp_millis(),
45 error_message,
46 };
47 Response::builder()
48 .status(status)
49 .header(
50 header::CONTENT_TYPE,
51 HeaderValue::from_static("application/json"),
52 )
53 .body(Body::from(
54 serde_json::to_vec(&body).expect("serializing firehose response"),
55 ))
56 .expect("building firehose response")
57}
58
59pub(crate) fn request_id_from(headers: &HeaderMap) -> String {
60 headers
61 .get(HEADER_REQUEST_ID)
62 .and_then(|v| v.to_str().ok())
63 .unwrap_or("")
64 .to_string()
65}
66
67pub async fn firehose_auth_middleware(
73 provider: Arc<dyn AuthProvider>,
74 mut req: Request,
75 next: Next,
76) -> Response {
77 let request_id = request_id_from(req.headers());
78 let Some(access_key) = req
79 .headers()
80 .get(HEADER_ACCESS_KEY)
81 .and_then(|v| v.to_str().ok())
82 else {
83 return firehose_response(
84 StatusCode::UNAUTHORIZED,
85 &request_id,
86 Some("missing X-Amz-Firehose-Access-Key"),
87 );
88 };
89 let client_ip = resolve_client_ip(req.headers(), req.extensions());
90 let mut headers = req.headers().clone();
91 if let Ok(bearer) = HeaderValue::from_str(&format!("Bearer {access_key}")) {
92 headers.insert(header::AUTHORIZATION, bearer);
93 }
94 let parts = HttpRequestParts {
95 headers,
96 method: req.method().clone(),
97 uri: req.uri().clone(),
98 client_ip,
99 };
100 match provider.validate_request(&parts as &dyn RequestParts).await {
101 Ok(ctx) => {
102 req.headers_mut().remove("x-auth-subject");
106 req.headers_mut().remove("x-auth-email");
107 req.headers_mut().remove("x-auth-issuer");
108 req.headers_mut().remove("x-allow-delegation");
109 req.headers_mut().remove("x-auth-is-admin");
110 req.extensions_mut().insert(ctx);
114 next.run(req).await
115 }
116 Err(e) => {
117 if e.downcast_ref::<ProviderUnavailable>().is_some() {
123 debug!("[firehose auth_failure] {e}");
124 firehose_response(
125 StatusCode::SERVICE_UNAVAILABLE,
126 &request_id,
127 Some("auth provider unavailable"),
128 )
129 } else {
130 warn!("[firehose auth_failure] {e}");
131 firehose_response(
132 StatusCode::UNAUTHORIZED,
133 &request_id,
134 Some("invalid access key"),
135 )
136 }
137 }
138 }
139}