1use async_trait::async_trait;
18use chrono::{DateTime, Duration, Utc};
19use serde::{Deserialize, Serialize};
20use sha2::{Digest, Sha256};
21use std::collections::{HashMap, HashSet};
22use thiserror::Error;
23use tracing::info;
24use uuid::Uuid;
25
26use uvb_core::TenantId;
27
28#[derive(Debug, Error)]
30pub enum SecretAccessError {
31 #[error("Access denied: {0}")]
32 AccessDenied(String),
33
34 #[error("Service not authenticated")]
35 ServiceNotAuthenticated,
36
37 #[error("Service not authorized for secret: {secret_id}")]
38 ServiceNotAuthorized { secret_id: String },
39
40 #[error("Secret not found: {0}")]
41 SecretNotFound(String),
42
43 #[error("Invalid service identity")]
44 InvalidServiceIdentity,
45
46 #[error("Access grant expired at {0}")]
47 AccessGrantExpired(DateTime<Utc>),
48
49 #[error("Access request pending approval")]
50 AccessRequestPending,
51
52 #[error("Access request denied")]
53 AccessRequestDenied,
54
55 #[error("Invalid access policy")]
56 InvalidAccessPolicy,
57
58 #[error("Storage error: {0}")]
59 Storage(String),
60}
61
62#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
64pub struct ServiceIdentity {
65 pub service_id: String,
67
68 pub name: String,
70
71 pub service_type: ServiceType,
73
74 pub tenant_id: TenantId,
76
77 pub cert_fingerprint: Option<String>,
79
80 pub environment: Environment,
82
83 pub created_at: DateTime<Utc>,
85}
86
87impl ServiceIdentity {
88 pub fn new(
90 name: String,
91 service_type: ServiceType,
92 tenant_id: TenantId,
93 environment: Environment,
94 ) -> Self {
95 Self {
96 service_id: Uuid::new_v4().to_string(),
97 name,
98 service_type,
99 tenant_id,
100 cert_fingerprint: None,
101 environment,
102 created_at: Utc::now(),
103 }
104 }
105
106 pub fn with_cert_fingerprint(mut self, fingerprint: String) -> Self {
108 self.cert_fingerprint = Some(fingerprint);
109 self
110 }
111
112 pub fn identity_hash(&self) -> String {
114 let mut hasher = Sha256::new();
115 hasher.update(self.service_id.as_bytes());
116 hasher.update(self.name.as_bytes());
117 hasher.update(self.tenant_id.to_string().as_bytes());
118 hex::encode(hasher.finalize())
119 }
120}
121
122#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
124pub enum ServiceType {
125 AuthService,
127
128 MfaService,
130
131 UserService,
133
134 ApiGateway,
136
137 BackgroundWorker,
139
140 AdminService,
142
143 AnalyticsService,
145
146 Unknown,
148}
149
150impl ServiceType {
151 pub fn name(&self) -> &'static str {
153 match self {
154 Self::AuthService => "Authentication Service",
155 Self::MfaService => "MFA Service",
156 Self::UserService => "User Service",
157 Self::ApiGateway => "API Gateway",
158 Self::BackgroundWorker => "Background Worker",
159 Self::AdminService => "Admin Service",
160 Self::AnalyticsService => "Analytics Service",
161 Self::Unknown => "Unknown",
162 }
163 }
164
165 pub fn default_trust_level(&self) -> TrustLevel {
167 match self {
168 Self::AuthService | Self::MfaService => TrustLevel::High,
169 Self::ApiGateway | Self::UserService => TrustLevel::Medium,
170 Self::BackgroundWorker | Self::AdminService => TrustLevel::Medium,
171 Self::AnalyticsService => TrustLevel::Low,
172 Self::Unknown => TrustLevel::Untrusted,
173 }
174 }
175}
176
177#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
179pub enum Environment {
180 Production,
181 Staging,
182 Development,
183 Testing,
184}
185
186#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
188pub enum TrustLevel {
189 Untrusted = 0,
190 Low = 1,
191 Medium = 2,
192 High = 3,
193 Critical = 4,
194}
195
196#[derive(Clone, Debug, Serialize, Deserialize)]
198pub struct SecretScope {
199 pub secret_id: String,
201
202 pub name: String,
204
205 pub secret_type: SecretType,
207
208 pub tenant_id: TenantId,
210
211 pub required_trust_level: TrustLevel,
213
214 pub allowed_services: HashSet<String>,
216
217 pub denied_services: HashSet<String>,
219
220 pub allowed_environments: HashSet<Environment>,
222
223 pub require_approval: bool,
225
226 pub created_at: DateTime<Utc>,
228
229 pub updated_at: DateTime<Utc>,
231}
232
233impl SecretScope {
234 pub fn new(
236 name: String,
237 secret_type: SecretType,
238 tenant_id: TenantId,
239 required_trust_level: TrustLevel,
240 ) -> Self {
241 let now = Utc::now();
242 Self {
243 secret_id: Uuid::new_v4().to_string(),
244 name,
245 secret_type,
246 tenant_id,
247 required_trust_level,
248 allowed_services: HashSet::new(),
249 denied_services: HashSet::new(),
250 allowed_environments: HashSet::from_iter(vec![
251 Environment::Production,
252 Environment::Staging,
253 Environment::Development,
254 ]),
255 require_approval: false,
256 created_at: now,
257 updated_at: now,
258 }
259 }
260
261 pub fn allow_service(mut self, service_id: String) -> Self {
263 self.allowed_services.insert(service_id);
264 self.updated_at = Utc::now();
265 self
266 }
267
268 pub fn deny_service(mut self, service_id: String) -> Self {
270 self.denied_services.insert(service_id);
271 self.updated_at = Utc::now();
272 self
273 }
274
275 pub fn with_environments(mut self, environments: HashSet<Environment>) -> Self {
277 self.allowed_environments = environments;
278 self.updated_at = Utc::now();
279 self
280 }
281
282 pub fn with_approval_required(mut self, required: bool) -> Self {
284 self.require_approval = required;
285 self.updated_at = Utc::now();
286 self
287 }
288
289 pub fn is_service_allowed(&self, service_id: &str) -> bool {
291 if self.denied_services.contains(service_id) {
293 return false;
294 }
295
296 if self.allowed_services.is_empty() {
298 return true;
299 }
300
301 self.allowed_services.contains(service_id)
303 }
304
305 pub fn is_environment_allowed(&self, environment: Environment) -> bool {
307 self.allowed_environments.contains(&environment)
308 }
309}
310
311#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
313pub enum SecretType {
314 EncryptionKey,
316
317 TotpSecret,
319
320 ApiKey,
322
323 DatabasePassword,
325
326 JwtSigningKey,
328
329 OAuthClientSecret,
331
332 WebhookSecret,
334
335 Other,
337}
338
339impl SecretType {
340 pub fn name(&self) -> &'static str {
342 match self {
343 Self::EncryptionKey => "Encryption Key",
344 Self::TotpSecret => "TOTP Secret",
345 Self::ApiKey => "API Key",
346 Self::DatabasePassword => "Database Password",
347 Self::JwtSigningKey => "JWT Signing Key",
348 Self::OAuthClientSecret => "OAuth Client Secret",
349 Self::WebhookSecret => "Webhook Secret",
350 Self::Other => "Other",
351 }
352 }
353
354 pub fn default_trust_level(&self) -> TrustLevel {
356 match self {
357 Self::EncryptionKey | Self::TotpSecret | Self::JwtSigningKey => TrustLevel::High,
358 Self::DatabasePassword | Self::OAuthClientSecret => TrustLevel::Medium,
359 Self::ApiKey | Self::WebhookSecret => TrustLevel::Medium,
360 Self::Other => TrustLevel::Low,
361 }
362 }
363}
364
365#[derive(Clone, Debug, Serialize, Deserialize)]
367pub struct AccessGrant {
368 pub grant_id: String,
370
371 pub service_identity: ServiceIdentity,
373
374 pub secret_id: String,
376
377 pub granted_at: DateTime<Utc>,
379
380 pub expires_at: Option<DateTime<Utc>>,
382
383 pub granted_by: String,
385
386 pub reason: String,
388
389 pub access_count: u64,
391
392 pub last_accessed_at: Option<DateTime<Utc>>,
394}
395
396impl AccessGrant {
397 pub fn new(
399 service_identity: ServiceIdentity,
400 secret_id: String,
401 granted_by: String,
402 reason: String,
403 expires_at: Option<DateTime<Utc>>,
404 ) -> Self {
405 Self {
406 grant_id: Uuid::new_v4().to_string(),
407 service_identity,
408 secret_id,
409 granted_at: Utc::now(),
410 expires_at,
411 granted_by,
412 reason,
413 access_count: 0,
414 last_accessed_at: None,
415 }
416 }
417
418 pub fn is_expired(&self) -> bool {
420 if let Some(expires_at) = self.expires_at {
421 Utc::now() > expires_at
422 } else {
423 false
424 }
425 }
426
427 pub fn record_access(&mut self) {
429 self.access_count += 1;
430 self.last_accessed_at = Some(Utc::now());
431 }
432}
433
434#[derive(Clone, Debug, Serialize, Deserialize)]
436pub struct AccessRequest {
437 pub request_id: String,
439
440 pub service_identity: ServiceIdentity,
442
443 pub secret_id: String,
445
446 pub requested_at: DateTime<Utc>,
448
449 pub requested_by: String,
451
452 pub reason: String,
454
455 pub status: AccessRequestStatus,
457
458 pub reviewed_by: Option<String>,
460
461 pub reviewed_at: Option<DateTime<Utc>>,
463
464 pub review_notes: Option<String>,
466}
467
468impl AccessRequest {
469 pub fn new(
471 service_identity: ServiceIdentity,
472 secret_id: String,
473 requested_by: String,
474 reason: String,
475 ) -> Self {
476 Self {
477 request_id: Uuid::new_v4().to_string(),
478 service_identity,
479 secret_id,
480 requested_at: Utc::now(),
481 requested_by,
482 reason,
483 status: AccessRequestStatus::Pending,
484 reviewed_by: None,
485 reviewed_at: None,
486 review_notes: None,
487 }
488 }
489
490 pub fn approve(&mut self, reviewer: String, notes: Option<String>) {
492 self.status = AccessRequestStatus::Approved;
493 self.reviewed_by = Some(reviewer);
494 self.reviewed_at = Some(Utc::now());
495 self.review_notes = notes;
496 }
497
498 pub fn deny(&mut self, reviewer: String, notes: Option<String>) {
500 self.status = AccessRequestStatus::Denied;
501 self.reviewed_by = Some(reviewer);
502 self.reviewed_at = Some(Utc::now());
503 self.review_notes = notes;
504 }
505}
506
507#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
509pub enum AccessRequestStatus {
510 Pending,
511 Approved,
512 Denied,
513}
514
515#[derive(Clone, Debug, Serialize, Deserialize)]
517pub struct AccessAuditLog {
518 pub log_id: String,
520
521 pub service_identity: ServiceIdentity,
523
524 pub secret_id: String,
526
527 pub access_type: AccessType,
529
530 pub result: AccessResult,
532
533 pub timestamp: DateTime<Utc>,
535
536 pub ip_address: Option<String>,
538
539 pub context: HashMap<String, String>,
541}
542
543impl AccessAuditLog {
544 pub fn new(
546 service_identity: ServiceIdentity,
547 secret_id: String,
548 access_type: AccessType,
549 result: AccessResult,
550 ) -> Self {
551 Self {
552 log_id: Uuid::new_v4().to_string(),
553 service_identity,
554 secret_id,
555 access_type,
556 result,
557 timestamp: Utc::now(),
558 ip_address: None,
559 context: HashMap::new(),
560 }
561 }
562
563 pub fn with_context(mut self, key: String, value: String) -> Self {
565 self.context.insert(key, value);
566 self
567 }
568
569 pub fn with_ip(mut self, ip: String) -> Self {
571 self.ip_address = Some(ip);
572 self
573 }
574}
575
576#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
578pub enum AccessType {
579 Read,
580 Write,
581 Delete,
582 List,
583}
584
585#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
587pub enum AccessResult {
588 Allowed,
589 Denied,
590 Error,
591}
592
593#[async_trait]
595pub trait SecretAccessStorage: Send + Sync {
596 async fn store_service_identity(
598 &self,
599 identity: &ServiceIdentity,
600 ) -> Result<(), SecretAccessError>;
601
602 async fn get_service_identity(
604 &self,
605 service_id: &str,
606 ) -> Result<Option<ServiceIdentity>, SecretAccessError>;
607
608 async fn store_secret_scope(&self, scope: &SecretScope) -> Result<(), SecretAccessError>;
610
611 async fn get_secret_scope(
613 &self,
614 secret_id: &str,
615 ) -> Result<Option<SecretScope>, SecretAccessError>;
616
617 async fn store_access_grant(&self, grant: &AccessGrant) -> Result<(), SecretAccessError>;
619
620 async fn get_access_grant(
622 &self,
623 service_id: &str,
624 secret_id: &str,
625 ) -> Result<Option<AccessGrant>, SecretAccessError>;
626
627 async fn update_access_grant(&self, grant: &AccessGrant) -> Result<(), SecretAccessError>;
629
630 async fn store_access_request(&self, request: &AccessRequest) -> Result<(), SecretAccessError>;
632
633 async fn get_access_request(
635 &self,
636 request_id: &str,
637 ) -> Result<Option<AccessRequest>, SecretAccessError>;
638
639 async fn update_access_request(&self, request: &AccessRequest)
641 -> Result<(), SecretAccessError>;
642
643 async fn store_audit_log(&self, log: &AccessAuditLog) -> Result<(), SecretAccessError>;
645
646 async fn get_audit_logs(
648 &self,
649 secret_id: &str,
650 limit: usize,
651 ) -> Result<Vec<AccessAuditLog>, SecretAccessError>;
652}
653
654pub struct InMemorySecretAccessStorage {
656 service_identities: tokio::sync::RwLock<HashMap<String, ServiceIdentity>>,
657 secret_scopes: tokio::sync::RwLock<HashMap<String, SecretScope>>,
658 access_grants: tokio::sync::RwLock<HashMap<(String, String), AccessGrant>>,
659 access_requests: tokio::sync::RwLock<HashMap<String, AccessRequest>>,
660 audit_logs: tokio::sync::RwLock<Vec<AccessAuditLog>>,
661}
662
663impl InMemorySecretAccessStorage {
664 pub fn new() -> Self {
665 Self {
666 service_identities: tokio::sync::RwLock::new(HashMap::new()),
667 secret_scopes: tokio::sync::RwLock::new(HashMap::new()),
668 access_grants: tokio::sync::RwLock::new(HashMap::new()),
669 access_requests: tokio::sync::RwLock::new(HashMap::new()),
670 audit_logs: tokio::sync::RwLock::new(Vec::new()),
671 }
672 }
673}
674
675impl Default for InMemorySecretAccessStorage {
676 fn default() -> Self {
677 Self::new()
678 }
679}
680
681#[async_trait]
682impl SecretAccessStorage for InMemorySecretAccessStorage {
683 async fn store_service_identity(
684 &self,
685 identity: &ServiceIdentity,
686 ) -> Result<(), SecretAccessError> {
687 let mut identities = self.service_identities.write().await;
688 identities.insert(identity.service_id.clone(), identity.clone());
689 Ok(())
690 }
691
692 async fn get_service_identity(
693 &self,
694 service_id: &str,
695 ) -> Result<Option<ServiceIdentity>, SecretAccessError> {
696 let identities = self.service_identities.read().await;
697 Ok(identities.get(service_id).cloned())
698 }
699
700 async fn store_secret_scope(&self, scope: &SecretScope) -> Result<(), SecretAccessError> {
701 let mut scopes = self.secret_scopes.write().await;
702 scopes.insert(scope.secret_id.clone(), scope.clone());
703 Ok(())
704 }
705
706 async fn get_secret_scope(
707 &self,
708 secret_id: &str,
709 ) -> Result<Option<SecretScope>, SecretAccessError> {
710 let scopes = self.secret_scopes.read().await;
711 Ok(scopes.get(secret_id).cloned())
712 }
713
714 async fn store_access_grant(&self, grant: &AccessGrant) -> Result<(), SecretAccessError> {
715 let mut grants = self.access_grants.write().await;
716 let key = (
717 grant.service_identity.service_id.clone(),
718 grant.secret_id.clone(),
719 );
720 grants.insert(key, grant.clone());
721 Ok(())
722 }
723
724 async fn get_access_grant(
725 &self,
726 service_id: &str,
727 secret_id: &str,
728 ) -> Result<Option<AccessGrant>, SecretAccessError> {
729 let grants = self.access_grants.read().await;
730 let key = (service_id.to_string(), secret_id.to_string());
731 Ok(grants.get(&key).cloned())
732 }
733
734 async fn update_access_grant(&self, grant: &AccessGrant) -> Result<(), SecretAccessError> {
735 self.store_access_grant(grant).await
736 }
737
738 async fn store_access_request(&self, request: &AccessRequest) -> Result<(), SecretAccessError> {
739 let mut requests = self.access_requests.write().await;
740 requests.insert(request.request_id.clone(), request.clone());
741 Ok(())
742 }
743
744 async fn get_access_request(
745 &self,
746 request_id: &str,
747 ) -> Result<Option<AccessRequest>, SecretAccessError> {
748 let requests = self.access_requests.read().await;
749 Ok(requests.get(request_id).cloned())
750 }
751
752 async fn update_access_request(
753 &self,
754 request: &AccessRequest,
755 ) -> Result<(), SecretAccessError> {
756 self.store_access_request(request).await
757 }
758
759 async fn store_audit_log(&self, log: &AccessAuditLog) -> Result<(), SecretAccessError> {
760 let mut logs = self.audit_logs.write().await;
761 logs.push(log.clone());
762 Ok(())
763 }
764
765 async fn get_audit_logs(
766 &self,
767 secret_id: &str,
768 limit: usize,
769 ) -> Result<Vec<AccessAuditLog>, SecretAccessError> {
770 let logs = self.audit_logs.read().await;
771 let mut filtered: Vec<_> = logs
772 .iter()
773 .filter(|log| log.secret_id == secret_id)
774 .cloned()
775 .collect();
776 filtered.sort_by_key(|a| std::cmp::Reverse(a.timestamp));
777 Ok(filtered.into_iter().take(limit).collect())
778 }
779}
780
781pub struct SecretAccessControlManager<S: SecretAccessStorage> {
783 storage: S,
784}
785
786impl<S: SecretAccessStorage> SecretAccessControlManager<S> {
787 pub fn new(storage: S) -> Self {
789 Self { storage }
790 }
791
792 pub async fn register_service(
794 &self,
795 identity: ServiceIdentity,
796 ) -> Result<ServiceIdentity, SecretAccessError> {
797 self.storage.store_service_identity(&identity).await?;
798 info!(
799 "Registered service {} ({})",
800 identity.name, identity.service_id
801 );
802 Ok(identity)
803 }
804
805 pub async fn create_secret_scope(
807 &self,
808 scope: SecretScope,
809 ) -> Result<SecretScope, SecretAccessError> {
810 self.storage.store_secret_scope(&scope).await?;
811 info!("Created secret scope {} ({})", scope.name, scope.secret_id);
812 Ok(scope)
813 }
814
815 pub async fn check_access(
817 &self,
818 service_id: &str,
819 secret_id: &str,
820 access_type: AccessType,
821 ) -> Result<(), SecretAccessError> {
822 let service = self
824 .storage
825 .get_service_identity(service_id)
826 .await?
827 .ok_or(SecretAccessError::ServiceNotAuthenticated)?;
828
829 let scope = self
831 .storage
832 .get_secret_scope(secret_id)
833 .await?
834 .ok_or_else(|| SecretAccessError::SecretNotFound(secret_id.to_string()))?;
835
836 if !scope.is_service_allowed(service_id) {
838 self.log_access(
839 service.clone(),
840 secret_id.to_string(),
841 access_type,
842 AccessResult::Denied,
843 )
844 .await?;
845 return Err(SecretAccessError::ServiceNotAuthorized {
846 secret_id: secret_id.to_string(),
847 });
848 }
849
850 if !scope.is_environment_allowed(service.environment) {
852 self.log_access(
853 service.clone(),
854 secret_id.to_string(),
855 access_type,
856 AccessResult::Denied,
857 )
858 .await?;
859 return Err(SecretAccessError::AccessDenied(format!(
860 "Environment {:?} not allowed",
861 service.environment
862 )));
863 }
864
865 let service_trust = service.service_type.default_trust_level();
867 if service_trust < scope.required_trust_level {
868 self.log_access(
869 service.clone(),
870 secret_id.to_string(),
871 access_type,
872 AccessResult::Denied,
873 )
874 .await?;
875 return Err(SecretAccessError::AccessDenied(format!(
876 "Insufficient trust level: {:?} < {:?}",
877 service_trust, scope.required_trust_level
878 )));
879 }
880
881 if let Some(mut grant) = self.storage.get_access_grant(service_id, secret_id).await? {
883 if grant.is_expired() {
884 self.log_access(
885 service.clone(),
886 secret_id.to_string(),
887 access_type,
888 AccessResult::Denied,
889 )
890 .await?;
891 return Err(SecretAccessError::AccessGrantExpired(
892 grant.expires_at.unwrap(),
893 ));
894 }
895
896 grant.record_access();
898 self.storage.update_access_grant(&grant).await?;
899 } else if scope.require_approval {
900 self.log_access(
902 service.clone(),
903 secret_id.to_string(),
904 access_type,
905 AccessResult::Denied,
906 )
907 .await?;
908 return Err(SecretAccessError::AccessDenied(
909 "Approval required for this secret".to_string(),
910 ));
911 }
912
913 self.log_access(
915 service,
916 secret_id.to_string(),
917 access_type,
918 AccessResult::Allowed,
919 )
920 .await?;
921
922 info!(
923 "Access granted: service {} -> secret {}",
924 service_id, secret_id
925 );
926 Ok(())
927 }
928
929 pub async fn grant_access(
931 &self,
932 service_id: &str,
933 secret_id: &str,
934 granted_by: String,
935 reason: String,
936 expires_in_seconds: Option<i64>,
937 ) -> Result<AccessGrant, SecretAccessError> {
938 let service = self
939 .storage
940 .get_service_identity(service_id)
941 .await?
942 .ok_or(SecretAccessError::ServiceNotAuthenticated)?;
943
944 let expires_at = expires_in_seconds.map(|secs| Utc::now() + Duration::seconds(secs));
945
946 let grant = AccessGrant::new(
947 service,
948 secret_id.to_string(),
949 granted_by,
950 reason,
951 expires_at,
952 );
953
954 self.storage.store_access_grant(&grant).await?;
955 info!(
956 "Access granted: service {} -> secret {}",
957 service_id, secret_id
958 );
959 Ok(grant)
960 }
961
962 pub async fn request_access(
964 &self,
965 service_id: &str,
966 secret_id: &str,
967 requested_by: String,
968 reason: String,
969 ) -> Result<AccessRequest, SecretAccessError> {
970 let service = self
971 .storage
972 .get_service_identity(service_id)
973 .await?
974 .ok_or(SecretAccessError::ServiceNotAuthenticated)?;
975
976 let request = AccessRequest::new(service, secret_id.to_string(), requested_by, reason);
977
978 self.storage.store_access_request(&request).await?;
979 info!(
980 "Access requested: service {} -> secret {}",
981 service_id, secret_id
982 );
983 Ok(request)
984 }
985
986 pub async fn approve_request(
988 &self,
989 request_id: &str,
990 reviewer: String,
991 notes: Option<String>,
992 expires_in_seconds: Option<i64>,
993 ) -> Result<AccessGrant, SecretAccessError> {
994 let mut request = self
995 .storage
996 .get_access_request(request_id)
997 .await?
998 .ok_or_else(|| SecretAccessError::Storage("Access request not found".to_string()))?;
999
1000 request.approve(reviewer.clone(), notes);
1001 self.storage.update_access_request(&request).await?;
1002
1003 let grant = self
1005 .grant_access(
1006 &request.service_identity.service_id,
1007 &request.secret_id,
1008 reviewer,
1009 request.reason.clone(),
1010 expires_in_seconds,
1011 )
1012 .await?;
1013
1014 info!("Access request approved: {}", request_id);
1015 Ok(grant)
1016 }
1017
1018 pub async fn deny_request(
1020 &self,
1021 request_id: &str,
1022 reviewer: String,
1023 notes: Option<String>,
1024 ) -> Result<(), SecretAccessError> {
1025 let mut request = self
1026 .storage
1027 .get_access_request(request_id)
1028 .await?
1029 .ok_or_else(|| SecretAccessError::Storage("Access request not found".to_string()))?;
1030
1031 request.deny(reviewer, notes);
1032 self.storage.update_access_request(&request).await?;
1033
1034 info!("Access request denied: {}", request_id);
1035 Ok(())
1036 }
1037
1038 async fn log_access(
1040 &self,
1041 service: ServiceIdentity,
1042 secret_id: String,
1043 access_type: AccessType,
1044 result: AccessResult,
1045 ) -> Result<(), SecretAccessError> {
1046 let log = AccessAuditLog::new(service, secret_id, access_type, result);
1047 self.storage.store_audit_log(&log).await?;
1048 Ok(())
1049 }
1050
1051 pub async fn get_audit_logs(
1053 &self,
1054 secret_id: &str,
1055 limit: usize,
1056 ) -> Result<Vec<AccessAuditLog>, SecretAccessError> {
1057 self.storage.get_audit_logs(secret_id, limit).await
1058 }
1059}
1060
1061#[cfg(test)]
1062mod tests {
1063 use super::*;
1064
1065 #[tokio::test]
1066 async fn test_service_registration() {
1067 let storage = InMemorySecretAccessStorage::new();
1068 let manager = SecretAccessControlManager::new(storage);
1069
1070 let identity = ServiceIdentity::new(
1071 "mfa-service".to_string(),
1072 ServiceType::MfaService,
1073 TenantId::new("test_tenant"),
1074 Environment::Production,
1075 );
1076
1077 let registered = manager.register_service(identity.clone()).await.unwrap();
1078 assert_eq!(registered.service_id, identity.service_id);
1079 }
1080
1081 #[tokio::test]
1082 async fn test_access_control() {
1083 let storage = InMemorySecretAccessStorage::new();
1084 let manager = SecretAccessControlManager::new(storage);
1085
1086 let service = ServiceIdentity::new(
1088 "mfa-service".to_string(),
1089 ServiceType::MfaService,
1090 TenantId::new("test_tenant"),
1091 Environment::Production,
1092 );
1093 manager.register_service(service.clone()).await.unwrap();
1094
1095 let scope = SecretScope::new(
1097 "totp-master-key".to_string(),
1098 SecretType::TotpSecret,
1099 TenantId::new("test_tenant"),
1100 TrustLevel::High,
1101 )
1102 .allow_service(service.service_id.clone());
1103
1104 manager.create_secret_scope(scope.clone()).await.unwrap();
1105
1106 assert!(manager
1108 .check_access(&service.service_id, &scope.secret_id, AccessType::Read)
1109 .await
1110 .is_ok());
1111 }
1112
1113 #[tokio::test]
1114 async fn test_access_denied() {
1115 let storage = InMemorySecretAccessStorage::new();
1116 let manager = SecretAccessControlManager::new(storage);
1117
1118 let service = ServiceIdentity::new(
1120 "analytics-service".to_string(),
1121 ServiceType::AnalyticsService,
1122 TenantId::new("test_tenant"),
1123 Environment::Production,
1124 );
1125 manager.register_service(service.clone()).await.unwrap();
1126
1127 let scope = SecretScope::new(
1129 "totp-master-key".to_string(),
1130 SecretType::TotpSecret,
1131 TenantId::new("test_tenant"),
1132 TrustLevel::High, );
1134
1135 manager.create_secret_scope(scope.clone()).await.unwrap();
1136
1137 assert!(manager
1139 .check_access(&service.service_id, &scope.secret_id, AccessType::Read)
1140 .await
1141 .is_err());
1142 }
1143
1144 #[tokio::test]
1145 async fn test_access_request_workflow() {
1146 let storage = InMemorySecretAccessStorage::new();
1147 let manager = SecretAccessControlManager::new(storage);
1148
1149 let service = ServiceIdentity::new(
1151 "api-gateway".to_string(),
1152 ServiceType::ApiGateway,
1153 TenantId::new("test_tenant"),
1154 Environment::Production,
1155 );
1156 manager.register_service(service.clone()).await.unwrap();
1157
1158 let scope = SecretScope::new(
1160 "jwt-signing-key".to_string(),
1161 SecretType::JwtSigningKey,
1162 TenantId::new("test_tenant"),
1163 TrustLevel::Medium,
1164 )
1165 .with_approval_required(true);
1166
1167 manager.create_secret_scope(scope.clone()).await.unwrap();
1168
1169 let request = manager
1171 .request_access(
1172 &service.service_id,
1173 &scope.secret_id,
1174 "dev@example.com".to_string(),
1175 "Need to sign JWTs for API".to_string(),
1176 )
1177 .await
1178 .unwrap();
1179
1180 let grant = manager
1182 .approve_request(
1183 &request.request_id,
1184 "admin@example.com".to_string(),
1185 Some("Approved for production use".to_string()),
1186 Some(86400), )
1188 .await
1189 .unwrap();
1190
1191 assert!(!grant.is_expired());
1192
1193 assert!(manager
1195 .check_access(&service.service_id, &scope.secret_id, AccessType::Read)
1196 .await
1197 .is_ok());
1198 }
1199}