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 is_truncated: bool,
221 marker: Option<&str>,
222 request_id: &str,
223) -> String {
224 let members: String = keys
225 .iter()
226 .map(|k| {
227 format!(
228 r#" <member>
229 <UserName>{user}</UserName>
230 <AccessKeyId>{key_id}</AccessKeyId>
231 <Status>{status}</Status>
232 <CreateDate>{date}</CreateDate>
233 </member>"#,
234 user = k.user_name,
235 key_id = k.access_key_id,
236 status = k.status,
237 date = k.created_at.format("%Y-%m-%dT%H:%M:%SZ"),
238 )
239 })
240 .collect::<Vec<_>>()
241 .join("\n");
242
243 let marker_section = if let Some(m) = marker {
244 format!("\n <Marker>{}</Marker>", xml_escape(m))
245 } else {
246 String::new()
247 };
248
249 format!(
250 r#"<?xml version="1.0" encoding="UTF-8"?>
251<ListAccessKeysResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
252 <ListAccessKeysResult>
253 <UserName>{user_name}</UserName>
254 <IsTruncated>{is_truncated}</IsTruncated>{marker_section}
255 <AccessKeyMetadata>
256{members}
257 </AccessKeyMetadata>
258 </ListAccessKeysResult>
259 <ResponseMetadata>
260 <RequestId>{request_id}</RequestId>
261 </ResponseMetadata>
262</ListAccessKeysResponse>"#,
263 )
264}
265
266pub fn create_role_response(role: &IamRole, request_id: &str) -> String {
267 let role_xml = role_xml(role);
268 format!(
269 r#"<?xml version="1.0" encoding="UTF-8"?>
270<CreateRoleResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
271 <CreateRoleResult>
272{role_xml}
273 </CreateRoleResult>
274 <ResponseMetadata>
275 <RequestId>{request_id}</RequestId>
276 </ResponseMetadata>
277</CreateRoleResponse>"#,
278 )
279}
280
281pub fn get_role_response(role: &IamRole, request_id: &str) -> String {
282 let role_xml = role_xml(role);
283 format!(
284 r#"<?xml version="1.0" encoding="UTF-8"?>
285<GetRoleResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
286 <GetRoleResult>
287{role_xml}
288 </GetRoleResult>
289 <ResponseMetadata>
290 <RequestId>{request_id}</RequestId>
291 </ResponseMetadata>
292</GetRoleResponse>"#,
293 )
294}
295
296pub fn list_roles_response(roles: &[IamRole], request_id: &str) -> String {
297 let members: String = roles
298 .iter()
299 .map(|r| {
300 let description_section = match &r.description {
302 Some(desc) => format!("\n <Description>{}</Description>", xml_escape(desc)),
303 None => String::new(),
304 };
305 format!(
306 r#" <member>
307 <Path>{path}</Path>
308 <RoleName>{name}</RoleName>
309 <RoleId>{id}</RoleId>
310 <Arn>{arn}</Arn>
311 <CreateDate>{date}</CreateDate>
312 <AssumeRolePolicyDocument>{policy}</AssumeRolePolicyDocument>{description_section}
313 <MaxSessionDuration>{max_session}</MaxSessionDuration>
314 </member>"#,
315 path = r.path,
316 name = r.role_name,
317 id = r.role_id,
318 arn = r.arn,
319 date = r.created_at.format("%Y-%m-%dT%H:%M:%SZ"),
320 policy = url_encode_policy(&r.assume_role_policy_document),
321 max_session = r.max_session_duration,
322 )
323 })
324 .collect::<Vec<_>>()
325 .join("\n");
326
327 format!(
328 r#"<?xml version="1.0" encoding="UTF-8"?>
329<ListRolesResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
330 <ListRolesResult>
331 <IsTruncated>false</IsTruncated>
332 <Roles>
333{members}
334 </Roles>
335 </ListRolesResult>
336 <ResponseMetadata>
337 <RequestId>{request_id}</RequestId>
338 </ResponseMetadata>
339</ListRolesResponse>"#,
340 )
341}
342
343pub fn list_roles_response_paginated(
344 roles: &[IamRole],
345 is_truncated: bool,
346 marker: Option<&str>,
347 request_id: &str,
348) -> String {
349 let members: String = roles
350 .iter()
351 .map(|r| {
352 let description_section = match &r.description {
354 Some(desc) => format!("\n <Description>{}</Description>", xml_escape(desc)),
355 None => String::new(),
356 };
357 format!(
358 r#" <member>
359 <Path>{path}</Path>
360 <RoleName>{name}</RoleName>
361 <RoleId>{id}</RoleId>
362 <Arn>{arn}</Arn>
363 <CreateDate>{date}</CreateDate>
364 <AssumeRolePolicyDocument>{policy}</AssumeRolePolicyDocument>{description_section}
365 <MaxSessionDuration>{max_session}</MaxSessionDuration>
366 </member>"#,
367 path = r.path,
368 name = r.role_name,
369 id = r.role_id,
370 arn = r.arn,
371 date = r.created_at.format("%Y-%m-%dT%H:%M:%SZ"),
372 policy = url_encode_policy(&r.assume_role_policy_document),
373 max_session = r.max_session_duration,
374 )
375 })
376 .collect::<Vec<_>>()
377 .join("\n");
378
379 let marker_section = if let Some(m) = marker {
380 format!("\n <Marker>{}</Marker>", xml_escape(m))
381 } else {
382 String::new()
383 };
384
385 format!(
386 r#"<?xml version="1.0" encoding="UTF-8"?>
387<ListRolesResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
388 <ListRolesResult>
389 <IsTruncated>{is_truncated}</IsTruncated>{marker_section}
390 <Roles>
391{members}
392 </Roles>
393 </ListRolesResult>
394 <ResponseMetadata>
395 <RequestId>{request_id}</RequestId>
396 </ResponseMetadata>
397</ListRolesResponse>"#,
398 )
399}
400
401pub fn create_policy_response(policy: &IamPolicy, request_id: &str) -> String {
402 let tags_section = if policy.tags.is_empty() {
403 String::new()
404 } else {
405 let tags_members = tags_xml(&policy.tags);
406 format!("\n <Tags>\n{tags_members}\n </Tags>")
407 };
408
409 format!(
410 r#"<?xml version="1.0" encoding="UTF-8"?>
411<CreatePolicyResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
412 <CreatePolicyResult>
413 <Policy>
414 <PolicyName>{name}</PolicyName>
415 <PolicyId>{id}</PolicyId>
416 <Arn>{arn}</Arn>
417 <Path>{path}</Path>
418 <DefaultVersionId>{default_version}</DefaultVersionId>
419 <AttachmentCount>{attachment_count}</AttachmentCount>
420 <IsAttachable>true</IsAttachable>
421 <CreateDate>{date}</CreateDate>{tags_section}
422 </Policy>
423 </CreatePolicyResult>
424 <ResponseMetadata>
425 <RequestId>{request_id}</RequestId>
426 </ResponseMetadata>
427</CreatePolicyResponse>"#,
428 name = policy.policy_name,
429 id = policy.policy_id,
430 arn = policy.arn,
431 path = policy.path,
432 default_version = policy.default_version_id,
433 attachment_count = policy.attachment_count,
434 date = policy.created_at.format("%Y-%m-%dT%H:%M:%SZ"),
435 )
436}
437
438pub fn list_policies_response(policies: &[IamPolicy], request_id: &str) -> String {
439 let members: String = policies
440 .iter()
441 .map(|p| {
442 format!(
444 r#" <member>
445 <PolicyName>{name}</PolicyName>
446 <PolicyId>{id}</PolicyId>
447 <Arn>{arn}</Arn>
448 <Path>{path}</Path>
449 <DefaultVersionId>{default_version}</DefaultVersionId>
450 <AttachmentCount>{attachment_count}</AttachmentCount>
451 <IsAttachable>true</IsAttachable>
452 <CreateDate>{date}</CreateDate>
453 </member>"#,
454 name = p.policy_name,
455 id = p.policy_id,
456 arn = p.arn,
457 path = p.path,
458 default_version = p.default_version_id,
459 attachment_count = p.attachment_count,
460 date = p.created_at.format("%Y-%m-%dT%H:%M:%SZ"),
461 )
462 })
463 .collect::<Vec<_>>()
464 .join("\n");
465
466 format!(
467 r#"<?xml version="1.0" encoding="UTF-8"?>
468<ListPoliciesResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
469 <ListPoliciesResult>
470 <IsTruncated>false</IsTruncated>
471 <Policies>
472{members}
473 </Policies>
474 </ListPoliciesResult>
475 <ResponseMetadata>
476 <RequestId>{request_id}</RequestId>
477 </ResponseMetadata>
478</ListPoliciesResponse>"#,
479 )
480}
481
482pub fn get_policy_response(policy: &IamPolicy, request_id: &str) -> String {
483 let tags_section = if policy.tags.is_empty() {
485 "\n <Tags/>".to_string()
486 } else {
487 let tags_members = tags_xml(&policy.tags);
488 format!("\n <Tags>\n{tags_members}\n </Tags>")
489 };
490
491 let description_section = if policy.description.is_empty() {
492 String::new()
493 } else {
494 format!(
495 "\n <Description>{}</Description>",
496 xml_escape(&policy.description)
497 )
498 };
499
500 format!(
501 r#"<?xml version="1.0" encoding="UTF-8"?>
502<GetPolicyResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
503 <GetPolicyResult>
504 <Policy>
505 <PolicyName>{name}</PolicyName>
506 <PolicyId>{id}</PolicyId>
507 <Arn>{arn}</Arn>
508 <Path>{path}</Path>
509 <DefaultVersionId>{default_version}</DefaultVersionId>
510 <AttachmentCount>{attachment_count}</AttachmentCount>
511 <IsAttachable>true</IsAttachable>
512 <CreateDate>{date}</CreateDate>{description_section}{tags_section}
513 </Policy>
514 </GetPolicyResult>
515 <ResponseMetadata>
516 <RequestId>{request_id}</RequestId>
517 </ResponseMetadata>
518</GetPolicyResponse>"#,
519 name = policy.policy_name,
520 id = policy.policy_id,
521 arn = policy.arn,
522 path = policy.path,
523 default_version = policy.default_version_id,
524 attachment_count = policy.attachment_count,
525 date = policy.created_at.format("%Y-%m-%dT%H:%M:%SZ"),
526 )
527}
528
529pub fn list_role_policies_response(policy_names: &[String], request_id: &str) -> String {
530 let members: String = policy_names
531 .iter()
532 .map(|name| format!(" <member>{name}</member>"))
533 .collect::<Vec<_>>()
534 .join("\n");
535
536 format!(
537 r#"<?xml version="1.0" encoding="UTF-8"?>
538<ListRolePoliciesResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
539 <ListRolePoliciesResult>
540 <IsTruncated>false</IsTruncated>
541 <PolicyNames>
542{members}
543 </PolicyNames>
544 </ListRolePoliciesResult>
545 <ResponseMetadata>
546 <RequestId>{request_id}</RequestId>
547 </ResponseMetadata>
548</ListRolePoliciesResponse>"#,
549 )
550}
551
552pub fn get_caller_identity_response(
553 account_id: &str,
554 arn: &str,
555 user_id: &str,
556 request_id: &str,
557) -> String {
558 format!(
559 r#"<?xml version="1.0" encoding="UTF-8"?>
560<GetCallerIdentityResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
561 <GetCallerIdentityResult>
562 <Arn>{arn}</Arn>
563 <UserId>{user_id}</UserId>
564 <Account>{account_id}</Account>
565 </GetCallerIdentityResult>
566 <ResponseMetadata>
567 <RequestId>{request_id}</RequestId>
568 </ResponseMetadata>
569</GetCallerIdentityResponse>"#,
570 )
571}
572
573pub struct StsCredentials {
575 pub access_key_id: String,
576 pub secret_access_key: String,
577 pub session_token: String,
578}
579
580impl StsCredentials {
581 pub fn generate() -> Self {
582 Self {
583 access_key_id: generate_access_key_id(),
584 secret_access_key: generate_secret_access_key(),
585 session_token: generate_session_token(),
586 }
587 }
588}
589
590#[allow(clippy::too_many_arguments)]
591pub fn assume_role_response(
592 role_arn: &str,
593 role_session_name: &str,
594 role_id: &str,
595 account_id: &str,
596 partition: &str,
597 creds: &StsCredentials,
598 expiration: &str,
599 request_id: &str,
600) -> String {
601 let role_name = role_arn.rsplit('/').next().unwrap_or("unknown");
603 let assumed_role_arn = format!(
604 "arn:{}:sts::{}:assumed-role/{}/{}",
605 partition, account_id, role_name, role_session_name
606 );
607
608 format!(
609 r#"<?xml version="1.0" encoding="UTF-8"?>
610<AssumeRoleResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
611 <AssumeRoleResult>
612 <Credentials>
613 <AccessKeyId>{access_key_id}</AccessKeyId>
614 <SecretAccessKey>{secret_access_key}</SecretAccessKey>
615 <SessionToken>{session_token}</SessionToken>
616 <Expiration>{expiration}</Expiration>
617 </Credentials>
618 <AssumedRoleUser>
619 <AssumedRoleId>{role_id}:{session}</AssumedRoleId>
620 <Arn>{assumed_role_arn}</Arn>
621 </AssumedRoleUser>
622 </AssumeRoleResult>
623 <ResponseMetadata>
624 <RequestId>{request_id}</RequestId>
625 </ResponseMetadata>
626</AssumeRoleResponse>"#,
627 access_key_id = creds.access_key_id,
628 secret_access_key = creds.secret_access_key,
629 session_token = creds.session_token,
630 role_id = role_id,
631 assumed_role_arn = assumed_role_arn,
632 session = role_session_name,
633 )
634}
635
636#[allow(clippy::too_many_arguments)]
637pub fn assume_role_with_web_identity_response(
638 role_arn: &str,
639 role_session_name: &str,
640 account_id: &str,
641 partition: &str,
642 creds: &StsCredentials,
643 assumed_role_id: &str,
644 expiration: &str,
645 request_id: &str,
646) -> String {
647 let role_name = role_arn.rsplit('/').next().unwrap_or("unknown");
648 let assumed_role_arn = format!(
649 "arn:{}:sts::{}:assumed-role/{}/{}",
650 partition, account_id, role_name, role_session_name
651 );
652
653 format!(
654 r#"<?xml version="1.0" encoding="UTF-8"?>
655<AssumeRoleWithWebIdentityResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
656 <AssumeRoleWithWebIdentityResult>
657 <Credentials>
658 <AccessKeyId>{access_key_id}</AccessKeyId>
659 <SecretAccessKey>{secret_access_key}</SecretAccessKey>
660 <SessionToken>{session_token}</SessionToken>
661 <Expiration>{expiration}</Expiration>
662 </Credentials>
663 <AssumedRoleUser>
664 <AssumedRoleId>{assumed_role_id}:{session}</AssumedRoleId>
665 <Arn>{assumed_role_arn}</Arn>
666 </AssumedRoleUser>
667 </AssumeRoleWithWebIdentityResult>
668 <ResponseMetadata>
669 <RequestId>{request_id}</RequestId>
670 </ResponseMetadata>
671</AssumeRoleWithWebIdentityResponse>"#,
672 access_key_id = creds.access_key_id,
673 secret_access_key = creds.secret_access_key,
674 session_token = creds.session_token,
675 assumed_role_id = assumed_role_id,
676 assumed_role_arn = assumed_role_arn,
677 session = role_session_name,
678 request_id = request_id,
679 )
680}
681
682#[allow(clippy::too_many_arguments)]
683pub fn assume_role_with_saml_response(
684 role_arn: &str,
685 role_session_name: &str,
686 account_id: &str,
687 partition: &str,
688 creds: &StsCredentials,
689 assumed_role_id: &str,
690 expiration: &str,
691 request_id: &str,
692) -> String {
693 let role_name = role_arn.rsplit('/').next().unwrap_or("unknown");
694 let assumed_role_arn = format!(
695 "arn:{}:sts::{}:assumed-role/{}/{}",
696 partition, account_id, role_name, role_session_name
697 );
698
699 format!(
700 r#"<?xml version="1.0" encoding="UTF-8"?>
701<AssumeRoleWithSAMLResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
702 <AssumeRoleWithSAMLResult>
703 <Credentials>
704 <AccessKeyId>{access_key_id}</AccessKeyId>
705 <SecretAccessKey>{secret_access_key}</SecretAccessKey>
706 <SessionToken>{session_token}</SessionToken>
707 <Expiration>{expiration}</Expiration>
708 </Credentials>
709 <AssumedRoleUser>
710 <AssumedRoleId>{assumed_role_id}:{session}</AssumedRoleId>
711 <Arn>{assumed_role_arn}</Arn>
712 </AssumedRoleUser>
713 </AssumeRoleWithSAMLResult>
714 <ResponseMetadata>
715 <RequestId>{request_id}</RequestId>
716 </ResponseMetadata>
717</AssumeRoleWithSAMLResponse>"#,
718 access_key_id = creds.access_key_id,
719 secret_access_key = creds.secret_access_key,
720 session_token = creds.session_token,
721 assumed_role_id = assumed_role_id,
722 assumed_role_arn = assumed_role_arn,
723 session = role_session_name,
724 request_id = request_id,
725 )
726}
727
728pub fn get_session_token_response(expiration: &str, request_id: &str) -> String {
729 let access_key_id = "FSIAIOSFODNN7EXAMPLE";
731 let secret_access_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY";
732 let session_token = "AQoEXAMPLEH4aoAH0gNCAPyJxz4BlCFFxWNE1OPTgk5TthT+FvwqnKwRcOIfrRh3c/LTo6UDdyJwOOvEVPvLXCrrrUtdnniCEXAMPLE/IvU1dYUg2RVAJBanLiHb4IgRmpRV3zrkuWJOgQs8IZZaIv2BXIa2R4OlgkBN9bkUDNCJiBeb/AXlzBBko7b15fjrBs2+cTQtpZ3CYWFXG8C5zqx37wnOE49mRl/+OtkIKGO7fAE";
733
734 format!(
735 r#"<?xml version="1.0" encoding="UTF-8"?>
736<GetSessionTokenResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
737 <GetSessionTokenResult>
738 <Credentials>
739 <AccessKeyId>{access_key_id}</AccessKeyId>
740 <SecretAccessKey>{secret_access_key}</SecretAccessKey>
741 <SessionToken>{session_token}</SessionToken>
742 <Expiration>{expiration}</Expiration>
743 </Credentials>
744 </GetSessionTokenResult>
745 <ResponseMetadata>
746 <RequestId>{request_id}</RequestId>
747 </ResponseMetadata>
748</GetSessionTokenResponse>"#,
749 access_key_id = access_key_id,
750 secret_access_key = secret_access_key,
751 session_token = session_token,
752 request_id = request_id,
753 )
754}
755
756pub fn get_federation_token_response(
757 name: &str,
758 account_id: &str,
759 partition: &str,
760 expiration: &str,
761 policy: Option<&str>,
762 request_id: &str,
763) -> String {
764 let access_key_id = "FSIAIOSFODNN7EXAMPLE";
766 let secret_access_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY";
767 let session_token = "AQoDYXdzEPT//////////wEXAMPLEtc764bNrC9SAPBSM22wDOk4x4HIZ8j4FZTwdQWLWsKWHGBuFqwAeMicRXmxfpSPfIeoIYRqTflfKD8YUuwthAx7mSEI/qkPpKPi/kMcGdQrmGdeehM4IC1NtBmUpp2wUE8phUZampKsburEDy0KPkyQDYwT7WZ0wq5VSXDvp75YU9HFvlRd8Tx6q6fE8YQcHNVXAkiY9q6d+xo0rKwT38xVqr7ZD0u0iPPkUL64lIZbqBAz+scqKmlzm8FDrypNC9Yjc8fPOLn9FX9KSYvKTr4rvx3iSIlTJabIQwj2ICCR/oLxBA==";
768
769 let name = xml_escape(name);
770 let federated_user_arn = format!(
771 "arn:{}:sts::{}:federated-user/{}",
772 partition, account_id, name
773 );
774 let federated_user_id = format!("{}:{}", account_id, name);
775
776 let policy_section = if let Some(p) = policy {
777 format!(
778 "\n <PackedPolicySize>6</PackedPolicySize>\n <Policy>{}</Policy>",
779 xml_escape(p)
780 )
781 } else {
782 String::new()
783 };
784
785 format!(
786 r#"<?xml version="1.0" encoding="UTF-8"?>
787<GetFederationTokenResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
788 <GetFederationTokenResult>
789 <Credentials>
790 <AccessKeyId>{access_key_id}</AccessKeyId>
791 <SecretAccessKey>{secret_access_key}</SecretAccessKey>
792 <SessionToken>{session_token}</SessionToken>
793 <Expiration>{expiration}</Expiration>
794 </Credentials>
795 <FederatedUser>
796 <FederatedUserId>{federated_user_id}</FederatedUserId>
797 <Arn>{federated_user_arn}</Arn>
798 </FederatedUser>{policy_section}
799 </GetFederationTokenResult>
800 <ResponseMetadata>
801 <RequestId>{request_id}</RequestId>
802 </ResponseMetadata>
803</GetFederationTokenResponse>"#,
804 access_key_id = access_key_id,
805 secret_access_key = secret_access_key,
806 session_token = session_token,
807 federated_user_arn = federated_user_arn,
808 federated_user_id = federated_user_id,
809 request_id = request_id,
810 )
811}
812
813pub fn decode_authorization_message_response(decoded_message: &str, request_id: &str) -> String {
814 format!(
815 r#"<?xml version="1.0" encoding="UTF-8"?>
816<DecodeAuthorizationMessageResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
817 <DecodeAuthorizationMessageResult>
818 <DecodedMessage>{decoded_message}</DecodedMessage>
819 </DecodeAuthorizationMessageResult>
820 <ResponseMetadata>
821 <RequestId>{request_id}</RequestId>
822 </ResponseMetadata>
823</DecodeAuthorizationMessageResponse>"#,
824 decoded_message = xml_escape(decoded_message),
825 request_id = request_id,
826 )
827}
828
829pub fn get_access_key_info_response(account_id: &str, request_id: &str) -> String {
830 format!(
831 r#"<?xml version="1.0" encoding="UTF-8"?>
832<GetAccessKeyInfoResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
833 <GetAccessKeyInfoResult>
834 <Account>{account_id}</Account>
835 </GetAccessKeyInfoResult>
836 <ResponseMetadata>
837 <RequestId>{request_id}</RequestId>
838 </ResponseMetadata>
839</GetAccessKeyInfoResponse>"#,
840 account_id = account_id,
841 request_id = request_id,
842 )
843}
844
845pub fn generate_access_key_id() -> String {
847 let id = generate_alphanum_id(16);
848 format!("FSIA{}", id)
849}
850
851pub fn generate_secret_access_key() -> String {
853 generate_alphanum_id(40)
854}
855
856pub fn generate_role_id() -> String {
858 let id = generate_alphanum_id(17);
859 format!("AROA{}", id)
860}
861
862pub fn generate_session_token() -> String {
864 let prefix = "FQoGZXIvYXdzE";
866 let remaining = 356 - prefix.len(); let mut raw = Vec::with_capacity(288);
870 for _ in 0..18 {
871 raw.extend_from_slice(uuid::Uuid::new_v4().as_bytes());
872 }
873 let encoded = base64::engine::general_purpose::STANDARD.encode(&raw);
874 let suffix = &encoded[..remaining];
876 format!("{}{}", prefix, suffix)
877}
878
879fn generate_alphanum_id(len: usize) -> String {
881 let raw = format!(
882 "{}{}{}",
883 uuid::Uuid::new_v4(),
884 uuid::Uuid::new_v4(),
885 uuid::Uuid::new_v4(),
886 );
887 raw.replace('-', "")
888 .chars()
889 .filter(|c| c.is_alphanumeric())
890 .take(len)
891 .collect()
892}