mirror of
https://github.com/dani-garcia/vaultwarden
synced 2024-12-14 09:33:44 +01:00
Merge pull request #2624 from BlackDex/fix-2623-csp-icon-redirect
Fix issue with CSP and icon redirects
This commit is contained in:
commit
f1a67663d1
3 changed files with 47 additions and 32 deletions
|
@ -30,10 +30,7 @@ use crate::{
|
|||
pub fn routes() -> Vec<Route> {
|
||||
match CONFIG.icon_service().as_str() {
|
||||
"internal" => routes![icon_internal],
|
||||
"bitwarden" => routes![icon_bitwarden],
|
||||
"duckduckgo" => routes![icon_duckduckgo],
|
||||
"google" => routes![icon_google],
|
||||
_ => routes![icon_custom],
|
||||
_ => routes![icon_external],
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,23 +97,8 @@ async fn icon_redirect(domain: &str, template: &str) -> Option<Redirect> {
|
|||
}
|
||||
|
||||
#[get("/<domain>/icon.png")]
|
||||
async fn icon_custom(domain: String) -> Option<Redirect> {
|
||||
icon_redirect(&domain, &CONFIG.icon_service()).await
|
||||
}
|
||||
|
||||
#[get("/<domain>/icon.png")]
|
||||
async fn icon_bitwarden(domain: String) -> Option<Redirect> {
|
||||
icon_redirect(&domain, "https://icons.bitwarden.net/{}/icon.png").await
|
||||
}
|
||||
|
||||
#[get("/<domain>/icon.png")]
|
||||
async fn icon_duckduckgo(domain: String) -> Option<Redirect> {
|
||||
icon_redirect(&domain, "https://icons.duckduckgo.com/ip3/{}.ico").await
|
||||
}
|
||||
|
||||
#[get("/<domain>/icon.png")]
|
||||
async fn icon_google(domain: String) -> Option<Redirect> {
|
||||
icon_redirect(&domain, "https://www.google.com/s2/favicons?domain={}&sz=32").await
|
||||
async fn icon_external(domain: String) -> Option<Redirect> {
|
||||
icon_redirect(&domain, &CONFIG._icon_service_url()).await
|
||||
}
|
||||
|
||||
#[get("/<domain>/icon.png")]
|
||||
|
|
|
@ -463,6 +463,10 @@ make_config! {
|
|||
/// service is set, an icon request to Vaultwarden will return an HTTP redirect to the
|
||||
/// corresponding icon at the external service.
|
||||
icon_service: String, false, def, "internal".to_string();
|
||||
/// Internal
|
||||
_icon_service_url: String, false, gen, |c| generate_icon_service_url(&c.icon_service);
|
||||
/// Internal
|
||||
_icon_service_csp: String, false, gen, |c| generate_icon_service_csp(&c.icon_service, &c._icon_service_url);
|
||||
/// Icon redirect code |> The HTTP status code to use for redirects to an external icon service.
|
||||
/// The supported codes are 301 (legacy permanent), 302 (legacy temporary), 307 (temporary), and 308 (permanent).
|
||||
/// Temporary redirects are useful while testing different icon services, but once a service
|
||||
|
@ -748,6 +752,34 @@ fn extract_url_path(url: &str) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
/// Generate the correct URL for the icon service.
|
||||
/// This will be used within icons.rs to call the external icon service.
|
||||
fn generate_icon_service_url(icon_service: &str) -> String {
|
||||
match icon_service {
|
||||
"internal" => "".to_string(),
|
||||
"bitwarden" => "https://icons.bitwarden.net/{}/icon.png".to_string(),
|
||||
"duckduckgo" => "https://icons.duckduckgo.com/ip3/{}.ico".to_string(),
|
||||
"google" => "https://www.google.com/s2/favicons?domain={}&sz=32".to_string(),
|
||||
_ => icon_service.to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate the CSP string needed to allow redirected icon fetching
|
||||
fn generate_icon_service_csp(icon_service: &str, icon_service_url: &str) -> String {
|
||||
// We split on the first '{', since that is the variable delimiter for an icon service URL.
|
||||
// Everything up until the first '{' should be fixed and can be used as an CSP string.
|
||||
let csp_string = match icon_service_url.split_once('{') {
|
||||
Some((c, _)) => c.to_string(),
|
||||
None => "".to_string(),
|
||||
};
|
||||
|
||||
// Because Google does a second redirect to there gstatic.com domain, we need to add an extra csp string.
|
||||
match icon_service {
|
||||
"google" => csp_string + " https://*.gstatic.com/favicon",
|
||||
_ => csp_string,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert the old SMTP_SSL and SMTP_EXPLICIT_TLS options
|
||||
fn smtp_convert_deprecated_ssl_options(smtp_ssl: Option<bool>, smtp_explicit_tls: Option<bool>) -> String {
|
||||
if smtp_explicit_tls.is_some() || smtp_ssl.is_some() {
|
||||
|
|
23
src/util.rs
23
src/util.rs
|
@ -38,18 +38,18 @@ impl Fairing for AppHeaders {
|
|||
|
||||
let req_uri_path = req.uri().path();
|
||||
|
||||
// Check if we are requesting an admin page, if so, allow unsafe-inline for scripts.
|
||||
// TODO: In the future maybe we need to see if we can generate a sha256 hash or have no scripts inline at all.
|
||||
let admin_path = format!("{}/admin", CONFIG.domain_path());
|
||||
let mut script_src = "";
|
||||
if req_uri_path.starts_with(admin_path.as_str()) {
|
||||
script_src = " 'unsafe-inline'";
|
||||
}
|
||||
|
||||
// Do not send the Content-Security-Policy (CSP) Header and X-Frame-Options for the *-connector.html files.
|
||||
// This can cause issues when some MFA requests needs to open a popup or page within the clients like WebAuthn, or Duo.
|
||||
// This is the same behaviour as upstream Bitwarden.
|
||||
if !req_uri_path.ends_with("connector.html") {
|
||||
// Check if we are requesting an admin page, if so, allow unsafe-inline for scripts.
|
||||
// TODO: In the future maybe we need to see if we can generate a sha256 hash or have no scripts inline at all.
|
||||
let admin_path = format!("{}/admin", CONFIG.domain_path());
|
||||
let mut script_src = "";
|
||||
if req_uri_path.starts_with(admin_path.as_str()) {
|
||||
script_src = " 'unsafe-inline'";
|
||||
}
|
||||
|
||||
// # Frame Ancestors:
|
||||
// Chrome Web Store: https://chrome.google.com/webstore/detail/bitwarden-free-password-m/nngceckbapebfimnlniiiahkandclblb
|
||||
// Edge Add-ons: https://microsoftedge.microsoft.com/addons/detail/bitwarden-free-password/jbkfoedolllekgbhcbcoahefnbanhhlh?hl=en-US
|
||||
|
@ -65,13 +65,14 @@ impl Fairing for AppHeaders {
|
|||
"default-src 'self'; \
|
||||
script-src 'self'{script_src}; \
|
||||
style-src 'self' 'unsafe-inline'; \
|
||||
img-src 'self' data: https://haveibeenpwned.com/ https://www.gravatar.com; \
|
||||
img-src 'self' data: https://haveibeenpwned.com/ https://www.gravatar.com {icon_service_csp}; \
|
||||
child-src 'self' https://*.duosecurity.com https://*.duofederal.com; \
|
||||
frame-src 'self' https://*.duosecurity.com https://*.duofederal.com; \
|
||||
connect-src 'self' https://api.pwnedpasswords.com/range/ https://2fa.directory/api/ https://app.simplelogin.io/api/ https://app.anonaddy.com/api/ https://relay.firefox.com/api/; \
|
||||
object-src 'self' blob:; \
|
||||
frame-ancestors 'self' chrome-extension://nngceckbapebfimnlniiiahkandclblb chrome-extension://jbkfoedolllekgbhcbcoahefnbanhhlh moz-extension://* {};",
|
||||
CONFIG.allowed_iframe_ancestors()
|
||||
frame-ancestors 'self' chrome-extension://nngceckbapebfimnlniiiahkandclblb chrome-extension://jbkfoedolllekgbhcbcoahefnbanhhlh moz-extension://* {allowed_iframe_ancestors};",
|
||||
icon_service_csp=CONFIG._icon_service_csp(),
|
||||
allowed_iframe_ancestors=CONFIG.allowed_iframe_ancestors()
|
||||
);
|
||||
res.set_raw_header("Content-Security-Policy", csp);
|
||||
res.set_raw_header("X-Frame-Options", "SAMEORIGIN");
|
||||
|
|
Loading…
Reference in a new issue