diff --git a/src/config.rs b/src/config.rs index b0d98fb5..9c1c571c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -883,6 +883,7 @@ pub struct Plugins { pub intercept: Option, pub table_access: Option, pub query_logger: Option, + pub audit_logger: Option, pub prewarmer: Option, } @@ -901,10 +902,11 @@ impl std::fmt::Display for Plugins { } write!( f, - "interceptor: {}, table_access: {}, query_logger: {}, prewarmer: {}", + "interceptor: {}, table_access: {}, query_logger: {}, audit_logger: {}, prewarmer: {}", is_enabled(self.intercept.as_ref()), is_enabled(self.table_access.as_ref()), is_enabled(self.query_logger.as_ref()), + is_enabled(self.audit_logger.as_ref()), is_enabled(self.prewarmer.as_ref()), ) } @@ -939,12 +941,24 @@ pub struct QueryLogger { pub enabled: bool, } +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, Hash, Eq)] +pub struct AuditLogger { + pub enabled: bool, + pub patterns: Vec, +} + impl Plugin for QueryLogger { fn is_enabled(&self) -> bool { self.enabled } } +impl Plugin for AuditLogger { + fn is_enabled(&self) -> bool { + self.enabled + } +} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default, Hash, Eq)] pub struct Prewarmer { pub enabled: bool, diff --git a/src/plugins/audit_logger.rs b/src/plugins/audit_logger.rs new file mode 100644 index 00000000..4324faa8 --- /dev/null +++ b/src/plugins/audit_logger.rs @@ -0,0 +1,71 @@ +//! Plugin for sanitizing and logging queries and their results +//! Replaces sensitive data matching configured regex patterns with + +use async_trait::async_trait; +use log::info; +use regex::Regex; +use sqlparser::ast::Statement; + +use crate::{ + errors::Error, + plugins::{Plugin, PluginOutput}, + query_router::QueryRouter, +}; + +#[derive(Clone)] +pub struct AuditLogger<'a> { + pub enabled: bool, + pub patterns: &'a Vec, + compiled_patterns: Vec, +} + +impl<'a> AuditLogger<'a> { + pub fn new(enabled: bool, patterns: &'a Vec) -> Result { + let compiled_patterns = patterns + .iter() + .map(|p| Regex::new(p)) + .collect::, regex::Error>>() + .map_err(|_e| Error::BadConfig)?; + + Ok(AuditLogger { + enabled, + patterns, + compiled_patterns, + }) + } + + fn sanitize(&self, text: &str) -> String { + let mut sanitized = text.to_string(); + for pattern in &self.compiled_patterns { + sanitized = pattern.replace_all(&sanitized, "").to_string(); + } + sanitized + } +} + +#[async_trait] +impl<'a> Plugin for AuditLogger<'a> { + async fn run( + &mut self, + query_router: &QueryRouter, + ast: &Vec, + ) -> Result { + if !self.enabled { + return Ok(PluginOutput::Allow); + } + + // Log sanitized queries + for stmt in ast { + let query = stmt.to_string(); + let sanitized = self.sanitize(&query); + info!( + "[pool: {}][user: {}] Query: {}", + query_router.pool_settings().db, + query_router.pool_settings().user.username, + sanitized + ); + } + + Ok(PluginOutput::Allow) + } +} diff --git a/src/plugins/mod.rs b/src/plugins/mod.rs index f1076d06..43fff602 100644 --- a/src/plugins/mod.rs +++ b/src/plugins/mod.rs @@ -8,6 +8,7 @@ //! - etc //! +pub mod audit_logger; pub mod intercept; pub mod prewarmer; pub mod query_logger; @@ -18,6 +19,7 @@ use async_trait::async_trait; use bytes::BytesMut; use sqlparser::ast::Statement; +pub use audit_logger::AuditLogger; pub use intercept::Intercept; pub use query_logger::QueryLogger; pub use table_access::TableAccess; diff --git a/src/query_router.rs b/src/query_router.rs index 3e485a0d..9e627f7f 100644 --- a/src/query_router.rs +++ b/src/query_router.rs @@ -1916,6 +1916,7 @@ mod test { let plugins = Plugins { table_access: Some(table_access), intercept: None, + audit_logger: None, query_logger: None, prewarmer: None, };