From 2c52d1a894dd168ae9c1ba550b47bd08e7320d57 Mon Sep 17 00:00:00 2001 From: Piotr Heilman <1212808+piohei@users.noreply.github.com> Date: Fri, 27 Sep 2024 08:16:38 +0200 Subject: [PATCH] Hide secrets from config in logs. (#56) --- src/config.rs | 23 ++++++------ src/types.rs | 2 ++ src/types/secret_string.rs | 72 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 11 deletions(-) create mode 100644 src/types/secret_string.rs diff --git a/src/config.rs b/src/config.rs index 35dcf90..1978ace 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,6 +6,7 @@ use config::FileFormat; use serde::{Deserialize, Serialize}; use crate::api_key::ApiKey; +use crate::types::secret_string::SecretString; pub fn load_config<'a>( config_files: impl Iterator, @@ -100,8 +101,8 @@ pub struct PredefinedRelayer { pub struct ServerConfig { pub host: SocketAddr, - pub username: Option, - pub password: Option, + pub username: Option, + pub password: Option, // Optional address to show in API explorer pub server_address: Option, @@ -126,18 +127,18 @@ pub enum DatabaseConfig { impl DatabaseConfig { pub fn connection_string(s: impl ToString) -> Self { Self::ConnectionString(DbConnectionString { - connection_string: s.to_string(), + connection_string: SecretString::new(s.to_string()), }) } pub fn to_connection_string(&self) -> String { match self { - Self::ConnectionString(s) => s.connection_string.clone(), + Self::ConnectionString(s) => s.connection_string.clone().into(), Self::Parts(parts) => { format!( "postgres://{}:{}@{}:{}/{}", - parts.username, - parts.password, + parts.username.expose(), + parts.password.expose(), parts.host, parts.port, parts.database @@ -150,7 +151,7 @@ impl DatabaseConfig { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub struct DbConnectionString { - pub connection_string: String, + pub connection_string: SecretString, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -158,8 +159,8 @@ pub struct DbConnectionString { pub struct DbParts { pub host: String, pub port: String, - pub username: String, - pub password: String, + pub username: SecretString, + pub password: SecretString, pub database: String, } @@ -328,8 +329,8 @@ mod tests { database: DatabaseConfig::Parts(DbParts { host: "host".to_string(), port: "5432".to_string(), - username: "user".to_string(), - password: "pass".to_string(), + username: SecretString::new("user".to_string()), + password: SecretString::new("pass".to_string()), database: "db".to_string(), }), keys: KeysConfig::Local(LocalKeysConfig::default()), diff --git a/src/types.rs b/src/types.rs index 9e15588..635f27b 100644 --- a/src/types.rs +++ b/src/types.rs @@ -6,6 +6,8 @@ use serde_json::Value; use crate::api_key::ApiKey; use crate::db::data::{NetworkInfo, RelayerGasPriceLimit, RelayerInfo}; +pub mod secret_string; + #[derive( Deserialize, Serialize, Debug, Clone, Copy, Default, sqlx::Type, Enum, )] diff --git a/src/types/secret_string.rs b/src/types/secret_string.rs new file mode 100644 index 0000000..78004f8 --- /dev/null +++ b/src/types/secret_string.rs @@ -0,0 +1,72 @@ +use std::fmt; +use std::ops::Deref; + +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Eq, PartialEq, Serialize, Deserialize)] +#[serde(transparent)] +pub struct SecretString(String); + +impl SecretString { + #[must_use] + pub fn new(str: String) -> Self { + Self(str) + } + + #[must_use] + pub fn expose(&self) -> &str { + self.0.as_str() + } + + fn format(&self) -> String { + "********".to_owned() + } +} + +impl fmt::Display for SecretString { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.format().fmt(f) + } +} + +impl fmt::Debug for SecretString { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.format().fmt(f) + } +} + +impl From for SecretString { + fn from(str: String) -> Self { + Self::new(str) + } +} + +impl From for String { + fn from(secret_string: SecretString) -> Self { + secret_string.0 + } +} + +impl Deref for SecretString { + type Target = str; + + fn deref(&self) -> &Self::Target { + self.0.deref() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_url_expose() { + let secret = SecretString::from( + "postgres://user:password@localhost:5432/database".to_string(), + ); + assert_eq!( + secret.expose(), + "postgres://user:password@localhost:5432/database" + ); + } +}