From 7040bbe4c92c522d0815bc93c36604accd321bd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ko=C5=82odziejczak?= <31549762+mrl5@users.noreply.github.com> Date: Sun, 6 Nov 2022 17:07:25 +0100 Subject: [PATCH] fix(backend): tighten http security headers (#860) * fix(backend): set http security headers (vol.1) * (vol.2) minimal *working* content security policy * (vol.3) set csp only if https and if hosted on cloud * improve generics * get CLOUD_HOSTED from extension * remove X-XSS-Protection rationale as per: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection * conditionally set all security related http headers --- backend/windmill-api/src/lib.rs | 4 +-- backend/windmill-api/src/static_assets.rs | 35 ++++++++++++++++++----- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/backend/windmill-api/src/lib.rs b/backend/windmill-api/src/lib.rs index b7db777ab4..3118fc1201 100644 --- a/backend/windmill-api/src/lib.rs +++ b/backend/windmill-api/src/lib.rs @@ -45,8 +45,8 @@ mod workspaces; const GIT_VERSION: &str = git_version!(args = ["--tag", "--always"], fallback = "unknown-version"); struct BaseUrl(String); -struct IsSecure(bool); -struct CloudHosted(bool); +pub struct IsSecure(bool); +pub struct CloudHosted(bool); pub use users::delete_expired_items_perdiodically; diff --git a/backend/windmill-api/src/static_assets.rs b/backend/windmill-api/src/static_assets.rs index b2dbd33dbd..bcc0467b8e 100644 --- a/backend/windmill-api/src/static_assets.rs +++ b/backend/windmill-api/src/static_assets.rs @@ -8,23 +8,30 @@ use axum::{ body::{self, BoxBody}, - http::{header, Response, Uri}, + http::{header, response::Builder, Response, Uri}, response::IntoResponse, + Extension, }; +use crate::{CloudHosted, IsSecure}; use mime_guess::mime; use rust_embed::RustEmbed; +use std::sync::Arc; // static_handler is a handler that serves static files from the -pub async fn static_handler(uri: Uri) -> impl IntoResponse { +pub async fn static_handler( + uri: Uri, + Extension(is_secure): Extension>, + Extension(is_cloud_hosted): Extension>, +) -> impl IntoResponse { let path = uri.path().trim_start_matches('/').to_string(); - StaticFile(path) + StaticFile(path, is_secure.0, is_cloud_hosted.0) } #[derive(RustEmbed)] #[folder = "../../frontend/build/"] struct Asset; -pub struct StaticFile(pub T); +pub struct StaticFile(pub T, pub bool, pub bool); impl IntoResponse for StaticFile where @@ -32,11 +39,12 @@ where { fn into_response(self) -> Response { let path = self.0.into(); - serve_path(path) + let can_set_security_headers = self.1 && self.2; + serve_path(path, can_set_security_headers) } } -fn serve_path(path: String) -> Response { +fn serve_path(path: String, can_set_security_headers: bool) -> Response { if path.starts_with("api/") { return Response::builder() .status(404) @@ -57,12 +65,25 @@ fn serve_path(path: String) -> Response { } else { res = res.header(header::CACHE_CONTROL, "no-cache, no-store, must-revalidate"); } + + if can_set_security_headers { + res = set_security_headers(res); + } res.body(body).unwrap() } None if path.as_str().starts_with("_app/") => Response::builder() .status(404) .body(body::boxed(body::Empty::new())) .unwrap(), - None => serve_path("200.html".to_owned()), + None => serve_path("200.html".to_owned(), can_set_security_headers), } } + +fn set_security_headers(mut res: Builder) -> Builder { + let csp = "frame-ancestors 'none'; frame-src 'none'; worker-src 'self'; child-src 'none'; object-src 'none'"; + res = res.header("Content-Security-Policy", csp); + res = res.header("X-Frame-Options", "DENY"); + res = res.header("X-Content-Type-Options", "nosniff"); + + res +}