use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SecurityEventSeverity {
Low,
Medium,
High,
Critical,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SecurityEventType {
AuthSuccess,
AuthFailure,
AuthTokenExpired,
AuthTokenRevoked,
AuthMfaEnabled,
AuthMfaDisabled,
AuthPasswordChanged,
AuthPasswordReset,
AuthzAccessGranted,
AuthzAccessDenied,
AuthzPrivilegeEscalation,
AuthzRoleChanged,
AuthzPermissionChanged,
AccessUserCreated,
AccessUserDeleted,
AccessUserSuspended,
AccessUserActivated,
AccessApiTokenCreated,
AccessApiTokenDeleted,
AccessApiTokenRotated,
ConfigChanged,
ConfigSecurityPolicyUpdated,
ConfigEncryptionKeyRotated,
ConfigTlsCertificateUpdated,
DataExported,
DataDeleted,
DataEncrypted,
DataDecrypted,
DataClassified,
SecurityVulnerabilityDetected,
SecurityThreatDetected,
SecurityAnomalyDetected,
SecurityRateLimitExceeded,
SecuritySuspiciousActivity,
ComplianceAuditLogAccessed,
ComplianceComplianceCheck,
CompliancePolicyViolation,
}
impl SecurityEventType {
pub fn as_str(&self) -> &'static str {
match self {
SecurityEventType::AuthSuccess => "auth.success",
SecurityEventType::AuthFailure => "auth.failure",
SecurityEventType::AuthTokenExpired => "auth.token_expired",
SecurityEventType::AuthTokenRevoked => "auth.token_revoked",
SecurityEventType::AuthMfaEnabled => "auth.mfa_enabled",
SecurityEventType::AuthMfaDisabled => "auth.mfa_disabled",
SecurityEventType::AuthPasswordChanged => "auth.password_changed",
SecurityEventType::AuthPasswordReset => "auth.password_reset",
SecurityEventType::AuthzAccessGranted => "authz.access_granted",
SecurityEventType::AuthzAccessDenied => "authz.access_denied",
SecurityEventType::AuthzPrivilegeEscalation => "authz.privilege_escalation",
SecurityEventType::AuthzRoleChanged => "authz.role_changed",
SecurityEventType::AuthzPermissionChanged => "authz.permission_changed",
SecurityEventType::AccessUserCreated => "access.user_created",
SecurityEventType::AccessUserDeleted => "access.user_deleted",
SecurityEventType::AccessUserSuspended => "access.user_suspended",
SecurityEventType::AccessUserActivated => "access.user_activated",
SecurityEventType::AccessApiTokenCreated => "access.api_token_created",
SecurityEventType::AccessApiTokenDeleted => "access.api_token_deleted",
SecurityEventType::AccessApiTokenRotated => "access.api_token_rotated",
SecurityEventType::ConfigChanged => "config.changed",
SecurityEventType::ConfigSecurityPolicyUpdated => "config.security_policy_updated",
SecurityEventType::ConfigEncryptionKeyRotated => "config.encryption_key_rotated",
SecurityEventType::ConfigTlsCertificateUpdated => "config.tls_certificate_updated",
SecurityEventType::DataExported => "data.exported",
SecurityEventType::DataDeleted => "data.deleted",
SecurityEventType::DataEncrypted => "data.encrypted",
SecurityEventType::DataDecrypted => "data.decrypted",
SecurityEventType::DataClassified => "data.classified",
SecurityEventType::SecurityVulnerabilityDetected => "security.vulnerability_detected",
SecurityEventType::SecurityThreatDetected => "security.threat_detected",
SecurityEventType::SecurityAnomalyDetected => "security.anomaly_detected",
SecurityEventType::SecurityRateLimitExceeded => "security.rate_limit_exceeded",
SecurityEventType::SecuritySuspiciousActivity => "security.suspicious_activity",
SecurityEventType::ComplianceAuditLogAccessed => "compliance.audit_log_accessed",
SecurityEventType::ComplianceComplianceCheck => "compliance.compliance_check",
SecurityEventType::CompliancePolicyViolation => "compliance.policy_violation",
}
}
pub fn default_severity(&self) -> SecurityEventSeverity {
match self {
SecurityEventType::AuthSuccess
| SecurityEventType::AuthzAccessGranted
| SecurityEventType::AccessUserCreated
| SecurityEventType::AccessUserActivated
| SecurityEventType::AccessApiTokenCreated
| SecurityEventType::AuthMfaEnabled
| SecurityEventType::DataEncrypted
| SecurityEventType::DataClassified
| SecurityEventType::ComplianceComplianceCheck => SecurityEventSeverity::Low,
SecurityEventType::AuthFailure
| SecurityEventType::AuthTokenExpired
| SecurityEventType::AuthTokenRevoked
| SecurityEventType::AuthMfaDisabled
| SecurityEventType::AuthPasswordChanged
| SecurityEventType::AuthPasswordReset
| SecurityEventType::AuthzAccessDenied
| SecurityEventType::AuthzRoleChanged
| SecurityEventType::AuthzPermissionChanged
| SecurityEventType::AccessUserSuspended
| SecurityEventType::AccessApiTokenDeleted
| SecurityEventType::ConfigChanged
| SecurityEventType::DataExported
| SecurityEventType::DataDeleted
| SecurityEventType::SecurityRateLimitExceeded
| SecurityEventType::ComplianceAuditLogAccessed => SecurityEventSeverity::Medium,
SecurityEventType::AuthzPrivilegeEscalation
| SecurityEventType::AccessUserDeleted
| SecurityEventType::AccessApiTokenRotated
| SecurityEventType::ConfigSecurityPolicyUpdated
| SecurityEventType::ConfigEncryptionKeyRotated
| SecurityEventType::ConfigTlsCertificateUpdated
| SecurityEventType::DataDecrypted
| SecurityEventType::SecurityThreatDetected
| SecurityEventType::SecurityAnomalyDetected
| SecurityEventType::SecuritySuspiciousActivity
| SecurityEventType::CompliancePolicyViolation => SecurityEventSeverity::High,
SecurityEventType::SecurityVulnerabilityDetected => SecurityEventSeverity::Critical,
}
}
pub fn soc2_cc(&self) -> Vec<&'static str> {
match self {
SecurityEventType::AuthSuccess
| SecurityEventType::AuthFailure
| SecurityEventType::AuthTokenExpired
| SecurityEventType::AuthTokenRevoked
| SecurityEventType::AuthMfaEnabled
| SecurityEventType::AuthMfaDisabled
| SecurityEventType::AuthPasswordChanged
| SecurityEventType::AuthPasswordReset
| SecurityEventType::AccessUserCreated
| SecurityEventType::AccessUserDeleted
| SecurityEventType::AccessUserSuspended
| SecurityEventType::AccessUserActivated
| SecurityEventType::AccessApiTokenCreated
| SecurityEventType::AccessApiTokenDeleted
| SecurityEventType::AccessApiTokenRotated
| SecurityEventType::AuthzPrivilegeEscalation
| SecurityEventType::AuthzRoleChanged
| SecurityEventType::AuthzPermissionChanged => vec!["CC6"],
SecurityEventType::AuthzAccessGranted | SecurityEventType::AuthzAccessDenied => {
vec!["CC6"]
}
SecurityEventType::ConfigChanged
| SecurityEventType::ConfigSecurityPolicyUpdated
| SecurityEventType::ConfigEncryptionKeyRotated
| SecurityEventType::ConfigTlsCertificateUpdated => vec!["CC7"],
SecurityEventType::DataExported
| SecurityEventType::DataDeleted
| SecurityEventType::DataEncrypted
| SecurityEventType::DataDecrypted
| SecurityEventType::DataClassified
| SecurityEventType::SecurityVulnerabilityDetected
| SecurityEventType::SecurityThreatDetected
| SecurityEventType::SecurityAnomalyDetected
| SecurityEventType::SecurityRateLimitExceeded
| SecurityEventType::SecuritySuspiciousActivity
| SecurityEventType::ComplianceAuditLogAccessed
| SecurityEventType::ComplianceComplianceCheck
| SecurityEventType::CompliancePolicyViolation => vec!["CC4"],
}
}
pub fn iso27001(&self) -> Vec<&'static str> {
match self {
SecurityEventType::AuthSuccess
| SecurityEventType::AuthFailure
| SecurityEventType::AuthTokenExpired
| SecurityEventType::AuthTokenRevoked
| SecurityEventType::AuthMfaEnabled
| SecurityEventType::AuthMfaDisabled
| SecurityEventType::AuthPasswordChanged
| SecurityEventType::AuthPasswordReset
| SecurityEventType::AccessUserCreated
| SecurityEventType::AccessUserDeleted
| SecurityEventType::AccessUserSuspended
| SecurityEventType::AccessUserActivated
| SecurityEventType::AccessApiTokenCreated
| SecurityEventType::AccessApiTokenDeleted
| SecurityEventType::AccessApiTokenRotated
| SecurityEventType::AuthzPrivilegeEscalation
| SecurityEventType::AuthzRoleChanged
| SecurityEventType::AuthzPermissionChanged => vec!["A.9.2"],
SecurityEventType::AuthzAccessGranted | SecurityEventType::AuthzAccessDenied => {
vec!["A.9.4"]
}
SecurityEventType::ConfigChanged
| SecurityEventType::ConfigSecurityPolicyUpdated
| SecurityEventType::ConfigEncryptionKeyRotated
| SecurityEventType::ConfigTlsCertificateUpdated => vec!["A.12.1"],
SecurityEventType::DataExported
| SecurityEventType::DataDeleted
| SecurityEventType::DataEncrypted
| SecurityEventType::DataDecrypted
| SecurityEventType::DataClassified
| SecurityEventType::SecurityVulnerabilityDetected
| SecurityEventType::SecurityThreatDetected
| SecurityEventType::SecurityAnomalyDetected
| SecurityEventType::SecurityRateLimitExceeded
| SecurityEventType::SecuritySuspiciousActivity
| SecurityEventType::ComplianceAuditLogAccessed
| SecurityEventType::ComplianceComplianceCheck
| SecurityEventType::CompliancePolicyViolation => vec!["A.12.4"],
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EventSource {
pub system: String,
pub component: String,
pub version: String,
}
impl Default for EventSource {
fn default() -> Self {
Self {
system: "mockforge".to_string(),
component: "core".to_string(),
version: env!("CARGO_PKG_VERSION").to_string(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EventActor {
pub user_id: Option<String>,
pub username: Option<String>,
pub ip_address: Option<String>,
pub user_agent: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EventTarget {
pub resource_type: Option<String>,
pub resource_id: Option<String>,
pub method: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EventOutcome {
pub success: bool,
pub reason: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EventCompliance {
pub soc2_cc: Vec<String>,
pub iso27001: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecurityEvent {
pub timestamp: DateTime<Utc>,
pub event_type: String,
pub severity: SecurityEventSeverity,
pub source: EventSource,
pub actor: Option<EventActor>,
pub target: Option<EventTarget>,
pub outcome: Option<EventOutcome>,
pub metadata: HashMap<String, serde_json::Value>,
pub compliance: EventCompliance,
}
impl SecurityEvent {
pub fn new(
event_type: SecurityEventType,
severity: Option<SecurityEventSeverity>,
source: Option<EventSource>,
) -> Self {
let default_severity = event_type.default_severity();
let severity = severity.unwrap_or(default_severity);
Self {
timestamp: Utc::now(),
event_type: event_type.as_str().to_string(),
severity,
source: source.unwrap_or_default(),
actor: None,
target: None,
outcome: None,
metadata: HashMap::new(),
compliance: EventCompliance {
soc2_cc: event_type.soc2_cc().iter().map(|s| s.to_string()).collect(),
iso27001: event_type.iso27001().iter().map(|s| s.to_string()).collect(),
},
}
}
pub fn with_actor(mut self, actor: EventActor) -> Self {
self.actor = Some(actor);
self
}
pub fn with_target(mut self, target: EventTarget) -> Self {
self.target = Some(target);
self
}
pub fn with_outcome(mut self, outcome: EventOutcome) -> Self {
self.outcome = Some(outcome);
self
}
pub fn with_metadata(mut self, key: String, value: serde_json::Value) -> Self {
self.metadata.insert(key, value);
self
}
pub fn to_json(&self) -> Result<String, serde_json::Error> {
serde_json::to_string(self)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_event_type_strings() {
assert_eq!(SecurityEventType::AuthSuccess.as_str(), "auth.success");
assert_eq!(SecurityEventType::AuthFailure.as_str(), "auth.failure");
assert_eq!(
SecurityEventType::AuthzPrivilegeEscalation.as_str(),
"authz.privilege_escalation"
);
}
#[test]
fn test_event_severity() {
assert_eq!(SecurityEventType::AuthSuccess.default_severity(), SecurityEventSeverity::Low);
assert_eq!(
SecurityEventType::AuthFailure.default_severity(),
SecurityEventSeverity::Medium
);
assert_eq!(
SecurityEventType::AuthzPrivilegeEscalation.default_severity(),
SecurityEventSeverity::High
);
assert_eq!(
SecurityEventType::SecurityVulnerabilityDetected.default_severity(),
SecurityEventSeverity::Critical
);
}
#[test]
fn test_compliance_mappings() {
let auth_success = SecurityEventType::AuthSuccess;
assert!(auth_success.soc2_cc().contains(&"CC6"));
assert!(auth_success.iso27001().contains(&"A.9.2"));
let config_changed = SecurityEventType::ConfigChanged;
assert!(config_changed.soc2_cc().contains(&"CC7"));
assert!(config_changed.iso27001().contains(&"A.12.1"));
}
#[test]
fn test_security_event_creation() {
let event = SecurityEvent::new(SecurityEventType::AuthSuccess, None, None);
assert_eq!(event.event_type, "auth.success");
assert_eq!(event.severity, SecurityEventSeverity::Low);
assert_eq!(event.source.system, "mockforge");
}
#[test]
fn test_security_event_builder() {
let event = SecurityEvent::new(SecurityEventType::AuthFailure, None, None)
.with_actor(EventActor {
user_id: Some("user-123".to_string()),
username: Some("admin".to_string()),
ip_address: Some("192.168.1.100".to_string()),
user_agent: Some("Mozilla/5.0".to_string()),
})
.with_target(EventTarget {
resource_type: Some("api".to_string()),
resource_id: Some("/api/v1/workspaces".to_string()),
method: Some("GET".to_string()),
})
.with_outcome(EventOutcome {
success: false,
reason: Some("Invalid credentials".to_string()),
})
.with_metadata("attempt_count".to_string(), serde_json::json!(3));
assert_eq!(event.event_type, "auth.failure");
assert!(event.actor.is_some());
assert!(event.target.is_some());
assert!(event.outcome.is_some());
assert_eq!(event.metadata.get("attempt_count"), Some(&serde_json::json!(3)));
}
#[test]
fn test_security_event_serialization() {
let event = SecurityEvent::new(SecurityEventType::AuthSuccess, None, None);
let json = event.to_json().unwrap();
assert!(json.contains("auth.success"));
assert!(json.contains("mockforge"));
}
}