diff --git a/CHANGELOG.md b/CHANGELOG.md index d1b59346..fceffa6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ **Features**: - Add support for Profiling feature. ([#479](https://github.com/getsentry/sentry-rust/pull/479)) +- Add `SSL_VERIFY` option to control certificate verification. ([#508](https://github.com/getsentry/sentry-rust/pull/508)) **Internal**: diff --git a/sentry-core/src/clientoptions.rs b/sentry-core/src/clientoptions.rs index 5077f076..1a62635e 100644 --- a/sentry-core/src/clientoptions.rs +++ b/sentry-core/src/clientoptions.rs @@ -128,6 +128,12 @@ pub struct ClientOptions { /// The timeout on client drop for draining events on shutdown. pub shutdown_timeout: Duration, // Other options not documented in Unified API + /// Disable SSL verification. + /// + /// # Warning + /// + /// This introduces significant vulnerabilities, and should only be used as a last resort. + pub accept_invalid_certs: bool, /// Enable Release Health Session tracking. /// /// When automatic session tracking is enabled, a new "user-mode" session @@ -206,6 +212,7 @@ impl fmt::Debug for ClientOptions { .field("http_proxy", &self.http_proxy) .field("https_proxy", &self.https_proxy) .field("shutdown_timeout", &self.shutdown_timeout) + .field("accept_invalid_certs", &self.accept_invalid_certs) .field("auto_session_tracking", &self.auto_session_tracking) .field("session_mode", &self.session_mode) .field("extra_border_frames", &self.extra_border_frames) @@ -240,6 +247,7 @@ impl Default for ClientOptions { http_proxy: None, https_proxy: None, shutdown_timeout: Duration::from_secs(2), + accept_invalid_certs: false, auto_session_tracking: false, session_mode: SessionMode::Application, extra_border_frames: vec![], diff --git a/sentry/Cargo.toml b/sentry/Cargo.toml index a25cb8db..32bbde68 100644 --- a/sentry/Cargo.toml +++ b/sentry/Cargo.toml @@ -45,11 +45,11 @@ transport = ["reqwest", "native-tls"] reqwest = ["reqwest_", "httpdate", "tokio"] curl = ["curl_", "httpdate"] surf-h1 = ["surf_/h1-client", "httpdate"] -surf = ["surf_/curl-client", "httpdate", "tokio"] -native-tls = ["reqwest_/default-tls"] -rustls = ["reqwest_/rustls-tls"] -ureq = ["ureq_/tls", "httpdate"] -ureq-native-tls = ["ureq_/native-tls", "httpdate"] +surf = ["surf_/curl-client", "http-client", "httpdate", "isahc", "tokio"] +native-tls = ["reqwest_/default-tls", "native-tls_", "ureq-native-tls"] +rustls = ["reqwest_/rustls-tls", "rustls_", "webpki-roots"] +ureq = ["ureq_/tls", "httpdate", "rustls_", "webpki-roots"] +ureq-native-tls = ["ureq_/native-tls", "httpdate", "native-tls_"] [dependencies] sentry-core = { version = "0.27.0", path = "../sentry-core", features = ["client"] } @@ -67,9 +67,14 @@ reqwest_ = { package = "reqwest", version = "0.11", optional = true, features = curl_ = { package = "curl", version = "0.4.25", optional = true } httpdate = { version = "1.0.0", optional = true } surf_ = { package = "surf", version = "2.0.0", optional = true, default-features = false } +http-client = { version = "6.5.3", optional = true } +isahc = { version = "0.9.14", optional = true } serde_json = { version = "1.0.48", optional = true } tokio = { version = "1.0", features = ["rt"], optional = true } ureq_ = { package = "ureq", version = "2.3.0", optional = true, default-features = false } +native-tls_ = { package = "native-tls", version = "0.2.8", optional = true } +rustls_ = { package = "rustls", version = "0.20.6", optional = true, features = ["dangerous_configuration"] } +webpki-roots = { version = "0.22.5", optional = true } [dev-dependencies] sentry-anyhow = { path = "../sentry-anyhow" } diff --git a/sentry/src/defaults.rs b/sentry/src/defaults.rs index d2179f29..95d97478 100644 --- a/sentry/src/defaults.rs +++ b/sentry/src/defaults.rs @@ -113,6 +113,9 @@ pub fn apply_defaults(mut opts: ClientOptions) -> ClientOptions { .or_else(|| std::env::var("https_proxy").ok().map(Cow::Owned)) .or_else(|| opts.http_proxy.clone()); } + if let Ok(accept_invalid_certs) = std::env::var("SSL_VERIFY") { + opts.accept_invalid_certs = !accept_invalid_certs.parse().unwrap_or(true); + } opts } diff --git a/sentry/src/transports/curl.rs b/sentry/src/transports/curl.rs index e66576c1..12e2cd45 100644 --- a/sentry/src/transports/curl.rs +++ b/sentry/src/transports/curl.rs @@ -35,6 +35,7 @@ impl CurlHttpTransport { let auth = dsn.to_auth(Some(&user_agent)).to_string(); let url = dsn.envelope_api_url().to_string(); let scheme = dsn.scheme(); + let accept_invalid_certs = options.accept_invalid_certs; let mut handle = client; let thread = TransportThread::new(move |envelope, rl| { @@ -42,6 +43,11 @@ impl CurlHttpTransport { handle.url(&url).unwrap(); handle.custom_request("POST").unwrap(); + if accept_invalid_certs { + handle.ssl_verify_host(false).unwrap(); + handle.ssl_verify_peer(false).unwrap(); + } + match (scheme, &http_proxy, &https_proxy) { (Scheme::Https, _, &Some(ref proxy)) => { if let Err(err) = handle.proxy(proxy) { diff --git a/sentry/src/transports/reqwest.rs b/sentry/src/transports/reqwest.rs index ad99cd7a..98a15d5a 100644 --- a/sentry/src/transports/reqwest.rs +++ b/sentry/src/transports/reqwest.rs @@ -32,6 +32,9 @@ impl ReqwestHttpTransport { fn new_internal(options: &ClientOptions, client: Option) -> Self { let client = client.unwrap_or_else(|| { let mut builder = reqwest_::Client::builder(); + if options.accept_invalid_certs { + builder = builder.danger_accept_invalid_certs(true); + } if let Some(url) = options.http_proxy.as_ref() { match Proxy::http(url.as_ref()) { Ok(proxy) => { diff --git a/sentry/src/transports/surf.rs b/sentry/src/transports/surf.rs index d458a06c..a2466602 100644 --- a/sentry/src/transports/surf.rs +++ b/sentry/src/transports/surf.rs @@ -1,5 +1,9 @@ use std::time::Duration; +use isahc::{ + config::{Configurable, SslOption}, + HttpClient, +}; use surf_::{http::headers as SurfHeaders, Client as SurfClient, StatusCode}; use super::tokio_thread::TransportThread; @@ -28,7 +32,16 @@ impl SurfHttpTransport { } fn new_internal(options: &ClientOptions, client: Option) -> Self { - let client = client.unwrap_or_else(SurfClient::new); + let mut client = client.unwrap_or_else(SurfClient::new); + if options.accept_invalid_certs { + let hc = HttpClient::builder() + .ssl_options(SslOption::DANGER_ACCEPT_INVALID_CERTS) + .build() + .unwrap(); + let http_client = http_client::isahc::IsahcClient::from_client(hc); + client = SurfClient::with_http_client(http_client) + } + let dsn = options.dsn.as_ref().unwrap(); let user_agent = options.user_agent.to_owned(); let auth = dsn.to_auth(Some(&user_agent)).to_string(); diff --git a/sentry/src/transports/ureq.rs b/sentry/src/transports/ureq.rs index e05359ae..5f292651 100644 --- a/sentry/src/transports/ureq.rs +++ b/sentry/src/transports/ureq.rs @@ -1,8 +1,20 @@ +use std::sync::Arc; use std::time::Duration; +#[cfg(feature = "rustls")] +use std::time::SystemTime; +#[cfg(feature = "native-tls")] +use native_tls_::TlsConnector; +#[cfg(feature = "rustls")] +use rustls_::{ + client::{ServerCertVerified, ServerCertVerifier}, + Certificate, ClientConfig, Error, OwnedTrustAnchor, RootCertStore, ServerName, +}; #[cfg(doc)] use ureq_ as ureq; use ureq_::{Agent, AgentBuilder, Proxy}; +#[cfg(feature = "rustls")] +use webpki_roots::TLS_SERVER_ROOTS; use super::thread::TransportThread; @@ -33,6 +45,53 @@ impl UreqHttpTransport { let agent = agent.unwrap_or_else(|| { let mut builder = AgentBuilder::new(); + if options.accept_invalid_certs { + #[cfg(feature = "native-tls")] + { + let tls_connector = TlsConnector::builder() + .danger_accept_invalid_certs(true) + .build() + .unwrap(); + builder = builder.tls_connector(Arc::new(tls_connector)); + } + + #[cfg(feature = "rustls")] + { + struct NoVerifier; + + impl ServerCertVerifier for NoVerifier { + fn verify_server_cert( + &self, + _end_entity: &Certificate, + _intermediates: &[Certificate], + _server_name: &ServerName, + _scts: &mut dyn Iterator, + _ocsp_response: &[u8], + _now: SystemTime, + ) -> Result { + Ok(ServerCertVerified::assertion()) + } + } + + let mut root_store = RootCertStore::empty(); + root_store.add_server_trust_anchors(TLS_SERVER_ROOTS.0.iter().map(|ta| { + OwnedTrustAnchor::from_subject_spki_name_constraints( + ta.subject, + ta.spki, + ta.name_constraints, + ) + })); + let mut config = ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates(root_store) + .with_no_client_auth(); + config + .dangerous() + .set_certificate_verifier(Arc::new(NoVerifier)); + builder = builder.tls_config(Arc::new(config)); + } + } + match (scheme, &options.http_proxy, &options.https_proxy) { (Scheme::Https, _, &Some(ref proxy)) => match Proxy::new(proxy) { Ok(proxy) => {