use crate::encryption::algorithms::{EncryptionEngine, EncryptionKey};
use crate::encryption::errors::{EncryptionError, EncryptionResult};
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use tracing::warn;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AutoEncryptionConfig {
pub enabled: bool,
pub key_id: String,
pub field_patterns: Vec<FieldPattern>,
pub header_patterns: Vec<String>,
pub encrypt_environment_variables: bool,
pub encrypt_request_bodies: bool,
pub encrypt_response_bodies: bool,
pub custom_rules: Vec<EncryptionRule>,
}
impl Default for AutoEncryptionConfig {
fn default() -> Self {
Self {
enabled: false,
key_id: "default".to_string(),
field_patterns: Vec::new(),
header_patterns: Vec::new(),
encrypt_environment_variables: false,
encrypt_request_bodies: false,
encrypt_response_bodies: false,
custom_rules: Vec::new(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FieldPattern {
pub pattern: String,
pub case_sensitive: bool,
pub algorithm: Option<crate::encryption::algorithms::EncryptionAlgorithm>,
}
#[derive(Debug, Clone)]
pub struct RequestContext {
pub method: String,
pub path: String,
pub headers: HashMap<String, String>,
pub content_type: Option<String>,
}
impl RequestContext {
pub fn new(method: String, path: String, headers: HashMap<String, String>) -> Self {
let content_type =
headers.get("content-type").or_else(|| headers.get("Content-Type")).cloned();
Self {
method,
path,
headers,
content_type,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EncryptionRule {
pub name: String,
pub conditions: Vec<RuleCondition>,
pub actions: Vec<RuleAction>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum RuleCondition {
FieldMatches { pattern: String },
HeaderExists {
name: String,
value_pattern: Option<String>,
},
PathMatches { pattern: String },
MethodMatches { method: String },
ContentTypeMatches { pattern: String },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum RuleAction {
EncryptField { field_path: String },
EncryptHeader { header_name: String },
SkipEncryption,
UseAlgorithm {
algorithm: crate::encryption::algorithms::EncryptionAlgorithm,
},
}
#[derive(Debug, Clone)]
pub struct AutoEncryptionResult {
pub encrypted: bool,
pub fields_encrypted: usize,
pub headers_encrypted: usize,
pub metadata: EncryptionMetadata,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EncryptionMetadata {
pub encrypted_fields: HashMap<String, FieldEncryptionInfo>,
pub encrypted_headers: HashMap<String, HeaderEncryptionInfo>,
pub encrypted_at: chrono::DateTime<chrono::Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FieldEncryptionInfo {
pub field_path: String,
pub algorithm: crate::encryption::algorithms::EncryptionAlgorithm,
pub success: bool,
pub error: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HeaderEncryptionInfo {
pub header_name: String,
pub algorithm: crate::encryption::algorithms::EncryptionAlgorithm,
pub success: bool,
pub error: Option<String>,
}
#[derive(Debug, Clone)]
pub struct AutoEncryptionProcessor {
config: AutoEncryptionConfig,
encryption_key: Option<EncryptionKey>,
compiled_patterns: Vec<(Regex, FieldPattern)>,
}
impl AutoEncryptionProcessor {
pub fn new(config: AutoEncryptionConfig) -> Self {
let compiled_patterns = Self::compile_patterns(&config.field_patterns);
Self {
config,
encryption_key: None,
compiled_patterns,
}
}
pub fn set_encryption_key(&mut self, key: EncryptionKey) {
self.encryption_key = Some(key);
}
pub fn is_enabled(&self) -> bool {
self.config.enabled && self.encryption_key.is_some()
}
pub fn process_request(
&self,
request_data: &mut serde_json::Value,
request_context: Option<&RequestContext>,
) -> EncryptionResult<AutoEncryptionResult> {
if !self.is_enabled() {
return Ok(AutoEncryptionResult {
encrypted: false,
fields_encrypted: 0,
headers_encrypted: 0,
metadata: EncryptionMetadata {
encrypted_fields: HashMap::new(),
encrypted_headers: HashMap::new(),
encrypted_at: chrono::Utc::now(),
},
});
}
let mut fields_encrypted = 0;
let mut encrypted_fields = HashMap::new();
if self.config.encrypt_request_bodies {
fields_encrypted += self.encrypt_fields_in_value(
request_data,
"",
&mut encrypted_fields,
request_context,
)?;
}
Ok(AutoEncryptionResult {
encrypted: fields_encrypted > 0,
fields_encrypted,
headers_encrypted: 0, metadata: EncryptionMetadata {
encrypted_fields,
encrypted_headers: HashMap::new(),
encrypted_at: chrono::Utc::now(),
},
})
}
pub fn process_response(
&self,
response_data: &mut serde_json::Value,
request_context: Option<&RequestContext>,
) -> EncryptionResult<AutoEncryptionResult> {
if !self.is_enabled() || !self.config.encrypt_response_bodies {
return Ok(AutoEncryptionResult {
encrypted: false,
fields_encrypted: 0,
headers_encrypted: 0,
metadata: EncryptionMetadata {
encrypted_fields: HashMap::new(),
encrypted_headers: HashMap::new(),
encrypted_at: chrono::Utc::now(),
},
});
}
let mut fields_encrypted = 0;
let mut encrypted_fields = HashMap::new();
fields_encrypted += self.encrypt_fields_in_value(
response_data,
"",
&mut encrypted_fields,
request_context,
)?;
Ok(AutoEncryptionResult {
encrypted: fields_encrypted > 0,
fields_encrypted,
headers_encrypted: 0,
metadata: EncryptionMetadata {
encrypted_fields,
encrypted_headers: HashMap::new(),
encrypted_at: chrono::Utc::now(),
},
})
}
fn encrypt_fields_in_value(
&self,
value: &mut serde_json::Value,
current_path: &str,
encrypted_fields: &mut HashMap<String, FieldEncryptionInfo>,
request_context: Option<&RequestContext>,
) -> EncryptionResult<usize> {
let mut count = 0;
match value {
serde_json::Value::Object(map) => {
let mut fields_to_encrypt = Vec::new();
for (key, _) in map.iter() {
let field_path = if current_path.is_empty() {
key.clone()
} else {
format!("{}.{}", current_path, key)
};
if self.should_encrypt_field(key, &field_path, request_context) {
fields_to_encrypt.push(key.clone());
}
}
for field_name in fields_to_encrypt {
let field_path = if current_path.is_empty() {
field_name.clone()
} else {
format!("{}.{}", current_path, field_name)
};
if let Some(field_value) = map.get(&field_name) {
if let Some(string_value) = field_value.as_str() {
if let Some(encryption_key) = &self.encryption_key {
match EncryptionEngine::encrypt_string(encryption_key, string_value)
{
Ok(encrypted) => {
let encrypted_json = serde_json::to_value(&encrypted)
.map_err(|e| {
EncryptionError::serialization_error(e.to_string())
})?;
map.insert(field_name.clone(), encrypted_json);
encrypted_fields.insert(
field_path.clone(),
FieldEncryptionInfo {
field_path: field_path.clone(),
algorithm: crate::encryption::algorithms::EncryptionAlgorithm::Aes256Gcm,
success: true,
error: None,
},
);
count += 1;
}
Err(e) => {
encrypted_fields.insert(
field_path.clone(),
FieldEncryptionInfo {
field_path: field_path.clone(),
algorithm: crate::encryption::algorithms::EncryptionAlgorithm::Aes256Gcm,
success: false,
error: Some(e.to_string()),
},
);
}
}
}
}
}
}
for (_, v) in map.iter_mut() {
let nested_path = if current_path.is_empty() {
String::new()
} else {
current_path.to_string()
};
count += self.encrypt_fields_in_value(
v,
&nested_path,
encrypted_fields,
request_context,
)?;
}
}
serde_json::Value::Array(arr) => {
for (index, item) in arr.iter_mut().enumerate() {
let nested_path = if current_path.is_empty() {
format!("[{}]", index)
} else {
format!("{}.[{}]", current_path, index)
};
count += self.encrypt_fields_in_value(
item,
&nested_path,
encrypted_fields,
request_context,
)?;
}
}
_ => {}
}
Ok(count)
}
fn should_encrypt_field(
&self,
field_name: &str,
field_path: &str,
request_context: Option<&RequestContext>,
) -> bool {
for rule in &self.config.custom_rules {
if self.rule_matches(rule, field_name, field_path, request_context) {
for action in &rule.actions {
match action {
RuleAction::EncryptField { .. } => return true,
RuleAction::SkipEncryption => return false,
_ => {}
}
}
}
}
for (regex, pattern) in &self.compiled_patterns {
let text_to_match = if pattern.case_sensitive {
field_path.to_string()
} else {
field_path.to_lowercase()
};
if regex.is_match(&text_to_match) {
return true;
}
}
false
}
fn rule_matches(
&self,
rule: &EncryptionRule,
field_name: &str,
field_path: &str,
request_context: Option<&RequestContext>,
) -> bool {
for condition in &rule.conditions {
match condition {
RuleCondition::FieldMatches { pattern } => {
if !Self::matches_pattern(field_name, pattern)
&& !Self::matches_pattern(field_path, pattern)
{
return false;
}
}
RuleCondition::HeaderExists {
name,
value_pattern,
} => {
if let Some(ctx) = request_context {
let header_exists = ctx.headers.contains_key(name);
if !header_exists {
return false;
}
if let Some(pattern) = value_pattern {
if let Some(header_value) = ctx.headers.get(name) {
if !Self::matches_pattern(header_value, pattern) {
return false;
}
} else {
return false;
}
}
} else {
continue;
}
}
RuleCondition::PathMatches { pattern } => {
if let Some(ctx) = request_context {
if !Self::matches_pattern(&ctx.path, pattern) {
return false;
}
} else {
continue;
}
}
RuleCondition::MethodMatches { method } => {
if let Some(ctx) = request_context {
if !ctx.method.eq_ignore_ascii_case(method) {
return false;
}
} else {
continue;
}
}
RuleCondition::ContentTypeMatches { pattern } => {
if let Some(ctx) = request_context {
if let Some(content_type) = &ctx.content_type {
if !Self::matches_pattern(content_type, pattern) {
return false;
}
} else {
return false;
}
} else {
return false;
}
}
}
}
true
}
fn matches_pattern(text: &str, pattern: &str) -> bool {
match Regex::new(pattern) {
Ok(regex) => regex.is_match(text),
Err(_) => {
text.contains(pattern)
}
}
}
fn compile_patterns(field_patterns: &[FieldPattern]) -> Vec<(Regex, FieldPattern)> {
let mut compiled = Vec::new();
for pattern in field_patterns {
match Regex::new(&pattern.pattern) {
Ok(regex) => {
compiled.push((regex, pattern.clone()));
}
Err(e) => {
warn!("Failed to compile regex pattern '{}': {}", pattern.pattern, e);
}
}
}
compiled
}
pub fn get_default_field_patterns() -> Vec<FieldPattern> {
vec![
FieldPattern {
pattern: r"(?i)password".to_string(),
case_sensitive: false,
algorithm: None,
},
FieldPattern {
pattern: r"(?i)secret".to_string(),
case_sensitive: false,
algorithm: None,
},
FieldPattern {
pattern: r"(?i)token".to_string(),
case_sensitive: false,
algorithm: None,
},
FieldPattern {
pattern: r"(?i)key".to_string(),
case_sensitive: false,
algorithm: None,
},
FieldPattern {
pattern: r"(?i)auth".to_string(),
case_sensitive: false,
algorithm: None,
},
]
}
pub fn get_default_header_patterns() -> Vec<String> {
vec![
"authorization".to_string(),
"x-api-key".to_string(),
"x-auth-token".to_string(),
"cookie".to_string(),
]
}
pub fn validate_config(&self) -> EncryptionResult<()> {
if self.config.enabled && self.encryption_key.is_none() {
return Err(EncryptionError::auto_encryption_config_error(
"Auto-encryption enabled but no encryption key provided",
));
}
for pattern in &self.config.field_patterns {
if pattern.pattern.is_empty() {
return Err(EncryptionError::auto_encryption_config_error("Empty field pattern"));
}
if let Err(e) = Regex::new(&pattern.pattern) {
return Err(EncryptionError::auto_encryption_config_error(format!(
"Invalid regex pattern '{}': {}",
pattern.pattern, e
)));
}
}
for rule in &self.config.custom_rules {
if rule.name.is_empty() {
return Err(EncryptionError::auto_encryption_config_error(
"Encryption rule name cannot be empty",
));
}
if rule.conditions.is_empty() {
return Err(EncryptionError::auto_encryption_config_error(
"Encryption rule must have at least one condition",
));
}
if rule.actions.is_empty() {
return Err(EncryptionError::auto_encryption_config_error(
"Encryption rule must have at least one action",
));
}
}
Ok(())
}
pub fn default_config() -> AutoEncryptionConfig {
AutoEncryptionConfig {
enabled: false,
key_id: "auto_encryption_key".to_string(),
field_patterns: Self::get_default_field_patterns(),
header_patterns: Self::get_default_header_patterns(),
encrypt_environment_variables: true,
encrypt_request_bodies: true,
encrypt_response_bodies: false,
custom_rules: Vec::new(),
}
}
}
impl Default for AutoEncryptionProcessor {
fn default() -> Self {
Self::new(AutoEncryptionConfig::default())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::encryption::algorithms::EncryptionAlgorithm;
#[test]
fn test_auto_encryption_config_default() {
let config = AutoEncryptionConfig::default();
assert!(!config.enabled);
assert_eq!(config.key_id, "default");
assert!(config.field_patterns.is_empty());
assert!(config.header_patterns.is_empty());
assert!(!config.encrypt_environment_variables);
assert!(!config.encrypt_request_bodies);
assert!(!config.encrypt_response_bodies);
assert!(config.custom_rules.is_empty());
}
#[test]
fn test_auto_encryption_config_creation() {
let config = AutoEncryptionConfig {
enabled: true,
key_id: "test-key".to_string(),
field_patterns: vec![],
header_patterns: vec!["Authorization".to_string()],
encrypt_environment_variables: true,
encrypt_request_bodies: true,
encrypt_response_bodies: true,
custom_rules: vec![],
};
assert!(config.enabled);
assert_eq!(config.key_id, "test-key");
assert_eq!(config.header_patterns.len(), 1);
}
#[test]
fn test_auto_encryption_config_serialization() {
let config = AutoEncryptionConfig::default();
let json = serde_json::to_string(&config).unwrap();
assert!(json.contains("enabled"));
assert!(json.contains("default"));
}
#[test]
fn test_field_pattern_creation() {
let pattern = FieldPattern {
pattern: "password".to_string(),
case_sensitive: false,
algorithm: Some(EncryptionAlgorithm::Aes256Gcm),
};
assert_eq!(pattern.pattern, "password");
assert!(!pattern.case_sensitive);
assert!(pattern.algorithm.is_some());
}
#[test]
fn test_field_pattern_serialization() {
let pattern = FieldPattern {
pattern: ".*secret.*".to_string(),
case_sensitive: true,
algorithm: None,
};
let json = serde_json::to_string(&pattern).unwrap();
assert!(json.contains(".*secret.*"));
}
#[test]
fn test_request_context_new() {
let mut headers = HashMap::new();
headers.insert("Content-Type".to_string(), "application/json".to_string());
let context =
RequestContext::new("POST".to_string(), "/api/test".to_string(), headers.clone());
assert_eq!(context.method, "POST");
assert_eq!(context.path, "/api/test");
assert_eq!(context.content_type, Some("application/json".to_string()));
}
#[test]
fn test_request_context_content_type_lowercase() {
let mut headers = HashMap::new();
headers.insert("content-type".to_string(), "text/xml".to_string());
let context = RequestContext::new("GET".to_string(), "/test".to_string(), headers);
assert_eq!(context.content_type, Some("text/xml".to_string()));
}
#[test]
fn test_request_context_no_content_type() {
let headers = HashMap::new();
let context = RequestContext::new("GET".to_string(), "/test".to_string(), headers);
assert_eq!(context.content_type, None);
}
#[test]
fn test_encryption_rule_creation() {
let rule = EncryptionRule {
name: "encrypt-passwords".to_string(),
conditions: vec![RuleCondition::FieldMatches {
pattern: "password".to_string(),
}],
actions: vec![RuleAction::EncryptField {
field_path: "password".to_string(),
}],
};
assert_eq!(rule.name, "encrypt-passwords");
assert_eq!(rule.conditions.len(), 1);
assert_eq!(rule.actions.len(), 1);
}
#[test]
fn test_encryption_rule_serialization() {
let rule = EncryptionRule {
name: "test-rule".to_string(),
conditions: vec![],
actions: vec![],
};
let json = serde_json::to_string(&rule).unwrap();
assert!(json.contains("test-rule"));
}
#[test]
fn test_rule_condition_variants() {
let field_match = RuleCondition::FieldMatches {
pattern: "password".to_string(),
};
let header_exists = RuleCondition::HeaderExists {
name: "Authorization".to_string(),
value_pattern: None,
};
let path_matches = RuleCondition::PathMatches {
pattern: "/api/.*".to_string(),
};
let method_matches = RuleCondition::MethodMatches {
method: "POST".to_string(),
};
let content_type_matches = RuleCondition::ContentTypeMatches {
pattern: "application/json".to_string(),
};
match field_match {
RuleCondition::FieldMatches { pattern } => assert_eq!(pattern, "password"),
_ => panic!("Wrong variant"),
}
match header_exists {
RuleCondition::HeaderExists {
name,
value_pattern,
} => {
assert_eq!(name, "Authorization");
assert!(value_pattern.is_none());
}
_ => panic!("Wrong variant"),
}
match path_matches {
RuleCondition::PathMatches { pattern } => assert_eq!(pattern, "/api/.*"),
_ => panic!("Wrong variant"),
}
match method_matches {
RuleCondition::MethodMatches { method } => assert_eq!(method, "POST"),
_ => panic!("Wrong variant"),
}
match content_type_matches {
RuleCondition::ContentTypeMatches { pattern } => {
assert_eq!(pattern, "application/json")
}
_ => panic!("Wrong variant"),
}
}
#[test]
fn test_rule_action_variants() {
let encrypt_field = RuleAction::EncryptField {
field_path: "password".to_string(),
};
let encrypt_header = RuleAction::EncryptHeader {
header_name: "Authorization".to_string(),
};
let skip = RuleAction::SkipEncryption;
let use_algorithm = RuleAction::UseAlgorithm {
algorithm: EncryptionAlgorithm::Aes256Gcm,
};
match encrypt_field {
RuleAction::EncryptField { field_path } => assert_eq!(field_path, "password"),
_ => panic!("Wrong variant"),
}
match encrypt_header {
RuleAction::EncryptHeader { header_name } => assert_eq!(header_name, "Authorization"),
_ => panic!("Wrong variant"),
}
match skip {
RuleAction::SkipEncryption => {}
_ => panic!("Wrong variant"),
}
match use_algorithm {
RuleAction::UseAlgorithm { algorithm } => {
assert_eq!(algorithm, EncryptionAlgorithm::Aes256Gcm)
}
_ => panic!("Wrong variant"),
}
}
#[test]
fn test_auto_encryption_result_creation() {
let result = AutoEncryptionResult {
encrypted: true,
fields_encrypted: 5,
headers_encrypted: 2,
metadata: EncryptionMetadata {
encrypted_fields: HashMap::new(),
encrypted_headers: HashMap::new(),
encrypted_at: chrono::Utc::now(),
},
};
assert!(result.encrypted);
assert_eq!(result.fields_encrypted, 5);
assert_eq!(result.headers_encrypted, 2);
}
#[test]
fn test_encryption_metadata_creation() {
let metadata = EncryptionMetadata {
encrypted_fields: HashMap::new(),
encrypted_headers: HashMap::new(),
encrypted_at: chrono::Utc::now(),
};
assert!(metadata.encrypted_fields.is_empty());
assert!(metadata.encrypted_headers.is_empty());
}
#[test]
fn test_encryption_metadata_serialization() {
let metadata = EncryptionMetadata {
encrypted_fields: HashMap::new(),
encrypted_headers: HashMap::new(),
encrypted_at: chrono::Utc::now(),
};
let json = serde_json::to_string(&metadata).unwrap();
assert!(json.contains("encrypted_fields"));
}
#[test]
fn test_field_encryption_info_creation() {
let info = FieldEncryptionInfo {
field_path: "user.password".to_string(),
algorithm: EncryptionAlgorithm::Aes256Gcm,
success: true,
error: None,
};
assert_eq!(info.field_path, "user.password");
assert!(info.success);
assert!(info.error.is_none());
}
#[test]
fn test_field_encryption_info_serialization() {
let info = FieldEncryptionInfo {
field_path: "test".to_string(),
algorithm: EncryptionAlgorithm::Aes256Gcm,
success: false,
error: Some("Encryption failed".to_string()),
};
let json = serde_json::to_string(&info).unwrap();
assert!(json.contains("test"));
assert!(json.contains("Encryption failed"));
}
#[test]
fn test_header_encryption_info_creation() {
let info = HeaderEncryptionInfo {
header_name: "Authorization".to_string(),
algorithm: EncryptionAlgorithm::Aes256Gcm,
success: true,
error: None,
};
assert_eq!(info.header_name, "Authorization");
assert!(info.success);
}
#[test]
fn test_header_encryption_info_serialization() {
let info = HeaderEncryptionInfo {
header_name: "X-API-Key".to_string(),
algorithm: EncryptionAlgorithm::Aes256Gcm,
success: true,
error: None,
};
let json = serde_json::to_string(&info).unwrap();
assert!(json.contains("X-API-Key"));
}
#[test]
fn test_auto_encryption_processor_new() {
let config = AutoEncryptionConfig::default();
let processor = AutoEncryptionProcessor::new(config.clone());
assert_eq!(processor.config.enabled, config.enabled);
assert!(processor.encryption_key.is_none());
}
#[test]
fn test_auto_encryption_processor_default() {
let processor = AutoEncryptionProcessor::default();
assert!(!processor.config.enabled);
assert!(processor.encryption_key.is_none());
}
#[test]
fn test_auto_encryption_processor_set_encryption_key() {
let config = AutoEncryptionConfig::default();
let mut processor = AutoEncryptionProcessor::new(config);
let key = EncryptionKey::generate(EncryptionAlgorithm::Aes256Gcm).unwrap();
processor.set_encryption_key(key.clone());
assert!(processor.encryption_key.is_some());
}
#[test]
fn test_auto_encryption_processor_is_enabled() {
let config = AutoEncryptionConfig {
enabled: true,
..Default::default()
};
let mut processor = AutoEncryptionProcessor::new(config);
assert!(!processor.is_enabled());
let key = EncryptionKey::generate(EncryptionAlgorithm::Aes256Gcm).unwrap();
processor.set_encryption_key(key);
assert!(processor.is_enabled());
}
#[test]
fn test_auto_encryption_processor_is_enabled_config_disabled() {
let config = AutoEncryptionConfig::default();
let mut processor = AutoEncryptionProcessor::new(config);
let key = EncryptionKey::generate(EncryptionAlgorithm::Aes256Gcm).unwrap();
processor.set_encryption_key(key);
assert!(!processor.is_enabled());
}
}