1use crate::config::AwsConfig;
2
3pub struct IamClient {
4 config: AwsConfig,
5}
6
7impl IamClient {
8 pub fn new(config: AwsConfig) -> Self {
9 Self { config }
10 }
11
12 pub async fn list_users(&self) -> Result<Vec<aws_sdk_iam::types::User>, String> {
13 self.config
14 .iam_client()
15 .list_users()
16 .into_paginator()
17 .items()
18 .send()
19 .collect::<Result<Vec<_>, _>>()
20 .await
21 .map_err(|e| format!("Failed to list users: {}", e))
22 }
23
24 pub async fn list_roles(&self) -> Result<Vec<aws_sdk_iam::types::Role>, String> {
25 self.config
26 .iam_client()
27 .list_roles()
28 .into_paginator()
29 .items()
30 .send()
31 .collect::<Result<Vec<_>, _>>()
32 .await
33 .map_err(|e| format!("Failed to list roles: {}", e))
34 }
35
36 pub async fn list_groups(&self) -> Result<Vec<aws_sdk_iam::types::Group>, String> {
37 self.config
38 .iam_client()
39 .list_groups()
40 .into_paginator()
41 .items()
42 .send()
43 .collect::<Result<Vec<_>, _>>()
44 .await
45 .map_err(|e| format!("Failed to list groups: {}", e))
46 }
47
48 pub async fn list_attached_role_policies(
49 &self,
50 role_name: &str,
51 ) -> Result<Vec<aws_sdk_iam::types::AttachedPolicy>, String> {
52 let client = self.config.iam_client();
53
54 let response = client
55 .list_attached_role_policies()
56 .role_name(role_name)
57 .send()
58 .await
59 .map_err(|e| format!("Failed to list attached role policies: {}", e))?;
60
61 Ok(response.attached_policies.unwrap_or_default())
62 }
63
64 pub async fn list_attached_group_policies(
65 &self,
66 group_name: &str,
67 ) -> Result<Vec<aws_sdk_iam::types::AttachedPolicy>, String> {
68 let client = self.config.iam_client();
69
70 let response = client
71 .list_attached_group_policies()
72 .group_name(group_name)
73 .send()
74 .await
75 .map_err(|e| format!("Failed to list attached group policies: {}", e))?;
76
77 Ok(response.attached_policies.unwrap_or_default())
78 }
79
80 pub async fn list_role_policies(&self, role_name: &str) -> Result<Vec<String>, String> {
81 let client = self.config.iam_client();
82
83 let response = client
84 .list_role_policies()
85 .role_name(role_name)
86 .send()
87 .await
88 .map_err(|e| format!("Failed to list role policies: {}", e))?;
89
90 Ok(response.policy_names)
91 }
92
93 pub async fn get_group(&self, group_name: &str) -> Result<usize, String> {
94 let client = self.config.iam_client();
95
96 let response = client
97 .get_group()
98 .group_name(group_name)
99 .send()
100 .await
101 .map_err(|e| format!("Failed to get group: {}", e))?;
102
103 Ok(response.users.len())
104 }
105
106 pub async fn get_group_users(
107 &self,
108 group_name: &str,
109 ) -> Result<Vec<aws_sdk_iam::types::User>, String> {
110 let client = self.config.iam_client();
111
112 let response = client
113 .get_group()
114 .group_name(group_name)
115 .send()
116 .await
117 .map_err(|e| format!("Failed to get group users: {}", e))?;
118
119 Ok(response.users)
120 }
121
122 pub async fn list_group_policies(&self, group_name: &str) -> Result<Vec<String>, String> {
123 let client = self.config.iam_client();
124
125 let response = client
126 .list_group_policies()
127 .group_name(group_name)
128 .send()
129 .await
130 .map_err(|e| format!("Failed to list group policies: {}", e))?;
131
132 Ok(response.policy_names)
133 }
134
135 pub async fn get_role_policy(
136 &self,
137 role_name: &str,
138 policy_name: &str,
139 ) -> Result<String, String> {
140 let client = self.config.iam_client();
141
142 let response = client
143 .get_role_policy()
144 .role_name(role_name)
145 .policy_name(policy_name)
146 .send()
147 .await
148 .map_err(|e| format!("Failed to get role policy: {}", e))?;
149
150 let policy_document = response.policy_document();
151
152 let decoded = percent_encoding::percent_decode_str(policy_document)
154 .decode_utf8()
155 .map_err(|e| format!("Failed to decode policy: {}", e))?;
156
157 let json: serde_json::Value = serde_json::from_str(&decoded)
158 .map_err(|e| format!("Failed to parse policy JSON: {}", e))?;
159
160 serde_json::to_string_pretty(&json)
161 .map_err(|e| format!("Failed to format policy JSON: {}", e))
162 }
163
164 pub async fn get_policy_version(&self, policy_arn: &str) -> Result<String, String> {
165 let client = self.config.iam_client();
166
167 let policy_response = client
169 .get_policy()
170 .policy_arn(policy_arn)
171 .send()
172 .await
173 .map_err(|e| format!("Failed to get policy: {}", e))?;
174
175 let default_version = policy_response
176 .policy()
177 .and_then(|p| p.default_version_id())
178 .ok_or_else(|| "No default version found".to_string())?;
179
180 let version_response = client
182 .get_policy_version()
183 .policy_arn(policy_arn)
184 .version_id(default_version)
185 .send()
186 .await
187 .map_err(|e| format!("Failed to get policy version: {}", e))?;
188
189 let policy_document = version_response
190 .policy_version()
191 .and_then(|v| v.document())
192 .ok_or_else(|| "No policy document found".to_string())?;
193
194 let decoded = percent_encoding::percent_decode_str(policy_document)
196 .decode_utf8()
197 .map_err(|e| format!("Failed to decode policy: {}", e))?;
198
199 let json: serde_json::Value = serde_json::from_str(&decoded)
200 .map_err(|e| format!("Failed to parse policy JSON: {}", e))?;
201
202 serde_json::to_string_pretty(&json)
203 .map_err(|e| format!("Failed to format policy JSON: {}", e))
204 }
205
206 pub async fn get_role(&self, role_name: &str) -> Result<String, String> {
207 let client = self.config.iam_client();
208
209 let response = client
210 .get_role()
211 .role_name(role_name)
212 .send()
213 .await
214 .map_err(|e| format!("Failed to get role: {}", e))?;
215
216 let assume_role_policy = response
217 .role()
218 .and_then(|r| r.assume_role_policy_document())
219 .ok_or_else(|| "No assume role policy document found".to_string())?;
220
221 let decoded = percent_encoding::percent_decode_str(assume_role_policy)
223 .decode_utf8()
224 .map_err(|e| format!("Failed to decode policy: {}", e))?;
225
226 let json: serde_json::Value = serde_json::from_str(&decoded)
227 .map_err(|e| format!("Failed to parse policy JSON: {}", e))?;
228
229 serde_json::to_string_pretty(&json)
230 .map_err(|e| format!("Failed to format policy JSON: {}", e))
231 }
232
233 pub async fn list_role_tags(&self, role_name: &str) -> Result<Vec<(String, String)>, String> {
234 let client = self.config.iam_client();
235
236 let response = client
237 .list_role_tags()
238 .role_name(role_name)
239 .send()
240 .await
241 .map_err(|e| format!("Failed to list role tags: {}", e))?;
242
243 Ok(response
244 .tags()
245 .iter()
246 .map(|t| (t.key().to_string(), t.value().to_string()))
247 .collect())
248 }
249
250 pub async fn list_user_tags(&self, user_name: &str) -> Result<Vec<(String, String)>, String> {
251 let client = self.config.iam_client();
252
253 let response = client
254 .list_user_tags()
255 .user_name(user_name)
256 .send()
257 .await
258 .map_err(|e| format!("Failed to list user tags: {}", e))?;
259
260 Ok(response
261 .tags()
262 .iter()
263 .map(|t| (t.key().to_string(), t.value().to_string()))
264 .collect())
265 }
266
267 pub async fn get_login_profile(&self, user_name: &str) -> Result<bool, String> {
268 let client = self.config.iam_client();
269
270 match client.get_login_profile().user_name(user_name).send().await {
271 Ok(_) => Ok(true),
272 Err(_) => Ok(false),
273 }
274 }
275
276 pub async fn list_access_keys(&self, user_name: &str) -> Result<usize, String> {
277 let client = self.config.iam_client();
278
279 let response = client
280 .list_access_keys()
281 .user_name(user_name)
282 .send()
283 .await
284 .map_err(|e| format!("Failed to list access keys: {}", e))?;
285
286 Ok(response.access_key_metadata().len())
287 }
288
289 pub async fn get_policy_metadata(&self, policy_arn: &str) -> Result<(String, String), String> {
291 let client = self.config.iam_client();
292
293 let resp = client
294 .get_policy()
295 .policy_arn(policy_arn)
296 .send()
297 .await
298 .map_err(|e| format!("Failed to get policy metadata: {}", e))?;
299
300 let policy = resp
301 .policy()
302 .ok_or_else(|| "No policy returned".to_string())?;
303
304 let fmt = |dt: Option<&aws_sdk_iam::primitives::DateTime>| -> String {
305 dt.map(|d| {
306 chrono::DateTime::from_timestamp(d.secs(), 0)
307 .unwrap_or_default()
308 .format("%Y-%m-%d %H:%M:%S (UTC)")
309 .to_string()
310 })
311 .unwrap_or_else(|| "-".to_string())
312 };
313
314 Ok((fmt(policy.create_date()), fmt(policy.update_date())))
315 }
316}