1use base64::Engine;
2
3use crate::state::{IamAccessKey, IamPolicy, IamRole, IamUser};
4
5fn xml_escape(s: &str) -> String {
6 s.replace('&', "&")
7 .replace('<', "<")
8 .replace('>', ">")
9 .replace('"', """)
10}
11
12fn url_encode_policy(s: &str) -> String {
14 let mut result = String::new();
15 for byte in s.bytes() {
16 match byte {
17 b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => {
18 result.push(byte as char);
19 }
20 _ => {
21 use std::fmt::Write;
22 write!(result, "%{:02X}", byte).unwrap();
23 }
24 }
25 }
26 result
27}
28
29fn tags_xml(tags: &[crate::state::Tag]) -> String {
30 if tags.is_empty() {
31 return String::new();
32 }
33 tags.iter()
34 .map(|t| {
35 format!(
36 " <member>\n <Key>{}</Key>\n <Value>{}</Value>\n </member>",
37 xml_escape(&t.key),
38 xml_escape(&t.value)
39 )
40 })
41 .collect::<Vec<_>>()
42 .join("\n")
43}
44
45fn user_xml(user: &IamUser) -> String {
46 let tags_section = if user.tags.is_empty() {
47 String::new()
48 } else {
49 let tags_members = tags_xml(&user.tags);
50 format!("\n <Tags>\n{tags_members}\n </Tags>")
51 };
52
53 let pb_section = user
54 .permissions_boundary
55 .as_ref()
56 .map(|pb| {
57 format!(
58 "\n <PermissionsBoundary>\n <PermissionsBoundaryType>PermissionsBoundaryPolicy</PermissionsBoundaryType>\n <PermissionsBoundaryArn>{pb}</PermissionsBoundaryArn>\n </PermissionsBoundary>"
59 )
60 })
61 .unwrap_or_default();
62
63 format!(
64 r#" <User>
65 <Path>{path}</Path>
66 <UserName>{name}</UserName>
67 <UserId>{id}</UserId>
68 <Arn>{arn}</Arn>
69 <CreateDate>{date}</CreateDate>{tags_section}{pb_section}
70 </User>"#,
71 path = user.path,
72 name = user.user_name,
73 id = user.user_id,
74 arn = user.arn,
75 date = user.created_at.format("%Y-%m-%dT%H:%M:%SZ"),
76 )
77}
78
79fn role_xml(role: &IamRole) -> String {
80 let tags_section = if role.tags.is_empty() {
81 String::new()
82 } else {
83 let tags_members = tags_xml(&role.tags);
84 format!("\n <Tags>\n{tags_members}\n </Tags>")
85 };
86
87 let pb_section = role
88 .permissions_boundary
89 .as_ref()
90 .map(|pb| {
91 format!(
92 "\n <PermissionsBoundary>\n <PermissionsBoundaryType>PermissionsBoundaryPolicy</PermissionsBoundaryType>\n <PermissionsBoundaryArn>{pb}</PermissionsBoundaryArn>\n </PermissionsBoundary>"
93 )
94 })
95 .unwrap_or_default();
96
97 let description_section = match &role.description {
98 Some(desc) => format!("\n <Description>{}</Description>", xml_escape(desc)),
99 None => String::new(),
100 };
101
102 format!(
103 r#" <Role>
104 <Path>{path}</Path>
105 <RoleName>{name}</RoleName>
106 <RoleId>{id}</RoleId>
107 <Arn>{arn}</Arn>
108 <CreateDate>{date}</CreateDate>
109 <AssumeRolePolicyDocument>{policy}</AssumeRolePolicyDocument>{description_section}
110 <MaxSessionDuration>{max_session}</MaxSessionDuration>
111 <RoleLastUsed/>{tags_section}{pb_section}
112 </Role>"#,
113 path = role.path,
114 name = role.role_name,
115 id = role.role_id,
116 arn = role.arn,
117 date = role.created_at.format("%Y-%m-%dT%H:%M:%SZ"),
118 policy = url_encode_policy(&role.assume_role_policy_document),
119 max_session = role.max_session_duration,
120 )
121}
122
123pub fn create_user_response(user: &IamUser, request_id: &str) -> String {
124 let user_xml = user_xml(user);
125 format!(
126 r#"<?xml version="1.0" encoding="UTF-8"?>
127<CreateUserResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
128 <CreateUserResult>
129{user_xml}
130 </CreateUserResult>
131 <ResponseMetadata>
132 <RequestId>{request_id}</RequestId>
133 </ResponseMetadata>
134</CreateUserResponse>"#,
135 )
136}
137
138pub fn get_user_response(user: &IamUser, request_id: &str) -> String {
139 let user_xml = user_xml(user);
140 format!(
141 r#"<?xml version="1.0" encoding="UTF-8"?>
142<GetUserResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
143 <GetUserResult>
144{user_xml}
145 </GetUserResult>
146 <ResponseMetadata>
147 <RequestId>{request_id}</RequestId>
148 </ResponseMetadata>
149</GetUserResponse>"#,
150 )
151}
152
153pub fn list_users_response(users: &[IamUser], request_id: &str) -> String {
154 let members: String = users
155 .iter()
156 .map(|u| {
157 format!(
159 r#" <member>
160 <Path>{path}</Path>
161 <UserName>{name}</UserName>
162 <UserId>{id}</UserId>
163 <Arn>{arn}</Arn>
164 <CreateDate>{date}</CreateDate>
165 </member>"#,
166 path = u.path,
167 name = u.user_name,
168 id = u.user_id,
169 arn = u.arn,
170 date = u.created_at.format("%Y-%m-%dT%H:%M:%SZ"),
171 )
172 })
173 .collect::<Vec<_>>()
174 .join("\n");
175
176 format!(
177 r#"<?xml version="1.0" encoding="UTF-8"?>
178<ListUsersResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
179 <ListUsersResult>
180 <IsTruncated>false</IsTruncated>
181 <Users>
182{members}
183 </Users>
184 </ListUsersResult>
185 <ResponseMetadata>
186 <RequestId>{request_id}</RequestId>
187 </ResponseMetadata>
188</ListUsersResponse>"#,
189 )
190}
191
192pub fn create_access_key_response(key: &IamAccessKey, request_id: &str) -> String {
193 format!(
194 r#"<?xml version="1.0" encoding="UTF-8"?>
195<CreateAccessKeyResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
196 <CreateAccessKeyResult>
197 <AccessKey>
198 <UserName>{user}</UserName>
199 <AccessKeyId>{key_id}</AccessKeyId>
200 <Status>{status}</Status>
201 <SecretAccessKey>{secret}</SecretAccessKey>
202 <CreateDate>{date}</CreateDate>
203 </AccessKey>
204 </CreateAccessKeyResult>
205 <ResponseMetadata>
206 <RequestId>{request_id}</RequestId>
207 </ResponseMetadata>
208</CreateAccessKeyResponse>"#,
209 user = key.user_name,
210 key_id = key.access_key_id,
211 status = key.status,
212 secret = key.secret_access_key,
213 date = key.created_at.format("%Y-%m-%dT%H:%M:%SZ"),
214 )
215}
216
217pub fn list_access_keys_response(
218 keys: &[IamAccessKey],
219 user_name: &str,
220 request_id: &str,
221) -> String {
222 let members: String = keys
223 .iter()
224 .map(|k| {
225 format!(
226 r#" <member>
227 <UserName>{user}</UserName>
228 <AccessKeyId>{key_id}</AccessKeyId>
229 <Status>{status}</Status>
230 <CreateDate>{date}</CreateDate>
231 </member>"#,
232 user = k.user_name,
233 key_id = k.access_key_id,
234 status = k.status,
235 date = k.created_at.format("%Y-%m-%dT%H:%M:%SZ"),
236 )
237 })
238 .collect::<Vec<_>>()
239 .join("\n");
240
241 format!(
242 r#"<?xml version="1.0" encoding="UTF-8"?>
243<ListAccessKeysResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
244 <ListAccessKeysResult>
245 <UserName>{user_name}</UserName>
246 <IsTruncated>false</IsTruncated>
247 <AccessKeyMetadata>
248{members}
249 </AccessKeyMetadata>
250 </ListAccessKeysResult>
251 <ResponseMetadata>
252 <RequestId>{request_id}</RequestId>
253 </ResponseMetadata>
254</ListAccessKeysResponse>"#,
255 )
256}
257
258pub fn create_role_response(role: &IamRole, request_id: &str) -> String {
259 let role_xml = role_xml(role);
260 format!(
261 r#"<?xml version="1.0" encoding="UTF-8"?>
262<CreateRoleResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
263 <CreateRoleResult>
264{role_xml}
265 </CreateRoleResult>
266 <ResponseMetadata>
267 <RequestId>{request_id}</RequestId>
268 </ResponseMetadata>
269</CreateRoleResponse>"#,
270 )
271}
272
273pub fn get_role_response(role: &IamRole, request_id: &str) -> String {
274 let role_xml = role_xml(role);
275 format!(
276 r#"<?xml version="1.0" encoding="UTF-8"?>
277<GetRoleResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
278 <GetRoleResult>
279{role_xml}
280 </GetRoleResult>
281 <ResponseMetadata>
282 <RequestId>{request_id}</RequestId>
283 </ResponseMetadata>
284</GetRoleResponse>"#,
285 )
286}
287
288pub fn list_roles_response(roles: &[IamRole], request_id: &str) -> String {
289 let members: String = roles
290 .iter()
291 .map(|r| {
292 let description_section = match &r.description {
294 Some(desc) => format!("\n <Description>{}</Description>", xml_escape(desc)),
295 None => String::new(),
296 };
297 format!(
298 r#" <member>
299 <Path>{path}</Path>
300 <RoleName>{name}</RoleName>
301 <RoleId>{id}</RoleId>
302 <Arn>{arn}</Arn>
303 <CreateDate>{date}</CreateDate>
304 <AssumeRolePolicyDocument>{policy}</AssumeRolePolicyDocument>{description_section}
305 <MaxSessionDuration>{max_session}</MaxSessionDuration>
306 </member>"#,
307 path = r.path,
308 name = r.role_name,
309 id = r.role_id,
310 arn = r.arn,
311 date = r.created_at.format("%Y-%m-%dT%H:%M:%SZ"),
312 policy = url_encode_policy(&r.assume_role_policy_document),
313 max_session = r.max_session_duration,
314 )
315 })
316 .collect::<Vec<_>>()
317 .join("\n");
318
319 format!(
320 r#"<?xml version="1.0" encoding="UTF-8"?>
321<ListRolesResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
322 <ListRolesResult>
323 <IsTruncated>false</IsTruncated>
324 <Roles>
325{members}
326 </Roles>
327 </ListRolesResult>
328 <ResponseMetadata>
329 <RequestId>{request_id}</RequestId>
330 </ResponseMetadata>
331</ListRolesResponse>"#,
332 )
333}
334
335pub fn list_roles_response_paginated(
336 roles: &[IamRole],
337 is_truncated: bool,
338 marker: Option<&str>,
339 request_id: &str,
340) -> String {
341 let members: String = roles
342 .iter()
343 .map(|r| {
344 let description_section = match &r.description {
346 Some(desc) => format!("\n <Description>{}</Description>", xml_escape(desc)),
347 None => String::new(),
348 };
349 format!(
350 r#" <member>
351 <Path>{path}</Path>
352 <RoleName>{name}</RoleName>
353 <RoleId>{id}</RoleId>
354 <Arn>{arn}</Arn>
355 <CreateDate>{date}</CreateDate>
356 <AssumeRolePolicyDocument>{policy}</AssumeRolePolicyDocument>{description_section}
357 <MaxSessionDuration>{max_session}</MaxSessionDuration>
358 </member>"#,
359 path = r.path,
360 name = r.role_name,
361 id = r.role_id,
362 arn = r.arn,
363 date = r.created_at.format("%Y-%m-%dT%H:%M:%SZ"),
364 policy = url_encode_policy(&r.assume_role_policy_document),
365 max_session = r.max_session_duration,
366 )
367 })
368 .collect::<Vec<_>>()
369 .join("\n");
370
371 let marker_section = if let Some(m) = marker {
372 format!("\n <Marker>{}</Marker>", xml_escape(m))
373 } else {
374 String::new()
375 };
376
377 format!(
378 r#"<?xml version="1.0" encoding="UTF-8"?>
379<ListRolesResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
380 <ListRolesResult>
381 <IsTruncated>{is_truncated}</IsTruncated>{marker_section}
382 <Roles>
383{members}
384 </Roles>
385 </ListRolesResult>
386 <ResponseMetadata>
387 <RequestId>{request_id}</RequestId>
388 </ResponseMetadata>
389</ListRolesResponse>"#,
390 )
391}
392
393pub fn create_policy_response(policy: &IamPolicy, request_id: &str) -> String {
394 let tags_section = if policy.tags.is_empty() {
395 String::new()
396 } else {
397 let tags_members = tags_xml(&policy.tags);
398 format!("\n <Tags>\n{tags_members}\n </Tags>")
399 };
400
401 format!(
402 r#"<?xml version="1.0" encoding="UTF-8"?>
403<CreatePolicyResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
404 <CreatePolicyResult>
405 <Policy>
406 <PolicyName>{name}</PolicyName>
407 <PolicyId>{id}</PolicyId>
408 <Arn>{arn}</Arn>
409 <Path>{path}</Path>
410 <DefaultVersionId>{default_version}</DefaultVersionId>
411 <AttachmentCount>{attachment_count}</AttachmentCount>
412 <IsAttachable>true</IsAttachable>
413 <CreateDate>{date}</CreateDate>{tags_section}
414 </Policy>
415 </CreatePolicyResult>
416 <ResponseMetadata>
417 <RequestId>{request_id}</RequestId>
418 </ResponseMetadata>
419</CreatePolicyResponse>"#,
420 name = policy.policy_name,
421 id = policy.policy_id,
422 arn = policy.arn,
423 path = policy.path,
424 default_version = policy.default_version_id,
425 attachment_count = policy.attachment_count,
426 date = policy.created_at.format("%Y-%m-%dT%H:%M:%SZ"),
427 )
428}
429
430pub fn list_policies_response(policies: &[IamPolicy], request_id: &str) -> String {
431 let members: String = policies
432 .iter()
433 .map(|p| {
434 format!(
436 r#" <member>
437 <PolicyName>{name}</PolicyName>
438 <PolicyId>{id}</PolicyId>
439 <Arn>{arn}</Arn>
440 <Path>{path}</Path>
441 <DefaultVersionId>{default_version}</DefaultVersionId>
442 <AttachmentCount>{attachment_count}</AttachmentCount>
443 <IsAttachable>true</IsAttachable>
444 <CreateDate>{date}</CreateDate>
445 </member>"#,
446 name = p.policy_name,
447 id = p.policy_id,
448 arn = p.arn,
449 path = p.path,
450 default_version = p.default_version_id,
451 attachment_count = p.attachment_count,
452 date = p.created_at.format("%Y-%m-%dT%H:%M:%SZ"),
453 )
454 })
455 .collect::<Vec<_>>()
456 .join("\n");
457
458 format!(
459 r#"<?xml version="1.0" encoding="UTF-8"?>
460<ListPoliciesResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
461 <ListPoliciesResult>
462 <IsTruncated>false</IsTruncated>
463 <Policies>
464{members}
465 </Policies>
466 </ListPoliciesResult>
467 <ResponseMetadata>
468 <RequestId>{request_id}</RequestId>
469 </ResponseMetadata>
470</ListPoliciesResponse>"#,
471 )
472}
473
474pub fn get_policy_response(policy: &IamPolicy, request_id: &str) -> String {
475 let tags_section = if policy.tags.is_empty() {
477 "\n <Tags/>".to_string()
478 } else {
479 let tags_members = tags_xml(&policy.tags);
480 format!("\n <Tags>\n{tags_members}\n </Tags>")
481 };
482
483 let description_section = if policy.description.is_empty() {
484 String::new()
485 } else {
486 format!(
487 "\n <Description>{}</Description>",
488 xml_escape(&policy.description)
489 )
490 };
491
492 format!(
493 r#"<?xml version="1.0" encoding="UTF-8"?>
494<GetPolicyResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
495 <GetPolicyResult>
496 <Policy>
497 <PolicyName>{name}</PolicyName>
498 <PolicyId>{id}</PolicyId>
499 <Arn>{arn}</Arn>
500 <Path>{path}</Path>
501 <DefaultVersionId>{default_version}</DefaultVersionId>
502 <AttachmentCount>{attachment_count}</AttachmentCount>
503 <IsAttachable>true</IsAttachable>
504 <CreateDate>{date}</CreateDate>{description_section}{tags_section}
505 </Policy>
506 </GetPolicyResult>
507 <ResponseMetadata>
508 <RequestId>{request_id}</RequestId>
509 </ResponseMetadata>
510</GetPolicyResponse>"#,
511 name = policy.policy_name,
512 id = policy.policy_id,
513 arn = policy.arn,
514 path = policy.path,
515 default_version = policy.default_version_id,
516 attachment_count = policy.attachment_count,
517 date = policy.created_at.format("%Y-%m-%dT%H:%M:%SZ"),
518 )
519}
520
521pub fn list_role_policies_response(policy_names: &[String], request_id: &str) -> String {
522 let members: String = policy_names
523 .iter()
524 .map(|name| format!(" <member>{name}</member>"))
525 .collect::<Vec<_>>()
526 .join("\n");
527
528 format!(
529 r#"<?xml version="1.0" encoding="UTF-8"?>
530<ListRolePoliciesResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
531 <ListRolePoliciesResult>
532 <IsTruncated>false</IsTruncated>
533 <PolicyNames>
534{members}
535 </PolicyNames>
536 </ListRolePoliciesResult>
537 <ResponseMetadata>
538 <RequestId>{request_id}</RequestId>
539 </ResponseMetadata>
540</ListRolePoliciesResponse>"#,
541 )
542}
543
544pub fn get_caller_identity_response(
545 account_id: &str,
546 arn: &str,
547 user_id: &str,
548 request_id: &str,
549) -> String {
550 format!(
551 r#"<?xml version="1.0" encoding="UTF-8"?>
552<GetCallerIdentityResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
553 <GetCallerIdentityResult>
554 <Arn>{arn}</Arn>
555 <UserId>{user_id}</UserId>
556 <Account>{account_id}</Account>
557 </GetCallerIdentityResult>
558 <ResponseMetadata>
559 <RequestId>{request_id}</RequestId>
560 </ResponseMetadata>
561</GetCallerIdentityResponse>"#,
562 )
563}
564
565pub struct StsCredentials {
567 pub access_key_id: String,
568 pub secret_access_key: String,
569 pub session_token: String,
570}
571
572impl StsCredentials {
573 pub fn generate() -> Self {
574 Self {
575 access_key_id: generate_access_key_id(),
576 secret_access_key: generate_secret_access_key(),
577 session_token: generate_session_token(),
578 }
579 }
580}
581
582pub fn assume_role_response(
583 role_arn: &str,
584 role_session_name: &str,
585 role_id: &str,
586 account_id: &str,
587 partition: &str,
588 creds: &StsCredentials,
589 request_id: &str,
590) -> String {
591 let role_name = role_arn.rsplit('/').next().unwrap_or("unknown");
593 let assumed_role_arn = format!(
594 "arn:{}:sts::{}:assumed-role/{}/{}",
595 partition, account_id, role_name, role_session_name
596 );
597
598 format!(
599 r#"<?xml version="1.0" encoding="UTF-8"?>
600<AssumeRoleResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
601 <AssumeRoleResult>
602 <Credentials>
603 <AccessKeyId>{access_key_id}</AccessKeyId>
604 <SecretAccessKey>{secret_access_key}</SecretAccessKey>
605 <SessionToken>{session_token}</SessionToken>
606 <Expiration>2099-12-31T23:59:59Z</Expiration>
607 </Credentials>
608 <AssumedRoleUser>
609 <AssumedRoleId>{role_id}:{session}</AssumedRoleId>
610 <Arn>{assumed_role_arn}</Arn>
611 </AssumedRoleUser>
612 </AssumeRoleResult>
613 <ResponseMetadata>
614 <RequestId>{request_id}</RequestId>
615 </ResponseMetadata>
616</AssumeRoleResponse>"#,
617 access_key_id = creds.access_key_id,
618 secret_access_key = creds.secret_access_key,
619 session_token = creds.session_token,
620 role_id = role_id,
621 assumed_role_arn = assumed_role_arn,
622 session = role_session_name,
623 )
624}
625
626pub fn assume_role_with_web_identity_response(
627 role_arn: &str,
628 role_session_name: &str,
629 account_id: &str,
630 partition: &str,
631 creds: &StsCredentials,
632 assumed_role_id: &str,
633 request_id: &str,
634) -> String {
635 let role_name = role_arn.rsplit('/').next().unwrap_or("unknown");
636 let assumed_role_arn = format!(
637 "arn:{}:sts::{}:assumed-role/{}/{}",
638 partition, account_id, role_name, role_session_name
639 );
640
641 format!(
642 r#"<?xml version="1.0" encoding="UTF-8"?>
643<AssumeRoleWithWebIdentityResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
644 <AssumeRoleWithWebIdentityResult>
645 <Credentials>
646 <AccessKeyId>{access_key_id}</AccessKeyId>
647 <SecretAccessKey>{secret_access_key}</SecretAccessKey>
648 <SessionToken>{session_token}</SessionToken>
649 <Expiration>2099-12-31T23:59:59Z</Expiration>
650 </Credentials>
651 <AssumedRoleUser>
652 <AssumedRoleId>{assumed_role_id}:{session}</AssumedRoleId>
653 <Arn>{assumed_role_arn}</Arn>
654 </AssumedRoleUser>
655 </AssumeRoleWithWebIdentityResult>
656 <ResponseMetadata>
657 <RequestId>{request_id}</RequestId>
658 </ResponseMetadata>
659</AssumeRoleWithWebIdentityResponse>"#,
660 access_key_id = creds.access_key_id,
661 secret_access_key = creds.secret_access_key,
662 session_token = creds.session_token,
663 assumed_role_id = assumed_role_id,
664 assumed_role_arn = assumed_role_arn,
665 session = role_session_name,
666 request_id = request_id,
667 )
668}
669
670pub fn assume_role_with_saml_response(
671 role_arn: &str,
672 role_session_name: &str,
673 account_id: &str,
674 partition: &str,
675 creds: &StsCredentials,
676 assumed_role_id: &str,
677 request_id: &str,
678) -> String {
679 let role_name = role_arn.rsplit('/').next().unwrap_or("unknown");
680 let assumed_role_arn = format!(
681 "arn:{}:sts::{}:assumed-role/{}/{}",
682 partition, account_id, role_name, role_session_name
683 );
684
685 format!(
686 r#"<?xml version="1.0" encoding="UTF-8"?>
687<AssumeRoleWithSAMLResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
688 <AssumeRoleWithSAMLResult>
689 <Credentials>
690 <AccessKeyId>{access_key_id}</AccessKeyId>
691 <SecretAccessKey>{secret_access_key}</SecretAccessKey>
692 <SessionToken>{session_token}</SessionToken>
693 <Expiration>2099-12-31T23:59:59Z</Expiration>
694 </Credentials>
695 <AssumedRoleUser>
696 <AssumedRoleId>{assumed_role_id}:{session}</AssumedRoleId>
697 <Arn>{assumed_role_arn}</Arn>
698 </AssumedRoleUser>
699 </AssumeRoleWithSAMLResult>
700 <ResponseMetadata>
701 <RequestId>{request_id}</RequestId>
702 </ResponseMetadata>
703</AssumeRoleWithSAMLResponse>"#,
704 access_key_id = creds.access_key_id,
705 secret_access_key = creds.secret_access_key,
706 session_token = creds.session_token,
707 assumed_role_id = assumed_role_id,
708 assumed_role_arn = assumed_role_arn,
709 session = role_session_name,
710 request_id = request_id,
711 )
712}
713
714pub fn get_session_token_response(request_id: &str) -> String {
715 let access_key_id = "FSIAIOSFODNN7EXAMPLE";
717 let secret_access_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY";
718 let session_token = "AQoEXAMPLEH4aoAH0gNCAPyJxz4BlCFFxWNE1OPTgk5TthT+FvwqnKwRcOIfrRh3c/LTo6UDdyJwOOvEVPvLXCrrrUtdnniCEXAMPLE/IvU1dYUg2RVAJBanLiHb4IgRmpRV3zrkuWJOgQs8IZZaIv2BXIa2R4OlgkBN9bkUDNCJiBeb/AXlzBBko7b15fjrBs2+cTQtpZ3CYWFXG8C5zqx37wnOE49mRl/+OtkIKGO7fAE";
719
720 format!(
721 r#"<?xml version="1.0" encoding="UTF-8"?>
722<GetSessionTokenResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
723 <GetSessionTokenResult>
724 <Credentials>
725 <AccessKeyId>{access_key_id}</AccessKeyId>
726 <SecretAccessKey>{secret_access_key}</SecretAccessKey>
727 <SessionToken>{session_token}</SessionToken>
728 <Expiration>2099-12-31T23:59:59Z</Expiration>
729 </Credentials>
730 </GetSessionTokenResult>
731 <ResponseMetadata>
732 <RequestId>{request_id}</RequestId>
733 </ResponseMetadata>
734</GetSessionTokenResponse>"#,
735 access_key_id = access_key_id,
736 secret_access_key = secret_access_key,
737 session_token = session_token,
738 request_id = request_id,
739 )
740}
741
742pub fn get_federation_token_response(
743 name: &str,
744 account_id: &str,
745 partition: &str,
746 request_id: &str,
747) -> String {
748 let access_key_id = "FSIAIOSFODNN7EXAMPLE";
750 let secret_access_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY";
751 let session_token = "AQoDYXdzEPT//////////wEXAMPLEtc764bNrC9SAPBSM22wDOk4x4HIZ8j4FZTwdQWLWsKWHGBuFqwAeMicRXmxfpSPfIeoIYRqTflfKD8YUuwthAx7mSEI/qkPpKPi/kMcGdQrmGdeehM4IC1NtBmUpp2wUE8phUZampKsburEDy0KPkyQDYwT7WZ0wq5VSXDvp75YU9HFvlRd8Tx6q6fE8YQcHNVXAkiY9q6d+xo0rKwT38xVqr7ZD0u0iPPkUL64lIZbqBAz+scqKmlzm8FDrypNC9Yjc8fPOLn9FX9KSYvKTr4rvx3iSIlTJabIQwj2ICCR/oLxBA==";
752
753 let name = xml_escape(name);
754 let federated_user_arn = format!(
755 "arn:{}:sts::{}:federated-user/{}",
756 partition, account_id, name
757 );
758 let federated_user_id = format!("{}:{}", account_id, name);
759
760 format!(
761 r#"<?xml version="1.0" encoding="UTF-8"?>
762<GetFederationTokenResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
763 <GetFederationTokenResult>
764 <Credentials>
765 <AccessKeyId>{access_key_id}</AccessKeyId>
766 <SecretAccessKey>{secret_access_key}</SecretAccessKey>
767 <SessionToken>{session_token}</SessionToken>
768 <Expiration>2099-12-31T23:59:59Z</Expiration>
769 </Credentials>
770 <FederatedUser>
771 <FederatedUserId>{federated_user_id}</FederatedUserId>
772 <Arn>{federated_user_arn}</Arn>
773 </FederatedUser>
774 </GetFederationTokenResult>
775 <ResponseMetadata>
776 <RequestId>{request_id}</RequestId>
777 </ResponseMetadata>
778</GetFederationTokenResponse>"#,
779 access_key_id = access_key_id,
780 secret_access_key = secret_access_key,
781 session_token = session_token,
782 federated_user_arn = federated_user_arn,
783 federated_user_id = federated_user_id,
784 request_id = request_id,
785 )
786}
787
788pub fn get_access_key_info_response(account_id: &str, request_id: &str) -> String {
789 format!(
790 r#"<?xml version="1.0" encoding="UTF-8"?>
791<GetAccessKeyInfoResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
792 <GetAccessKeyInfoResult>
793 <Account>{account_id}</Account>
794 </GetAccessKeyInfoResult>
795 <ResponseMetadata>
796 <RequestId>{request_id}</RequestId>
797 </ResponseMetadata>
798</GetAccessKeyInfoResponse>"#,
799 account_id = account_id,
800 request_id = request_id,
801 )
802}
803
804pub fn generate_access_key_id() -> String {
806 let id = generate_alphanum_id(16);
807 format!("FSIA{}", id)
808}
809
810pub fn generate_secret_access_key() -> String {
812 generate_alphanum_id(40)
813}
814
815pub fn generate_role_id() -> String {
817 let id = generate_alphanum_id(17);
818 format!("AROA{}", id)
819}
820
821pub fn generate_session_token() -> String {
823 let prefix = "FQoGZXIvYXdzE";
825 let remaining = 356 - prefix.len(); let mut raw = Vec::with_capacity(288);
829 for _ in 0..18 {
830 raw.extend_from_slice(uuid::Uuid::new_v4().as_bytes());
831 }
832 let encoded = base64::engine::general_purpose::STANDARD.encode(&raw);
833 let suffix = &encoded[..remaining];
835 format!("{}{}", prefix, suffix)
836}
837
838fn generate_alphanum_id(len: usize) -> String {
840 let raw = format!(
841 "{}{}{}",
842 uuid::Uuid::new_v4(),
843 uuid::Uuid::new_v4(),
844 uuid::Uuid::new_v4(),
845 );
846 raw.replace('-', "")
847 .chars()
848 .filter(|c| c.is_alphanumeric())
849 .take(len)
850 .collect()
851}