dataprof_db/security/
ssl_config.rs1use super::utils::add_query_param;
4use crate::DataProfilerError;
5use serde::{Deserialize, Serialize};
6use std::path::Path;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct SslConfig {
11 pub require_ssl: bool,
13 pub ca_cert_path: Option<String>,
15 pub client_cert_path: Option<String>,
17 pub client_key_path: Option<String>,
19 pub verify_server_cert: bool,
21 pub ssl_mode: Option<String>,
23}
24
25impl Default for SslConfig {
26 fn default() -> Self {
27 Self {
28 require_ssl: false,
29 ca_cert_path: None,
30 client_cert_path: None,
31 client_key_path: None,
32 verify_server_cert: true,
33 ssl_mode: Some("prefer".to_string()),
34 }
35 }
36}
37
38impl SslConfig {
39 pub fn production() -> Self {
41 Self {
42 require_ssl: true,
43 ca_cert_path: None,
44 client_cert_path: None,
45 client_key_path: None,
46 verify_server_cert: true,
47 ssl_mode: Some("require".to_string()),
48 }
49 }
50
51 pub fn development() -> Self {
53 Self {
54 require_ssl: false,
55 ca_cert_path: None,
56 client_cert_path: None,
57 client_key_path: None,
58 verify_server_cert: false,
59 ssl_mode: Some("prefer".to_string()),
60 }
61 }
62
63 pub fn validate(&self) -> Result<(), DataProfilerError> {
65 if let Some(ca_cert_path) = &self.ca_cert_path
66 && !Path::new(ca_cert_path).exists()
67 {
68 return Err(DataProfilerError::database_ssl(&format!(
69 "CA certificate file not found: {}",
70 ca_cert_path
71 )));
72 }
73
74 if let Some(client_cert_path) = &self.client_cert_path
75 && !Path::new(client_cert_path).exists()
76 {
77 return Err(DataProfilerError::database_ssl(&format!(
78 "Client certificate file not found: {}",
79 client_cert_path
80 )));
81 }
82
83 if let Some(client_key_path) = &self.client_key_path
84 && !Path::new(client_key_path).exists()
85 {
86 return Err(DataProfilerError::database_ssl(&format!(
87 "Client private key file not found: {}",
88 client_key_path
89 )));
90 }
91
92 if let Some(ssl_mode) = &self.ssl_mode {
93 let valid_modes = [
94 "disable",
95 "allow",
96 "prefer",
97 "require",
98 "verify-ca",
99 "verify-full",
100 ];
101 if !valid_modes.contains(&ssl_mode.as_str()) {
102 return Err(DataProfilerError::database_ssl(&format!(
103 "Invalid SSL mode '{}'. Valid modes: {}",
104 ssl_mode,
105 valid_modes.join(", ")
106 )));
107 }
108 }
109
110 if self.require_ssl && !self.verify_server_cert {
111 log::warn!(
112 "SSL required but server certificate verification is disabled. This may be insecure."
113 );
114 }
115
116 if !self.require_ssl {
117 log::warn!("SSL not required. Database connections may be unencrypted.");
118 }
119
120 Ok(())
121 }
122
123 pub fn apply_to_connection_string(
125 &self,
126 mut connection_string: String,
127 database_type: &str,
128 ) -> String {
129 match database_type {
130 "postgresql" => {
131 if let Some(ssl_mode) = &self.ssl_mode {
132 connection_string = add_query_param(connection_string, "sslmode", ssl_mode);
133 }
134
135 if let Some(ca_cert) = &self.ca_cert_path {
136 connection_string = add_query_param(connection_string, "sslrootcert", ca_cert);
137 }
138
139 if let Some(client_cert) = &self.client_cert_path {
140 connection_string = add_query_param(connection_string, "sslcert", client_cert);
141 }
142
143 if let Some(client_key) = &self.client_key_path {
144 connection_string = add_query_param(connection_string, "sslkey", client_key);
145 }
146 }
147 "mysql" => {
148 if self.require_ssl {
149 connection_string = add_query_param(connection_string, "tls", "true");
150 }
151
152 if !self.verify_server_cert {
153 connection_string =
154 add_query_param(connection_string, "tls-skip-verify", "true");
155 }
156
157 if let Some(ca_cert) = &self.ca_cert_path {
158 connection_string = add_query_param(connection_string, "tls-ca", ca_cert);
159 }
160 }
161 "sqlite" => {
162 if self.require_ssl {
163 log::warn!("SSL configuration ignored for SQLite (embedded database)");
164 }
165 }
166 _ => {
167 log::warn!(
168 "SSL configuration validation for '{}' not fully implemented",
169 database_type
170 );
171 }
172 }
173
174 connection_string
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181
182 #[test]
183 fn test_ssl_config_validation() {
184 let mut config = SslConfig::default();
185 assert!(config.validate().is_ok());
186
187 config.ca_cert_path = Some("/nonexistent/path".to_string());
188 assert!(config.validate().is_err());
189 }
190}