dataprof_db/security/
connection_security.rs1use super::{
4 credentials::DatabaseCredentials, environment::load_ssl_config_from_environment,
5 ssl_config::SslConfig,
6};
7use crate::DataProfilerError;
8use std::collections::HashMap;
9
10pub fn validate_connection_security(
12 connection_string: &str,
13 ssl_config: &SslConfig,
14 database_type: &str,
15) -> Result<Vec<String>, DataProfilerError> {
16 let mut warnings = Vec::new();
17
18 if let Ok(conn_info) = crate::connection::ConnectionInfo::parse(connection_string) {
19 if conn_info.password.is_some() {
20 warnings.push(
21 "Password embedded in connection string. Consider using environment variables."
22 .to_string(),
23 );
24 }
25
26 if database_type != "sqlite" {
27 let has_ssl_params = conn_info.query_params.contains_key("sslmode")
28 || conn_info.query_params.contains_key("tls")
29 || conn_info.query_params.contains_key("ssl");
30
31 if !has_ssl_params && !ssl_config.require_ssl {
32 warnings.push(
33 "No SSL/TLS configuration detected. Database traffic may be unencrypted."
34 .to_string(),
35 );
36 }
37 }
38
39 if let Some(host) = &conn_info.host
40 && (host == "localhost" || host == "127.0.0.1" || host == "::1")
41 {
42 warnings.push(
43 "Connecting to localhost. Ensure this is intentional in production.".to_string(),
44 );
45 }
46
47 let default_ports = HashMap::from([("postgresql", 5432), ("mysql", 3306)]);
48
49 if let Some(default_port) = default_ports.get(database_type)
50 && conn_info.port == Some(*default_port)
51 {
52 warnings.push(format!(
53 "Using default port for {}. Consider using a non-standard port for security.",
54 database_type
55 ));
56 }
57 }
58
59 if let Err(e) = ssl_config.validate() {
60 warnings.push(format!("SSL configuration issue: {}", e));
61 }
62
63 Ok(warnings)
64}
65
66pub fn load_secure_database_config(
68 database_type: &str,
69) -> Result<(String, SslConfig), DataProfilerError> {
70 let credentials = DatabaseCredentials::from_environment(database_type);
71
72 let base_connection_string = match database_type {
73 "postgresql" => {
74 let host = credentials.host.as_deref().unwrap_or("localhost");
75 let port = credentials.port.unwrap_or(5432);
76 let database = credentials.database.as_deref().unwrap_or("postgres");
77 format!("postgresql://{}:{}/{}", host, port, database)
78 }
79 "mysql" => {
80 let host = credentials.host.as_deref().unwrap_or("localhost");
81 let port = credentials.port.unwrap_or(3306);
82 let database = credentials.database.as_deref().unwrap_or("mysql");
83 format!("mysql://{}:{}/{}", host, port, database)
84 }
85 "sqlite" => credentials
86 .database
87 .clone()
88 .unwrap_or_else(|| ":memory:".to_string()),
89 _ => {
90 return Err(DataProfilerError::database_config(&format!(
91 "Unsupported database type: {}",
92 database_type
93 )));
94 }
95 };
96
97 let connection_string = credentials.apply_to_connection_string(&base_connection_string);
98 let ssl_config = load_ssl_config_from_environment(database_type);
99 let secure_connection_string =
100 ssl_config.apply_to_connection_string(connection_string, database_type);
101
102 credentials.validate(database_type)?;
103
104 let warnings =
105 validate_connection_security(&secure_connection_string, &ssl_config, database_type)?;
106 for warning in warnings {
107 log::warn!("SECURITY WARNING: {}", warning);
108 }
109
110 Ok((secure_connection_string, ssl_config))
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 #[test]
118 fn test_connection_security_validation() {
119 let connection_string = "postgresql://user:pass@localhost:5432/db";
120 let ssl_config = SslConfig::default();
121
122 let warnings = validate_connection_security(connection_string, &ssl_config, "postgresql")
123 .expect("Failed to validate connection security");
124
125 assert!(!warnings.is_empty());
126 assert!(warnings.iter().any(|w| w.contains("Password embedded")));
127 assert!(warnings.iter().any(|w| w.contains("localhost")));
128 }
129}