fakecloud_iam/
credential_resolver.rs1use std::sync::Arc;
10
11use fakecloud_core::auth::{CredentialResolver, Principal, PrincipalType, ResolvedCredential};
12
13use crate::state::SharedIamState;
14
15#[derive(Clone)]
19pub struct IamCredentialResolver {
20 state: SharedIamState,
21}
22
23impl IamCredentialResolver {
24 pub fn new(state: SharedIamState) -> Self {
25 Self { state }
26 }
27
28 pub fn shared(state: SharedIamState) -> Arc<dyn CredentialResolver> {
29 Arc::new(Self::new(state))
30 }
31}
32
33impl CredentialResolver for IamCredentialResolver {
34 fn resolve(&self, access_key_id: &str) -> Option<ResolvedCredential> {
35 let mut state = self.state.write();
36 let lookup = state.credential_secret(access_key_id)?;
37 let principal_type = PrincipalType::from_arn(&lookup.principal_arn);
42 Some(ResolvedCredential {
43 secret_access_key: lookup.secret_access_key,
44 session_token: lookup.session_token,
45 principal: Principal {
46 arn: lookup.principal_arn,
47 user_id: lookup.user_id,
48 account_id: lookup.account_id,
49 principal_type,
50 source_identity: None,
51 },
52 })
53 }
54}
55
56#[allow(dead_code)]
59fn _assert_impl<T: CredentialResolver>() {}
60const _: fn() = || {
61 _assert_impl::<IamCredentialResolver>();
62};
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67 use crate::state::{IamAccessKey, IamState, IamUser};
68 use chrono::Utc;
69 use parking_lot::RwLock;
70
71 #[test]
72 fn resolves_iam_user_secret_from_state() {
73 let mut state = IamState::new("123456789012");
74 state.users.insert(
75 "alice".to_string(),
76 IamUser {
77 user_name: "alice".into(),
78 user_id: "AIDAALICE".into(),
79 arn: "arn:aws:iam::123456789012:user/alice".into(),
80 path: "/".into(),
81 created_at: Utc::now(),
82 tags: Vec::new(),
83 permissions_boundary: None,
84 },
85 );
86 state.access_keys.insert(
87 "alice".to_string(),
88 vec![IamAccessKey {
89 access_key_id: "FKIAALICE".into(),
90 secret_access_key: "the-secret".into(),
91 user_name: "alice".into(),
92 status: "Active".into(),
93 created_at: Utc::now(),
94 }],
95 );
96 let resolver = IamCredentialResolver::new(Arc::new(RwLock::new(state)));
97 let resolved = resolver.resolve("FKIAALICE").unwrap();
98 assert_eq!(resolved.secret_access_key, "the-secret");
99 assert_eq!(
100 resolved.principal.arn,
101 "arn:aws:iam::123456789012:user/alice"
102 );
103 assert_eq!(resolved.principal.principal_type, PrincipalType::User);
104 assert_eq!(resolved.session_token, None);
105 }
106
107 #[test]
108 fn returns_none_for_unknown_akid() {
109 let state = IamState::new("123456789012");
110 let resolver = IamCredentialResolver::new(Arc::new(RwLock::new(state)));
111 assert!(resolver.resolve("FKIANONE").is_none());
112 }
113
114 #[test]
115 fn classifies_sts_assumed_role_principal() {
116 use crate::state::StsTempCredential;
117 let mut state = IamState::new("123456789012");
118 state.sts_temp_credentials.insert(
119 "FSIATEMP".to_string(),
120 StsTempCredential {
121 access_key_id: "FSIATEMP".into(),
122 secret_access_key: "temp-secret".into(),
123 session_token: "temp-token".into(),
124 principal_arn: "arn:aws:sts::123456789012:assumed-role/ops/session".into(),
125 user_id: "AROA:session".into(),
126 account_id: "123456789012".into(),
127 expiration: Utc::now() + chrono::Duration::minutes(30),
128 },
129 );
130 let resolver = IamCredentialResolver::new(Arc::new(RwLock::new(state)));
131 let resolved = resolver.resolve("FSIATEMP").unwrap();
132 assert_eq!(
133 resolved.principal.principal_type,
134 PrincipalType::AssumedRole
135 );
136 assert_eq!(resolved.session_token.as_deref(), Some("temp-token"));
137 }
138}