From cef30c6c67ead285ebec1df4864192bea277d9e1 Mon Sep 17 00:00:00 2001 From: rot13maxi Date: Tue, 21 Nov 2023 13:06:47 -0500 Subject: [PATCH] add a server flag for csp origin --- src/page_config.rs | 12 ++++++ src/subcommand/server.rs | 84 ++++++++++++++++++++++++++++++++++------ src/templates.rs | 4 ++ 3 files changed, 88 insertions(+), 12 deletions(-) diff --git a/src/page_config.rs b/src/page_config.rs index 0bc36cb5cc..f3939b11d3 100644 --- a/src/page_config.rs +++ b/src/page_config.rs @@ -5,4 +5,16 @@ pub(crate) struct PageConfig { pub(crate) chain: Chain, pub(crate) domain: Option, pub(crate) index_sats: bool, + pub(crate) content_security_policy_origin: Option, +} + +impl Default for PageConfig { + fn default() -> Self { + Self { + chain: Chain::Mainnet, + domain: None, + index_sats: false, + content_security_policy_origin: None, + } + } } diff --git a/src/subcommand/server.rs b/src/subcommand/server.rs index 29d210712d..2a46f3ec65 100644 --- a/src/subcommand/server.rs +++ b/src/subcommand/server.rs @@ -132,6 +132,11 @@ pub(crate) struct Server { help = "Request ACME TLS certificate for . This ord instance must be reachable at :443 to respond to Let's Encrypt ACME challenges." )] acme_domain: Vec, + #[arg( + long, + help = "Origin to use for the content-security-policy header. Set this to the fully-qualified domain name of your ord instance. [default: ]" + )] + content_security_policy_origin: Option, #[arg( long, help = "Listen on for incoming HTTP requests. [default: 80]." @@ -179,11 +184,17 @@ impl Server { let config = options.load_config()?; let acme_domains = self.acme_domains()?; + let content_security_policy_origin = self + .content_security_policy_origin + .clone() + .or_else(|| acme_domains.first().cloned()) + .or(None); let page_config = Arc::new(PageConfig { chain: options.chain(), domain: acme_domains.first().cloned(), index_sats: index.has_sat_index(), + content_security_policy_origin, }); let router = Router::new() @@ -986,6 +997,7 @@ impl Server { async fn content( Extension(index): Extension>, Extension(config): Extension>, + Extension(page_config): Extension>, Path(inscription_id): Path, accept_encoding: AcceptEncoding, ) -> ServerResult { @@ -998,7 +1010,7 @@ impl Server { .ok_or_not_found(|| format!("inscription {inscription_id}"))?; Ok( - Self::content_response(inscription, accept_encoding)? + Self::content_response(inscription, accept_encoding, &page_config)? .ok_or_not_found(|| format!("inscription {inscription_id} content"))? .into_response(), ) @@ -1007,6 +1019,7 @@ impl Server { fn content_response( inscription: Inscription, accept_encoding: AcceptEncoding, + page_config: &PageConfig, ) -> ServerResult)>> { let mut headers = HeaderMap::new(); @@ -1028,15 +1041,25 @@ impl Server { } } - headers.insert( - header::CONTENT_SECURITY_POLICY, - HeaderValue::from_static("default-src 'self' 'unsafe-eval' 'unsafe-inline' data: blob:"), - ); - - headers.append( - header::CONTENT_SECURITY_POLICY, - HeaderValue::from_static("default-src *:*/content/ *:*/blockheight *:*/blockhash *:*/blockhash/ *:*/blocktime *:*/r/ 'unsafe-eval' 'unsafe-inline' data: blob:"), - ); + match &page_config.content_security_policy_origin { + None => { + headers.insert( + header::CONTENT_SECURITY_POLICY, + HeaderValue::from_static("default-src 'self' 'unsafe-eval' 'unsafe-inline' data: blob:"), + ); + headers.append( + header::CONTENT_SECURITY_POLICY, + HeaderValue::from_static("default-src *:*/content/ *:*/blockheight *:*/blockhash *:*/blockhash/ *:*/blocktime *:*/r/ 'unsafe-eval' 'unsafe-inline' data: blob:"), + ); + } + Some(origin) => { + let csp = format!("default-src {origin}/content/ {origin}/blockheight {origin}/blockhash {origin}/blockhash/ {origin}/blocktime 'unsafe-eval' 'unsafe-inline' data: blob:"); + headers.insert( + header::CONTENT_SECURITY_POLICY, + HeaderValue::from_str(&csp).map_err(|err| ServerError::Internal(Error::from(err)))?, + ); + } + } headers.insert( header::CACHE_CONTROL, @@ -1053,6 +1076,7 @@ impl Server { async fn preview( Extension(index): Extension>, Extension(config): Extension>, + Extension(page_config): Extension>, Path(inscription_id): Path, accept_encoding: AcceptEncoding, ) -> ServerResult { @@ -1090,7 +1114,7 @@ impl Server { .into_response(), ), Media::Iframe => Ok( - Self::content_response(inscription, accept_encoding)? + Self::content_response(inscription, accept_encoding, &page_config)? .ok_or_not_found(|| format!("inscription {inscription_id} content"))? .into_response(), ), @@ -3119,7 +3143,8 @@ mod tests { assert_eq!( Server::content_response( Inscription::new(Some("text/plain".as_bytes().to_vec()), None), - AcceptEncoding::default() + AcceptEncoding::default(), + &PageConfig::default(), ) .unwrap(), None @@ -3131,6 +3156,7 @@ mod tests { let (headers, body) = Server::content_response( Inscription::new(Some("text/plain".as_bytes().to_vec()), Some(vec![1, 2, 3])), AcceptEncoding::default(), + &PageConfig::default(), ) .unwrap() .unwrap(); @@ -3139,6 +3165,38 @@ mod tests { assert_eq!(body, vec![1, 2, 3]); } + #[test] + fn content_security_policy_no_origin() { + let (headers, _) = Server::content_response( + Inscription::new(Some("text/plain".as_bytes().to_vec()), Some(vec![1, 2, 3])), + AcceptEncoding::default(), + &PageConfig::default(), + ) + .unwrap() + .unwrap(); + + assert_eq!( + headers["content-security-policy"], + HeaderValue::from_static("default-src 'self' 'unsafe-eval' 'unsafe-inline' data: blob:") + ); + } + + #[test] + fn content_security_policy_with_origin() { + let (headers, _) = Server::content_response( + Inscription::new(Some("text/plain".as_bytes().to_vec()), Some(vec![1, 2, 3])), + AcceptEncoding::default(), + &PageConfig { + content_security_policy_origin: Some("https://ordinals.com".into()), + ..Default::default() + }, + ) + .unwrap() + .unwrap(); + + assert_eq!(headers["content-security-policy"], HeaderValue::from_static("default-src https://ordinals.com/content/ https://ordinals.com/blockheight https://ordinals.com/blockhash https://ordinals.com/blockhash/ https://ordinals.com/blocktime 'unsafe-eval' 'unsafe-inline' data: blob:")); + } + #[test] fn code_preview() { let server = TestServer::new_with_regtest(); @@ -3169,6 +3227,7 @@ mod tests { let (headers, body) = Server::content_response( Inscription::new(None, Some(Vec::new())), AcceptEncoding::default(), + &PageConfig::default(), ) .unwrap() .unwrap(); @@ -3182,6 +3241,7 @@ mod tests { let (headers, body) = Server::content_response( Inscription::new(Some("\n".as_bytes().to_vec()), Some(Vec::new())), AcceptEncoding::default(), + &PageConfig::default(), ) .unwrap() .unwrap(); diff --git a/src/templates.rs b/src/templates.rs index 32f6653776..46c9b526ea 100644 --- a/src/templates.rs +++ b/src/templates.rs @@ -119,6 +119,7 @@ mod tests { chain: Chain::Mainnet, domain: Some("signet.ordinals.com".into()), index_sats: true, + content_security_policy_origin: Some("https://signet.ordinals.com".into()), }),), r" @@ -164,6 +165,7 @@ mod tests { chain: Chain::Mainnet, domain: None, index_sats: true, + content_security_policy_origin: None, }),), r".*