Skip to content

Commit a9a3928

Browse files
committed
logger: fix overly permissive CORS configuration
- Restrict allowed methods to GET, POST, OPTIONS instead of Any - Restrict allowed headers to Content-Type instead of Any - Add CORS_ALLOWED_ORIGIN env var for production origin restriction - Log warning when CORS_ALLOWED_ORIGIN is not set - Remove redundant manual CORS headers from get_logs handler This prevents malicious websites from making cross-origin requests to the logger service and potentially accessing sensitive log data.
1 parent 0cbadd4 commit a9a3928

File tree

1 file changed

+42
-13
lines changed

1 file changed

+42
-13
lines changed

logger/src/server.rs

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use axum::{
22
BoxError, Router,
33
extract::Path,
4+
http::{HeaderValue, Method},
45
response::{
56
IntoResponse, Result,
67
sse::{Event, KeepAlive, Sse},
@@ -14,7 +15,7 @@ use serde::{Deserialize, Serialize};
1415
use slatedb::Db;
1516
use std::pin::Pin;
1617
use std::sync::Arc;
17-
use tower_http::cors::{self, CorsLayer};
18+
use tower_http::cors::CorsLayer;
1819
use uuid::Uuid;
1920

2021
use crate::Log;
@@ -32,17 +33,47 @@ struct LogWithLine {
3233
line: u64,
3334
}
3435

35-
pub fn create_app(state: Arc<AppState>) -> Router {
36+
/// Creates the CORS layer with appropriate security settings.
37+
///
38+
/// The allowed origin is determined by the CORS_ALLOWED_ORIGIN environment variable.
39+
/// If not set, defaults to allowing all origins for local development.
40+
/// In production, this should be set to the specific frontend origin.
41+
fn create_cors_layer() -> CorsLayer {
42+
let allowed_origin = std::env::var("CORS_ALLOWED_ORIGIN").ok();
43+
3644
let cors = CorsLayer::new()
37-
.allow_origin(cors::Any)
38-
.allow_methods(cors::Any)
39-
.allow_headers(cors::Any)
45+
.allow_methods([Method::GET, Method::POST, Method::OPTIONS])
46+
.allow_headers([axum::http::header::CONTENT_TYPE])
4047
.expose_headers([
4148
axum::http::header::CONTENT_TYPE,
4249
axum::http::header::CACHE_CONTROL,
4350
])
4451
.max_age(std::time::Duration::from_secs(3600));
4552

53+
match allowed_origin {
54+
Some(origin) => {
55+
// Use specific origin for production security
56+
cors.allow_origin(
57+
origin
58+
.parse::<HeaderValue>()
59+
.expect("CORS_ALLOWED_ORIGIN must be a valid header value"),
60+
)
61+
}
62+
None => {
63+
// Fall back to permissive for local development only
64+
// Log a warning to remind operators to set CORS_ALLOWED_ORIGIN in production
65+
eprintln!(
66+
"WARNING: CORS_ALLOWED_ORIGIN not set, allowing all origins. \
67+
Set this environment variable in production."
68+
);
69+
cors.allow_origin(tower_http::cors::Any)
70+
}
71+
}
72+
}
73+
74+
pub fn create_app(state: Arc<AppState>) -> Router {
75+
let cors = create_cors_layer();
76+
4677
Router::new()
4778
.route("/{uuid}", post(post_logs))
4879
.route("/{uuid}", get(get_logs))
@@ -134,19 +165,17 @@ async fn get_logs(
134165
let sse = Sse::new(stream).keep_alive(keep_alive);
135166
let mut response = sse.into_response();
136167

168+
// Set cache and content-type headers for SSE
169+
// Note: CORS headers are handled by the CorsLayer middleware
137170
let headers = response.headers_mut();
138-
headers.insert("Access-Control-Allow-Origin", "*".parse().unwrap());
139171
headers.insert(
140-
"Access-Control-Allow-Methods",
141-
"GET, POST, OPTIONS".parse().unwrap(),
172+
axum::http::header::CACHE_CONTROL,
173+
"no-cache".parse().expect("valid header value"),
142174
);
143-
headers.insert("Access-Control-Allow-Headers", "*".parse().unwrap());
144175
headers.insert(
145-
"Access-Control-Expose-Headers",
146-
"Content-Type, Cache-Control".parse().unwrap(),
176+
axum::http::header::CONTENT_TYPE,
177+
"text/event-stream".parse().expect("valid header value"),
147178
);
148-
headers.insert("Cache-Control", "no-cache".parse().unwrap());
149-
headers.insert("Content-Type", "text/event-stream".parse().unwrap());
150179

151180
response
152181
}

0 commit comments

Comments
 (0)