From c7fd23ede49b0fc52c010cd6efc615387d07196b Mon Sep 17 00:00:00 2001 From: Feike Steenbergen Date: Sat, 28 Oct 2023 09:47:34 +0200 Subject: [PATCH] Prevent passwords from being logged As passwords were treated as Strings, and Debug was derived, any debug logging would print the full password in the diagnostic logs of the applicaiton running this library. By wrapping the password in Secret and implementing most traits, we can (almost) seamlessly prevent passwords from being displayed in those contexts. --- sqlx-core/src/connection.rs | 38 ++++++++++++++++++++++++++++++ sqlx-mysql/src/options/mod.rs | 5 ++-- sqlx-postgres/src/options/mod.rs | 3 ++- sqlx-postgres/src/options/parse.rs | 2 ++ 4 files changed, 45 insertions(+), 3 deletions(-) diff --git a/sqlx-core/src/connection.rs b/sqlx-core/src/connection.rs index f254344a27..6e24e4b8bc 100644 --- a/sqlx-core/src/connection.rs +++ b/sqlx-core/src/connection.rs @@ -183,6 +183,44 @@ impl LogSettings { } } +/// `Secret` is a wrapper around a String which ensures it is not going +/// to be masked when printed in a regular fashion +#[derive(Clone,PartialEq,Eq)] +pub struct Secret(String); + +impl std::fmt::Debug for Secret { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("********") + } +} +impl std::fmt::Display for Secret { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(self, f) + } +} +impl From for Secret { + fn from(value: String) -> Self { + Self(value) + } +} +impl From<&str> for Secret { + fn from(value: &str) -> Self { + Self(value.to_owned()) + } +} +impl From for String { + fn from(value: Secret) -> Self { + value.0 + } +} +impl std::ops::Deref for Secret { + type Target = str; + + fn deref(&self) -> &Self::Target { + self.0.deref() + } +} + pub trait ConnectOptions: 'static + Send + Sync + FromStr + Debug + Clone { type Connection: Connection + ?Sized; diff --git a/sqlx-mysql/src/options/mod.rs b/sqlx-mysql/src/options/mod.rs index dc06380b07..5aee233c08 100644 --- a/sqlx-mysql/src/options/mod.rs +++ b/sqlx-mysql/src/options/mod.rs @@ -5,6 +5,7 @@ mod parse; mod ssl_mode; use crate::{connection::LogSettings, net::tls::CertificateInput}; +use sqlx_core::connection::Secret; pub use ssl_mode::MySqlSslMode; /// Options and flags which can be used to configure a MySQL connection. @@ -57,7 +58,7 @@ pub struct MySqlConnectOptions { pub(crate) port: u16, pub(crate) socket: Option, pub(crate) username: String, - pub(crate) password: Option, + pub(crate) password: Option, pub(crate) database: Option, pub(crate) ssl_mode: MySqlSslMode, pub(crate) ssl_ca: Option, @@ -132,7 +133,7 @@ impl MySqlConnectOptions { /// Sets the password to connect with. pub fn password(mut self, password: &str) -> Self { - self.password = Some(password.to_owned()); + self.password = Some(password.into()); self } diff --git a/sqlx-postgres/src/options/mod.rs b/sqlx-postgres/src/options/mod.rs index 0f1972f92d..0b9f027347 100644 --- a/sqlx-postgres/src/options/mod.rs +++ b/sqlx-postgres/src/options/mod.rs @@ -3,6 +3,7 @@ use std::env::var; use std::fmt::{Display, Write}; use std::path::{Path, PathBuf}; +use sqlx_core::connection::Secret; pub use ssl_mode::PgSslMode; use crate::{connection::LogSettings, net::tls::CertificateInput}; @@ -83,7 +84,7 @@ pub struct PgConnectOptions { pub(crate) port: u16, pub(crate) socket: Option, pub(crate) username: String, - pub(crate) password: Option, + pub(crate) password: Option, pub(crate) database: Option, pub(crate) ssl_mode: PgSslMode, pub(crate) ssl_root_cert: Option, diff --git a/sqlx-postgres/src/options/parse.rs b/sqlx-postgres/src/options/parse.rs index 4c5cf41c3e..0a78e06802 100644 --- a/sqlx-postgres/src/options/parse.rs +++ b/sqlx-postgres/src/options/parse.rs @@ -204,6 +204,8 @@ fn it_parses_password_with_non_ascii_chars_correctly() { let opts = PgConnectOptions::from_str(url).unwrap(); assert_eq!(Some("p@ssw0rd".into()), opts.password); + assert_eq!("********", format!("{0}", opts.password.unwrap())); + assert_eq!("********", format!("{0:?}", opts.password.unwrap())); } #[test]