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