mirror of
				https://github.com/dani-garcia/vaultwarden.git
				synced 2025-10-31 13:51:14 +00:00 
			
		
		
		
	Fix issue with CSP and icon redirects
When using anything else but the `internal` icon service it would trigger an CSP block because the redirects were not allowed. This PR fixes #2623 by dynamically adding the needed CSP strings. This should also work with custom services. For Google i needed to add an extra check because that does a redirect it self to there gstatic.com domain.
This commit is contained in:
		
					parent
					
						
							
								a0eab35768
							
						
					
				
			
			
				commit
				
					
						0f95bdc9bb
					
				
			
		
					 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…
	
	Add table
		Add a link
		
	
		Reference in a new issue