Skip to main content

mockforge_core/security/
privileged_access.rs

1//! Privileged Access Management
2//!
3//! This module provides comprehensive privileged access management including:
4//! - MFA enforcement
5//! - Access justification tracking
6//! - Privileged action monitoring
7//! - Session management
8//! - Automatic revocation
9
10use crate::security::{
11    emit_security_event_async,
12    events::{EventActor, EventOutcome, EventTarget, SecurityEvent, SecurityEventType},
13    justification_storage::{AccessJustification, JustificationStorage},
14    mfa_tracking::MfaStorage,
15};
16use crate::Error;
17use chrono::{DateTime, Duration, Utc};
18use serde::{Deserialize, Serialize};
19use serde_json;
20use std::collections::HashMap;
21use uuid::Uuid;
22
23/// Privileged role types
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
25#[serde(rename_all = "lowercase")]
26pub enum PrivilegedRole {
27    /// Admin role - full system access
28    Admin,
29    /// Owner role - organization ownership
30    Owner,
31    /// Service account - automated system access
32    ServiceAccount,
33}
34
35/// Privileged access request status
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
37#[serde(rename_all = "lowercase")]
38pub enum RequestStatus {
39    /// Request pending manager approval
40    PendingManager,
41    /// Request pending security review
42    PendingSecurity,
43    /// Request approved
44    Approved,
45    /// Request denied
46    Denied,
47    /// Request cancelled
48    Cancelled,
49}
50
51/// Privileged action type
52#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
53#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
54#[serde(rename_all = "snake_case")]
55pub enum PrivilegedActionType {
56    /// User management actions
57    UserCreate,
58    /// User deletion
59    UserDelete,
60    /// User modification
61    UserModify,
62    /// Role assignment
63    RoleAssign,
64    /// Role revocation
65    RoleRevoke,
66    /// Role escalation
67    RoleEscalate,
68    /// Permission grant
69    PermissionGrant,
70    /// Permission revocation
71    PermissionRevoke,
72    /// Configuration modification
73    ConfigModify,
74    /// Security policy change
75    SecurityPolicyChange,
76    /// Security setting change
77    SecuritySettingChange,
78    /// Audit log access
79    AuditLogAccess,
80    /// Data export
81    DataExport,
82    /// Data deletion
83    DataDelete,
84    /// Other privileged actions
85    Other,
86}
87
88/// Privileged access request
89#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct PrivilegedAccessRequest {
91    /// Request ID
92    pub request_id: Uuid,
93    /// User requesting access
94    pub user_id: Uuid,
95    /// Requested role
96    pub requested_role: PrivilegedRole,
97    /// Justification text
98    pub justification: String,
99    /// Business need description
100    pub business_need: Option<String>,
101    /// Manager who approved (if applicable)
102    pub manager_approval: Option<Uuid>,
103    /// Security team approval
104    pub security_approval: Option<Uuid>,
105    /// Request status
106    pub status: RequestStatus,
107    /// Request creation date
108    pub created_at: DateTime<Utc>,
109    /// Last update date
110    pub updated_at: DateTime<Utc>,
111    /// Access expiration date (if approved)
112    pub expires_at: Option<DateTime<Utc>>,
113}
114
115impl PrivilegedAccessRequest {
116    /// Create a new privileged access request
117    pub fn new(
118        user_id: Uuid,
119        requested_role: PrivilegedRole,
120        justification: String,
121        business_need: Option<String>,
122        manager_approval: Option<Uuid>,
123    ) -> Self {
124        let now = Utc::now();
125        Self {
126            request_id: Uuid::new_v4(),
127            user_id,
128            requested_role,
129            justification,
130            business_need,
131            manager_approval,
132            security_approval: None,
133            status: RequestStatus::PendingManager,
134            created_at: now,
135            updated_at: now,
136            expires_at: None,
137        }
138    }
139
140    /// Check if request is approved
141    pub fn is_approved(&self) -> bool {
142        self.status == RequestStatus::Approved
143    }
144
145    /// Check if request is expired
146    pub fn is_expired(&self) -> bool {
147        self.expires_at.map(|exp| Utc::now() > exp).unwrap_or(false)
148    }
149}
150
151/// Privileged action record
152#[derive(Debug, Clone, Serialize, Deserialize)]
153pub struct PrivilegedAction {
154    /// Action ID
155    pub action_id: Uuid,
156    /// User who performed the action
157    pub user_id: Uuid,
158    /// Action type
159    pub action_type: PrivilegedActionType,
160    /// Resource affected
161    pub resource: Option<String>,
162    /// Action details
163    pub details: Option<String>,
164    /// IP address
165    pub ip_address: Option<String>,
166    /// User agent
167    pub user_agent: Option<String>,
168    /// Session ID
169    pub session_id: Option<String>,
170    /// Timestamp
171    pub timestamp: DateTime<Utc>,
172}
173
174/// Privileged session information
175#[derive(Debug, Clone, Serialize, Deserialize)]
176pub struct PrivilegedSession {
177    /// Session ID
178    pub session_id: String,
179    /// User ID
180    pub user_id: Uuid,
181    /// Role
182    pub role: PrivilegedRole,
183    /// Session start time
184    pub started_at: DateTime<Utc>,
185    /// Last activity time
186    pub last_activity: DateTime<Utc>,
187    /// IP address
188    pub ip_address: Option<String>,
189    /// User agent
190    pub user_agent: Option<String>,
191    /// Whether session is active
192    pub is_active: bool,
193}
194
195/// Privileged access configuration
196#[derive(Debug, Clone, Serialize, Deserialize)]
197#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
198pub struct PrivilegedAccessConfig {
199    /// Require MFA for privileged users
200    pub require_mfa: bool,
201    /// MFA grace period in days
202    pub mfa_grace_period_days: u64,
203    /// Auto-suspend if MFA not enabled
204    pub auto_suspend_no_mfa: bool,
205    /// Session timeout in minutes
206    pub session_timeout_minutes: u64,
207    /// Max concurrent sessions
208    pub max_concurrent_sessions: u32,
209    /// Record sensitive actions
210    pub record_sensitive_actions: bool,
211    /// Monitor activity
212    pub monitor_activity: bool,
213    /// Sensitive action types that require alerting
214    pub sensitive_actions: Vec<PrivilegedActionType>,
215}
216
217impl Default for PrivilegedAccessConfig {
218    fn default() -> Self {
219        Self {
220            require_mfa: true,
221            mfa_grace_period_days: 7,
222            auto_suspend_no_mfa: true,
223            session_timeout_minutes: 30,
224            max_concurrent_sessions: 2,
225            record_sensitive_actions: true,
226            monitor_activity: true,
227            sensitive_actions: vec![
228                PrivilegedActionType::UserDelete,
229                PrivilegedActionType::RoleEscalate,
230                PrivilegedActionType::SecurityPolicyChange,
231                PrivilegedActionType::DataExport,
232                PrivilegedActionType::AuditLogAccess,
233            ],
234        }
235    }
236}
237
238/// Privileged access manager
239///
240/// Manages privileged access requests, monitoring, and enforcement
241pub struct PrivilegedAccessManager {
242    config: PrivilegedAccessConfig,
243    mfa_storage: Option<Arc<dyn MfaStorage>>,
244    justification_storage: Option<Arc<dyn JustificationStorage>>,
245    /// Active privileged sessions
246    sessions: Arc<RwLock<HashMap<String, PrivilegedSession>>>,
247    /// Privileged actions log
248    actions: Arc<RwLock<Vec<PrivilegedAction>>>,
249    /// Active access requests
250    requests: Arc<RwLock<HashMap<Uuid, PrivilegedAccessRequest>>>,
251}
252
253impl PrivilegedAccessManager {
254    /// Create a new privileged access manager
255    pub fn new(
256        config: PrivilegedAccessConfig,
257        mfa_storage: Option<Arc<dyn MfaStorage>>,
258        justification_storage: Option<Arc<dyn JustificationStorage>>,
259    ) -> Self {
260        Self {
261            config,
262            mfa_storage,
263            justification_storage,
264            sessions: Arc::new(RwLock::new(HashMap::new())),
265            actions: Arc::new(RwLock::new(Vec::new())),
266            requests: Arc::new(RwLock::new(HashMap::new())),
267        }
268    }
269
270    /// Request privileged access
271    pub async fn request_privileged_access(
272        &self,
273        user_id: Uuid,
274        requested_role: PrivilegedRole,
275        justification: String,
276        business_need: Option<String>,
277        manager_approval: Option<Uuid>,
278    ) -> Result<PrivilegedAccessRequest, Error> {
279        let request = PrivilegedAccessRequest::new(
280            user_id,
281            requested_role,
282            justification,
283            business_need,
284            manager_approval,
285        );
286
287        let mut requests = self.requests.write().await;
288        requests.insert(request.request_id, request.clone());
289
290        Ok(request)
291    }
292
293    /// Approve privileged access request (manager approval)
294    pub async fn approve_manager(&self, request_id: Uuid, approver_id: Uuid) -> Result<(), Error> {
295        let mut requests = self.requests.write().await;
296        let request = requests
297            .get_mut(&request_id)
298            .ok_or_else(|| Error::Generic("Request not found".to_string()))?;
299
300        if request.status != RequestStatus::PendingManager {
301            return Err(Error::Generic("Request is not pending manager approval".to_string()));
302        }
303
304        let user_id = request.user_id;
305        request.manager_approval = Some(approver_id);
306        request.status = RequestStatus::PendingSecurity;
307        request.updated_at = Utc::now();
308
309        // Emit security event for manager approval
310        let event = SecurityEvent::new(SecurityEventType::AuthzPrivilegeEscalation, None, None)
311            .with_actor(EventActor {
312                user_id: Some(approver_id.to_string()),
313                username: None,
314                ip_address: None,
315                user_agent: None,
316            })
317            .with_target(EventTarget {
318                resource_type: Some("privileged_access_request".to_string()),
319                resource_id: Some(request_id.to_string()),
320                method: None,
321            })
322            .with_outcome(EventOutcome {
323                success: true,
324                reason: Some("Manager approval granted".to_string()),
325            })
326            .with_metadata("request_user_id".to_string(), serde_json::json!(user_id.to_string()))
327            .with_metadata(
328                "requested_role".to_string(),
329                serde_json::json!(format!("{:?}", request.requested_role)),
330            );
331
332        emit_security_event_async(event);
333
334        Ok(())
335    }
336
337    /// Approve privileged access request (security approval)
338    pub async fn approve_security(
339        &self,
340        request_id: Uuid,
341        approver_id: Uuid,
342        expiration_days: u64,
343    ) -> Result<(), Error> {
344        let mut requests = self.requests.write().await;
345        let request = requests
346            .get_mut(&request_id)
347            .ok_or_else(|| Error::Generic("Request not found".to_string()))?;
348
349        if request.status != RequestStatus::PendingSecurity {
350            return Err(Error::Generic("Request is not pending security approval".to_string()));
351        }
352
353        request.security_approval = Some(approver_id);
354        request.status = RequestStatus::Approved;
355        request.expires_at = Some(Utc::now() + Duration::days(expiration_days as i64));
356        request.updated_at = Utc::now();
357
358        // Store justification
359        if let Some(ref just_storage) = self.justification_storage {
360            let justification = AccessJustification::new(
361                request.user_id,
362                request.justification.clone(),
363                request.business_need.clone(),
364                request.manager_approval,
365                request.expires_at,
366            );
367            just_storage.set_justification(justification).await?;
368        }
369
370        // Emit security event for security approval
371        let event = SecurityEvent::new(SecurityEventType::AuthzPrivilegeEscalation, None, None)
372            .with_actor(EventActor {
373                user_id: Some(approver_id.to_string()),
374                username: None,
375                ip_address: None,
376                user_agent: None,
377            })
378            .with_target(EventTarget {
379                resource_type: Some("privileged_access_request".to_string()),
380                resource_id: Some(request_id.to_string()),
381                method: None,
382            })
383            .with_outcome(EventOutcome {
384                success: true,
385                reason: Some("Security approval granted".to_string()),
386            })
387            .with_metadata(
388                "request_user_id".to_string(),
389                serde_json::json!(request.user_id.to_string()),
390            )
391            .with_metadata(
392                "requested_role".to_string(),
393                serde_json::json!(format!("{:?}", request.requested_role)),
394            )
395            .with_metadata("expiration_days".to_string(), serde_json::json!(expiration_days));
396
397        emit_security_event_async(event);
398
399        Ok(())
400    }
401
402    /// Deny privileged access request
403    pub async fn deny_request(&self, request_id: Uuid, reason: String) -> Result<(), Error> {
404        let mut requests = self.requests.write().await;
405        let request = requests
406            .get_mut(&request_id)
407            .ok_or_else(|| Error::Generic("Request not found".to_string()))?;
408
409        let user_id = request.user_id;
410        request.status = RequestStatus::Denied;
411        request.updated_at = Utc::now();
412
413        // Emit security event for request denial
414        let event = SecurityEvent::new(SecurityEventType::AuthzAccessDenied, None, None)
415            .with_actor(EventActor {
416                user_id: Some(user_id.to_string()),
417                username: None,
418                ip_address: None,
419                user_agent: None,
420            })
421            .with_target(EventTarget {
422                resource_type: Some("privileged_access_request".to_string()),
423                resource_id: Some(request_id.to_string()),
424                method: None,
425            })
426            .with_outcome(EventOutcome {
427                success: false,
428                reason: Some(reason.clone()),
429            })
430            .with_metadata(
431                "requested_role".to_string(),
432                serde_json::json!(format!("{:?}", request.requested_role)),
433            );
434
435        emit_security_event_async(event);
436
437        Ok(())
438    }
439
440    /// Check MFA compliance for a user
441    pub async fn check_mfa_compliance(&self, user_id: Uuid) -> Result<bool, Error> {
442        if !self.config.require_mfa {
443            return Ok(true);
444        }
445
446        if let Some(ref mfa_storage) = self.mfa_storage {
447            let mfa_status = mfa_storage.get_mfa_status(user_id).await?;
448            Ok(mfa_status.map(|s| s.enabled).unwrap_or(false))
449        } else {
450            // No MFA storage configured, assume compliant
451            Ok(true)
452        }
453    }
454
455    /// Record a privileged action
456    #[allow(clippy::too_many_arguments)]
457    pub async fn record_action(
458        &self,
459        user_id: Uuid,
460        action_type: PrivilegedActionType,
461        resource: Option<String>,
462        details: Option<String>,
463        ip_address: Option<String>,
464        user_agent: Option<String>,
465        session_id: Option<String>,
466    ) -> Result<Uuid, Error> {
467        let action = PrivilegedAction {
468            action_id: Uuid::new_v4(),
469            user_id,
470            action_type,
471            resource,
472            details,
473            ip_address,
474            user_agent,
475            session_id,
476            timestamp: Utc::now(),
477        };
478
479        let mut actions = self.actions.write().await;
480        actions.push(action.clone());
481
482        // Emit security event for all privileged actions
483        // Use higher severity for sensitive actions
484        let event_type = if self.config.sensitive_actions.contains(&action_type) {
485            SecurityEventType::AuthzPrivilegeEscalation
486        } else {
487            SecurityEventType::AuthzAccessGranted
488        };
489
490        let event = SecurityEvent::new(event_type, None, None)
491            .with_actor(EventActor {
492                user_id: Some(action.user_id.to_string()),
493                username: None,
494                ip_address: action.ip_address.clone(),
495                user_agent: action.user_agent.clone(),
496            })
497            .with_target(EventTarget {
498                resource_type: Some(format!("privileged_action_{:?}", action_type)),
499                resource_id: Some(action.action_id.to_string()),
500                method: action.resource.clone(),
501            })
502            .with_outcome(EventOutcome {
503                success: true,
504                reason: action.details.clone(),
505            })
506            .with_metadata(
507                "action_type".to_string(),
508                serde_json::json!(format!("{:?}", action_type)),
509            )
510            .with_metadata(
511                "session_id".to_string(),
512                serde_json::json!(action.session_id.clone().unwrap_or_default()),
513            );
514
515        // Emit asynchronously to avoid blocking
516        emit_security_event_async(event);
517
518        Ok(action.action_id)
519    }
520
521    /// Start a privileged session
522    pub async fn start_session(
523        &self,
524        session_id: String,
525        user_id: Uuid,
526        role: PrivilegedRole,
527        ip_address: Option<String>,
528        user_agent: Option<String>,
529    ) -> Result<(), Error> {
530        // Check MFA compliance
531        if !self.check_mfa_compliance(user_id).await? && self.config.auto_suspend_no_mfa {
532            return Err(Error::Generic("MFA not enabled for privileged user".to_string()));
533        }
534
535        // Check concurrent session limit
536        let sessions = self.sessions.read().await;
537        let active_sessions =
538            sessions.values().filter(|s| s.user_id == user_id && s.is_active).count();
539
540        if active_sessions >= self.config.max_concurrent_sessions as usize {
541            return Err(Error::Generic("Maximum concurrent sessions reached".to_string()));
542        }
543        drop(sessions);
544
545        // Clone values before moving into session
546        let ip_address_clone = ip_address.clone();
547        let user_agent_clone = user_agent.clone();
548
549        let session = PrivilegedSession {
550            session_id: session_id.clone(),
551            user_id,
552            role,
553            started_at: Utc::now(),
554            last_activity: Utc::now(),
555            ip_address,
556            user_agent,
557            is_active: true,
558        };
559
560        let mut sessions = self.sessions.write().await;
561        sessions.insert(session_id.clone(), session);
562
563        // Emit security event for privileged session start
564        let event = SecurityEvent::new(SecurityEventType::AuthzPrivilegeEscalation, None, None)
565            .with_actor(EventActor {
566                user_id: Some(user_id.to_string()),
567                username: None,
568                ip_address: ip_address_clone,
569                user_agent: user_agent_clone,
570            })
571            .with_target(EventTarget {
572                resource_type: Some("privileged_session".to_string()),
573                resource_id: Some(session_id.clone()),
574                method: Some(format!("{:?}", role)),
575            })
576            .with_outcome(EventOutcome {
577                success: true,
578                reason: Some("Privileged session started".to_string()),
579            })
580            .with_metadata("role".to_string(), serde_json::json!(format!("{:?}", role)));
581
582        emit_security_event_async(event);
583
584        Ok(())
585    }
586
587    /// Update session activity
588    pub async fn update_session_activity(&self, session_id: &str) -> Result<(), Error> {
589        let mut sessions = self.sessions.write().await;
590        if let Some(session) = sessions.get_mut(session_id) {
591            session.last_activity = Utc::now();
592        }
593        Ok(())
594    }
595
596    /// End a privileged session
597    pub async fn end_session(&self, session_id: &str) -> Result<(), Error> {
598        let mut sessions = self.sessions.write().await;
599        if let Some(session) = sessions.get_mut(session_id) {
600            let user_id = session.user_id;
601            let role = session.role;
602            session.is_active = false;
603
604            // Emit security event for privileged session end
605            let event = SecurityEvent::new(SecurityEventType::AuthzAccessGranted, None, None)
606                .with_actor(EventActor {
607                    user_id: Some(user_id.to_string()),
608                    username: None,
609                    ip_address: session.ip_address.clone(),
610                    user_agent: session.user_agent.clone(),
611                })
612                .with_target(EventTarget {
613                    resource_type: Some("privileged_session".to_string()),
614                    resource_id: Some(session_id.to_string()),
615                    method: Some(format!("{:?}", role)),
616                })
617                .with_outcome(EventOutcome {
618                    success: true,
619                    reason: Some("Privileged session ended".to_string()),
620                })
621                .with_metadata("role".to_string(), serde_json::json!(format!("{:?}", role)))
622                .with_metadata(
623                    "duration_seconds".to_string(),
624                    serde_json::json!((Utc::now() - session.started_at).num_seconds()),
625                );
626
627            emit_security_event_async(event);
628        }
629        Ok(())
630    }
631
632    /// Check for expired sessions and clean them up
633    pub async fn cleanup_expired_sessions(&self) -> Result<Vec<String>, Error> {
634        let timeout = Duration::minutes(self.config.session_timeout_minutes as i64);
635        let now = Utc::now();
636        let mut expired = Vec::new();
637
638        let mut sessions = self.sessions.write().await;
639        for (session_id, session) in sessions.iter_mut() {
640            if session.is_active && (now - session.last_activity) > timeout {
641                session.is_active = false;
642                expired.push(session_id.clone());
643            }
644        }
645
646        Ok(expired)
647    }
648
649    /// Get all privileged actions for a user
650    pub async fn get_user_actions(&self, user_id: Uuid) -> Result<Vec<PrivilegedAction>, Error> {
651        let actions = self.actions.read().await;
652        Ok(actions.iter().filter(|a| a.user_id == user_id).cloned().collect())
653    }
654
655    /// Get all active privileged sessions
656    pub async fn get_active_sessions(&self) -> Result<Vec<PrivilegedSession>, Error> {
657        let sessions = self.sessions.read().await;
658        Ok(sessions.values().filter(|s| s.is_active).cloned().collect())
659    }
660
661    /// Get access request by ID
662    pub async fn get_request(
663        &self,
664        request_id: Uuid,
665    ) -> Result<Option<PrivilegedAccessRequest>, Error> {
666        let requests = self.requests.read().await;
667        Ok(requests.get(&request_id).cloned())
668    }
669
670    /// Get all requests for a user
671    pub async fn get_user_requests(
672        &self,
673        user_id: Uuid,
674    ) -> Result<Vec<PrivilegedAccessRequest>, Error> {
675        let requests = self.requests.read().await;
676        Ok(requests.values().filter(|r| r.user_id == user_id).cloned().collect())
677    }
678}
679
680// Required for Arc usage
681use std::sync::Arc;
682use tokio::sync::RwLock;
683
684#[cfg(test)]
685mod tests {
686    use super::*;
687
688    #[tokio::test]
689    async fn test_privileged_access_request() {
690        let manager = PrivilegedAccessManager::new(PrivilegedAccessConfig::default(), None, None);
691
692        let request = manager
693            .request_privileged_access(
694                Uuid::new_v4(),
695                PrivilegedRole::Admin,
696                "Required for system administration".to_string(),
697                Some("Manage production infrastructure".to_string()),
698                Some(Uuid::new_v4()),
699            )
700            .await
701            .unwrap();
702
703        assert_eq!(request.status, RequestStatus::PendingManager);
704        assert!(request.manager_approval.is_some());
705    }
706}