Skip to content

Commit

Permalink
Prevent passwords from being logged
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
feikesteenbergen committed Oct 28, 2023
1 parent 253d8c9 commit c7fd23e
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 3 deletions.
38 changes: 38 additions & 0 deletions sqlx-core/src/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> 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<Secret> 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<Err = Error> + Debug + Clone {
type Connection: Connection + ?Sized;

Expand Down
5 changes: 3 additions & 2 deletions sqlx-mysql/src/options/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -57,7 +58,7 @@ pub struct MySqlConnectOptions {
pub(crate) port: u16,
pub(crate) socket: Option<PathBuf>,
pub(crate) username: String,
pub(crate) password: Option<String>,
pub(crate) password: Option<Secret>,
pub(crate) database: Option<String>,
pub(crate) ssl_mode: MySqlSslMode,
pub(crate) ssl_ca: Option<CertificateInput>,
Expand Down Expand Up @@ -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
}

Expand Down
3 changes: 2 additions & 1 deletion sqlx-postgres/src/options/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -83,7 +84,7 @@ pub struct PgConnectOptions {
pub(crate) port: u16,
pub(crate) socket: Option<PathBuf>,
pub(crate) username: String,
pub(crate) password: Option<String>,
pub(crate) password: Option<Secret>,
pub(crate) database: Option<String>,
pub(crate) ssl_mode: PgSslMode,
pub(crate) ssl_root_cert: Option<CertificateInput>,
Expand Down
2 changes: 2 additions & 0 deletions sqlx-postgres/src/options/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down

0 comments on commit c7fd23e

Please sign in to comment.