mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-21 08:02:26 +00:00
19af16729c
* feat: add optional CSP headers when ENABLE_CSP_HEADERS is set - Add middleware to conditionally set Content-Security-Policy headers - Check for ENABLE_CSP_HEADERS environment variable - Apply secure default CSP policy suitable for web applications - Follows existing middleware patterns in codebase Resolves #6031 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Ruben Fiszel <rubenfiszel@users.noreply.github.com> * perf: optimize CSP headers with lazy_static and add configurability - Cache CSP_ENABLED and CSP_POLICY using lazy_static to avoid env var lookups on every request - Add comprehensive documentation to add_csp_headers middleware function - Make CSP policy configurable via CSP_POLICY environment variable - Maintain backward compatibility with secure default policy - Implement proper error handling for custom CSP policy values Co-authored-by: Alexander Petric <alpetric@users.noreply.github.com> * refactor: conditionally add CSP middleware layer instead of NO-OP - Use .option_layer() to only add CSP middleware when ENABLE_CSP_HEADERS is set - Remove runtime conditional check from add_csp_headers function - Improve performance by avoiding unnecessary middleware execution when disabled Co-authored-by: Ruben Fiszel <rubenfiszel@users.noreply.github.com> * refactor: conditionally add CSP middleware layer instead of NO-OP Remove CSP_ENABLED variable and instead check if CSP_POLICY is not empty. This eliminates the need for a separate enable flag and simplifies the logic to just check if a meaningful CSP policy is configured. - Remove CSP_ENABLED lazy_static variable - Simplify CSP_POLICY to use unwrap_or_default() - Update conditional middleware logic to check !CSP_POLICY.is_empty() - Update documentation to reflect new behavior Co-authored-by: Ruben Fiszel <rubenfiszel@users.noreply.github.com> * refactor: conditionally add CSP middleware layer instead of NO-OP Move CSP headers from general middleware stack to static assets only. CSP headers are only relevant for browsers consuming static content, not for API endpoints. Changes: - Add CSP headers directly to static asset responses in serve_path() - Remove CSP middleware from general middleware stack - Remove unused add_csp_headers middleware function - Maintain same CSP_POLICY environment variable configuration Co-authored-by: Alexander Petric <alpetric@users.noreply.github.com> * fix: conditionally compile CSP header imports with static_frontend feature Fixes unused import errors by wrapping HeaderValue and CSP_POLICY imports with #[cfg(feature = "static_frontend")] to match their usage context. Co-authored-by: Alexander Petric <alpetric@users.noreply.github.com> * refactor: move CSP lazy static block to static_assets.rs - Move CSP_POLICY lazy static from lib.rs to static_assets.rs for better encapsulation - Remove crate-level import since CSP_POLICY is now local to static_assets module - Maintain same functionality with proper feature gating for static_frontend Co-authored-by: Ruben Fiszel <rubenfiszel@users.noreply.github.com> --------- Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Co-authored-by: Ruben Fiszel <rubenfiszel@users.noreply.github.com> Co-authored-by: Alexander Petric <alpetric@users.noreply.github.com>
95 lines
3.3 KiB
Rust
95 lines
3.3 KiB
Rust
/*
|
|
* Author: Ruben Fiszel
|
|
* Copyright: Windmill Labs, Inc 2022
|
|
* This file and its contents are licensed under the AGPLv3 License.
|
|
* Please see the included NOTICE for copyright information and
|
|
* LICENSE-AGPL for a copy of the license.
|
|
*/
|
|
|
|
use axum::{body::Body, extract::OriginalUri, http::Response, response::IntoResponse};
|
|
|
|
#[cfg(feature = "static_frontend")]
|
|
use axum::http::header;
|
|
#[cfg(feature = "static_frontend")]
|
|
use http::HeaderValue;
|
|
|
|
use hyper::Uri;
|
|
#[cfg(feature = "static_frontend")]
|
|
use mime_guess::mime;
|
|
#[cfg(feature = "static_frontend")]
|
|
use rust_embed::RustEmbed;
|
|
|
|
// Content Security Policy configuration
|
|
#[cfg(feature = "static_frontend")]
|
|
lazy_static::lazy_static! {
|
|
static ref CSP_POLICY: String = std::env::var("CSP_POLICY").unwrap_or_default();
|
|
}
|
|
|
|
// static_handler is a handler that serves static files from the
|
|
pub async fn static_handler(OriginalUri(original_uri): OriginalUri) -> StaticFile {
|
|
StaticFile(original_uri)
|
|
}
|
|
|
|
#[cfg(feature = "static_frontend")]
|
|
#[derive(RustEmbed)]
|
|
#[folder = "${FRONTEND_BUILD_DIR:-../../frontend/build/}"]
|
|
struct Asset;
|
|
pub struct StaticFile(Uri);
|
|
|
|
impl IntoResponse for StaticFile {
|
|
fn into_response(self) -> Response<Body> {
|
|
let path = self.0.path().trim_start_matches('/');
|
|
serve_path(path)
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "static_frontend")]
|
|
const TWO_HUNDRED: &str = "200.html";
|
|
|
|
fn serve_path(path: &str) -> Response<Body> {
|
|
if path.starts_with("api/") {
|
|
return Response::builder().status(404).body(Body::empty()).unwrap();
|
|
}
|
|
|
|
#[cfg(feature = "static_frontend")]
|
|
match Asset::get(path) {
|
|
Some(content) => {
|
|
let body = Body::from(content.data);
|
|
let mime = mime_guess::from_path(path).first_or_octet_stream();
|
|
let mut res = Response::builder()
|
|
.header(header::CONTENT_TYPE, mime.as_ref())
|
|
.header(header::ACCESS_CONTROL_ALLOW_ORIGIN, "*");
|
|
|
|
// Add Content-Security-Policy header for static assets when policy is set
|
|
if !CSP_POLICY.is_empty() {
|
|
if let Ok(header_value) = HeaderValue::try_from(CSP_POLICY.as_str()) {
|
|
res = res.header("Content-Security-Policy", header_value);
|
|
}
|
|
}
|
|
if mime.as_ref() == mime::APPLICATION_JAVASCRIPT
|
|
|| mime.as_ref() == mime::TEXT_JAVASCRIPT
|
|
|| path.ends_with(".wasm")
|
|
{
|
|
res = res.header(header::CACHE_CONTROL, "max-age=31536000");
|
|
} else if (mime.type_(), mime.subtype()) == (mime::TEXT, mime::CSS) {
|
|
res = res.header(header::CACHE_CONTROL, "max-age=31536000");
|
|
} else if (mime.type_()) == (mime::IMAGE) || (mime.type_()) == (mime::FONT) {
|
|
res = res.header(header::CACHE_CONTROL, "max-age=31536000");
|
|
} else {
|
|
res = res.header(header::CACHE_CONTROL, "no-cache, no-store, must-revalidate");
|
|
}
|
|
|
|
res.body(body).unwrap()
|
|
}
|
|
None if path.starts_with("_app/") => {
|
|
Response::builder().status(404).body(Body::empty()).unwrap()
|
|
}
|
|
None => serve_path(TWO_HUNDRED),
|
|
}
|
|
|
|
#[cfg(not(feature = "static_frontend"))]
|
|
{
|
|
Response::builder().status(404).body(Body::empty()).unwrap()
|
|
}
|
|
}
|