bws_web_server/server/
management_api.rs1use crate::config::ManagementConfig;
7use crate::handlers::ApiHandler;
8use crate::server::WebServerService;
9use async_trait::async_trait;
10use pingora::http::ResponseHeader;
11use pingora::prelude::*;
12use std::sync::Arc;
13
14#[derive(Clone)]
16pub struct ManagementApiService {
17 api_handler: ApiHandler,
18 config: ManagementConfig,
19}
20
21impl ManagementApiService {
22 pub fn new(_web_service: Arc<WebServerService>, config: ManagementConfig) -> Self {
24 Self {
25 api_handler: ApiHandler::new(),
26 config,
27 }
28 }
29
30 fn is_localhost_request(&self, session: &Session) -> bool {
32 if let Some(client_addr) = session.client_addr() {
33 if let Some(socket_addr) = client_addr.as_inet() {
34 let ip = socket_addr.ip();
35 return ip.is_loopback();
36 }
37 }
38 false
39 }
40
41 fn check_api_key(&self, session: &Session) -> bool {
43 if let Some(expected_key) = &self.config.api_key {
44 if let Some(provided_key) = session.get_header("X-API-Key") {
45 if let Ok(key_str) = provided_key.to_str() {
46 return key_str == expected_key;
47 }
48 }
49 false
50 } else {
51 true }
53 }
54
55 async fn send_error_response(
57 &self,
58 session: &mut Session,
59 status: u16,
60 message: &str,
61 ) -> Result<()> {
62 let error_body = format!(r#"{{"error": "{}"}}"#, message);
63 let mut header = ResponseHeader::build(status, Some(4))?;
64 header.insert_header("Content-Type", "application/json; charset=utf-8")?;
65 header.insert_header("Content-Length", error_body.len().to_string())?;
66 header.insert_header("Cache-Control", "no-cache, no-store, must-revalidate")?;
67
68 session
69 .write_response_header(Box::new(header), false)
70 .await?;
71 session
72 .write_response_body(Some(error_body.into_bytes().into()), true)
73 .await?;
74
75 Ok(())
76 }
77
78 async fn send_success_response(&self, session: &mut Session, message: &str) -> Result<()> {
80 let success_body = format!(r#"{{"message": "{}"}}"#, message);
81 let mut header = ResponseHeader::build(200, Some(4))?;
82 header.insert_header("Content-Type", "application/json; charset=utf-8")?;
83 header.insert_header("Content-Length", success_body.len().to_string())?;
84 header.insert_header("Cache-Control", "no-cache, no-store, must-revalidate")?;
85
86 session
87 .write_response_header(Box::new(header), false)
88 .await?;
89 session
90 .write_response_body(Some(success_body.into_bytes().into()), true)
91 .await?;
92
93 Ok(())
94 }
95}
96
97#[async_trait]
98impl ProxyHttp for ManagementApiService {
99 type CTX = ();
100
101 fn new_ctx(&self) -> Self::CTX {}
102
103 async fn upstream_peer(
104 &self,
105 _session: &mut Session,
106 _ctx: &mut Self::CTX,
107 ) -> Result<Box<HttpPeer>> {
108 Err(Error::new(ErrorType::InternalError).into_down())
110 }
111
112 async fn request_filter(&self, session: &mut Session, _ctx: &mut Self::CTX) -> Result<bool> {
113 if !self.is_localhost_request(session) {
115 log::warn!(
116 "Management API access denied: request not from localhost ({})",
117 session
118 .client_addr()
119 .map(|addr| addr.to_string())
120 .unwrap_or_else(|| "unknown".to_string())
121 );
122 self.send_error_response(session, 403, "Access denied: localhost only")
123 .await?;
124 return Ok(true);
125 }
126
127 if !self.check_api_key(session) {
129 log::warn!("Management API access denied: invalid or missing API key");
130 self.send_error_response(session, 401, "Unauthorized: invalid API key")
131 .await?;
132 return Ok(true);
133 }
134
135 let path = session.req_header().uri.path();
136 let method = session.req_header().method.as_str();
137
138 match (method, path) {
139 ("POST", "/api/config/reload") => {
140 log::info!("Management API: Config reload requested");
141
142 match self.api_handler.handle(session, None).await {
144 Ok(_) => {
145 log::info!("Configuration reloaded successfully via management API");
146 self.send_success_response(session, "Configuration reloaded successfully")
147 .await?;
148 }
149 Err(e) => {
150 log::error!("Configuration reload failed via management API: {}", e);
151 self.send_error_response(session, 500, "Configuration reload failed")
152 .await?;
153 return Ok(true);
154 }
155 }
156 Ok(true)
157 }
158 _ => {
159 self.send_error_response(session, 404, "Endpoint not found")
161 .await?;
162 Ok(true)
163 }
164 }
165 }
166
167 async fn upstream_request_filter(
168 &self,
169 _session: &mut Session,
170 _upstream_request: &mut pingora::http::RequestHeader,
171 _ctx: &mut Self::CTX,
172 ) -> Result<()> {
173 Ok(())
175 }
176
177 async fn response_filter(
178 &self,
179 _session: &mut Session,
180 _upstream_response: &mut pingora::http::ResponseHeader,
181 _ctx: &mut Self::CTX,
182 ) -> Result<()> {
183 Ok(())
185 }
186
187 async fn logging(
188 &self,
189 session: &mut Session,
190 _e: Option<&pingora::Error>,
191 _ctx: &mut Self::CTX,
192 ) {
193 if let Some(client_addr) = session.client_addr() {
194 log::info!(
195 "Management API request: {} {} from {}",
196 session.req_header().method,
197 session.req_header().uri.path(),
198 client_addr
199 );
200 }
201 }
202}