Skip to content

Commit

Permalink
add a server flag for csp origin
Browse files Browse the repository at this point in the history
  • Loading branch information
rot13maxi committed Nov 21, 2023
1 parent dd56f2c commit cef30c6
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 12 deletions.
12 changes: 12 additions & 0 deletions src/page_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,16 @@ pub(crate) struct PageConfig {
pub(crate) chain: Chain,
pub(crate) domain: Option<String>,
pub(crate) index_sats: bool,
pub(crate) content_security_policy_origin: Option<String>,
}

impl Default for PageConfig {
fn default() -> Self {
Self {
chain: Chain::Mainnet,
domain: None,
index_sats: false,
content_security_policy_origin: None,
}
}
}
84 changes: 72 additions & 12 deletions src/subcommand/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,11 @@ pub(crate) struct Server {
help = "Request ACME TLS certificate for <ACME_DOMAIN>. This ord instance must be reachable at <ACME_DOMAIN>:443 to respond to Let's Encrypt ACME challenges."
)]
acme_domain: Vec<String>,
#[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: <ACME_DOMAIN>]"
)]
content_security_policy_origin: Option<String>,
#[arg(
long,
help = "Listen on <HTTP_PORT> for incoming HTTP requests. [default: 80]."
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -986,6 +997,7 @@ impl Server {
async fn content(
Extension(index): Extension<Arc<Index>>,
Extension(config): Extension<Arc<Config>>,
Extension(page_config): Extension<Arc<PageConfig>>,
Path(inscription_id): Path<InscriptionId>,
accept_encoding: AcceptEncoding,
) -> ServerResult<Response> {
Expand All @@ -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(),
)
Expand All @@ -1007,6 +1019,7 @@ impl Server {
fn content_response(
inscription: Inscription,
accept_encoding: AcceptEncoding,
page_config: &PageConfig,
) -> ServerResult<Option<(HeaderMap, Vec<u8>)>> {
let mut headers = HeaderMap::new();

Expand All @@ -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,
Expand All @@ -1053,6 +1076,7 @@ impl Server {
async fn preview(
Extension(index): Extension<Arc<Index>>,
Extension(config): Extension<Arc<Config>>,
Extension(page_config): Extension<Arc<PageConfig>>,
Path(inscription_id): Path<InscriptionId>,
accept_encoding: AcceptEncoding,
) -> ServerResult<Response> {
Expand Down Expand Up @@ -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(),
),
Expand Down Expand Up @@ -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
Expand All @@ -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();
Expand All @@ -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();
Expand Down Expand Up @@ -3169,6 +3227,7 @@ mod tests {
let (headers, body) = Server::content_response(
Inscription::new(None, Some(Vec::new())),
AcceptEncoding::default(),
&PageConfig::default(),
)
.unwrap()
.unwrap();
Expand All @@ -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();
Expand Down
4 changes: 4 additions & 0 deletions src/templates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"<!doctype html>
<html lang=en>
Expand Down Expand Up @@ -164,6 +165,7 @@ mod tests {
chain: Chain::Mainnet,
domain: None,
index_sats: true,
content_security_policy_origin: None,
}),),
r".*<nav class=links>\s*<a href=/>Ordinals<sup>alpha</sup></a>.*"
);
Expand All @@ -176,6 +178,7 @@ mod tests {
chain: Chain::Mainnet,
domain: None,
index_sats: false,
content_security_policy_origin: None
}),),
r".*<nav class=links>\s*<a href=/>Ordinals<sup>alpha</sup></a>.*<a href=/clock>Clock</a>\s*<form action=/search.*",
);
Expand All @@ -188,6 +191,7 @@ mod tests {
chain: Chain::Signet,
domain: None,
index_sats: true,
content_security_policy_origin: None,
}),),
r".*<nav class=links>\s*<a href=/>Ordinals<sup>signet</sup></a>.*"
);
Expand Down

0 comments on commit cef30c6

Please sign in to comment.