@beesolve/aws-accounts 1.1.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
1
  import { loadAwsConfigModelFromTsFile } from "../awsConfig.js";
2
2
  const INLINE_POLICY_MAX_CHARS = 10240;
3
+ const ORG_POLICY_CONTENT_MAX_BYTES = 5120;
3
4
  async function runValidateCommand(input) {
4
5
  const configPath = input.configPath ?? "aws.config.ts";
5
6
  const typesPath = input.typesPath ?? "aws.config.types.ts";
@@ -14,6 +15,9 @@ async function runValidateCommand(input) {
14
15
  checkCircularOuReferences(config, errors);
15
16
  checkAssignmentPrincipals(config, errors);
16
17
  checkInlinePolicySizes(config, errors);
18
+ checkOrgPolicySizes(config, errors);
19
+ checkOrgPolicyTargets(config, errors);
20
+ checkDelegatedAdministratorDuplicates(config, errors);
17
21
  if (errors.length > 0) {
18
22
  for (const error of errors) {
19
23
  input.logger.log(`Error: ${error}`);
@@ -62,6 +66,77 @@ function checkAssignmentPrincipals(config, errors) {
62
66
  }
63
67
  }
64
68
  }
69
+ function checkOrgPolicySizes(config, errors) {
70
+ for (const policy of config.policies.serviceControlPolicies) {
71
+ const contentBytes = Buffer.byteLength(JSON.stringify(policy.content), "utf8");
72
+ if (contentBytes > ORG_POLICY_CONTENT_MAX_BYTES) {
73
+ errors.push(
74
+ `Service control policy "${policy.name}" content is ${contentBytes} bytes (limit: ${ORG_POLICY_CONTENT_MAX_BYTES}).`
75
+ );
76
+ }
77
+ }
78
+ for (const policy of config.policies.resourceControlPolicies) {
79
+ const contentBytes = Buffer.byteLength(JSON.stringify(policy.content), "utf8");
80
+ if (contentBytes > ORG_POLICY_CONTENT_MAX_BYTES) {
81
+ errors.push(
82
+ `Resource control policy "${policy.name}" content is ${contentBytes} bytes (limit: ${ORG_POLICY_CONTENT_MAX_BYTES}).`
83
+ );
84
+ }
85
+ }
86
+ for (const policy of config.policies.backupPolicies) {
87
+ const contentBytes = Buffer.byteLength(JSON.stringify(policy.content), "utf8");
88
+ if (contentBytes > ORG_POLICY_CONTENT_MAX_BYTES) {
89
+ errors.push(
90
+ `Backup policy "${policy.name}" content is ${contentBytes} bytes (limit: ${ORG_POLICY_CONTENT_MAX_BYTES}).`
91
+ );
92
+ }
93
+ }
94
+ }
95
+ function checkOrgPolicyTargets(config, errors) {
96
+ const ouNames = new Set(config.organizationalUnits.map((ou) => ou.name));
97
+ const accountNames = new Set(
98
+ config.organizationalUnits.flatMap((ou) => ou.accounts.map((a) => a.name))
99
+ );
100
+ for (const policy of config.policies.serviceControlPolicies) {
101
+ for (const target of policy.targets) {
102
+ if (target !== "root" && !ouNames.has(target) && !accountNames.has(target)) {
103
+ errors.push(
104
+ `Service control policy "${policy.name}" references unknown target "${target}".`
105
+ );
106
+ }
107
+ }
108
+ }
109
+ for (const policy of config.policies.resourceControlPolicies) {
110
+ for (const target of policy.targets) {
111
+ if (target !== "root" && !ouNames.has(target) && !accountNames.has(target)) {
112
+ errors.push(
113
+ `Resource control policy "${policy.name}" references unknown target "${target}".`
114
+ );
115
+ }
116
+ }
117
+ }
118
+ for (const policy of config.policies.backupPolicies) {
119
+ for (const target of policy.targets) {
120
+ if (target !== "root" && !ouNames.has(target) && !accountNames.has(target)) {
121
+ errors.push(
122
+ `Backup policy "${policy.name}" references unknown target "${target}".`
123
+ );
124
+ }
125
+ }
126
+ }
127
+ }
128
+ function checkDelegatedAdministratorDuplicates(config, errors) {
129
+ const seen = /* @__PURE__ */ new Set();
130
+ for (const da of config.delegatedAdministrators) {
131
+ const key = `${da.account}|${da.servicePrincipal}`;
132
+ if (seen.has(key)) {
133
+ errors.push(
134
+ `Duplicate delegated administrator entry: account "${da.account}" + service principal "${da.servicePrincipal}".`
135
+ );
136
+ }
137
+ seen.add(key);
138
+ }
139
+ }
65
140
  function checkInlinePolicySizes(config, errors) {
66
141
  for (const ps of config.permissionSets) {
67
142
  if (ps.inlinePolicy == null) {
package/dist/diff.js CHANGED
@@ -26,13 +26,26 @@ const operationExecutionPriority = {
26
26
  attachIdcCustomerManagedPolicyReferenceToPermissionSet: 20,
27
27
  detachIdcCustomerManagedPolicyReferenceFromPermissionSet: 21,
28
28
  provisionIdcPermissionSet: 22,
29
- grantIdcAccountAssignment: 23,
30
- removeIdcGroupMembership: 24,
31
- revokeIdcAccountAssignment: 25,
32
- deleteIdcUser: 26,
33
- deleteIdcGroup: 27,
34
- deleteIdcPermissionSet: 28,
35
- deleteOu: 29
29
+ putIdcPermissionSetPermissionsBoundary: 23,
30
+ deleteIdcPermissionSetPermissionsBoundary: 24,
31
+ grantIdcAccountAssignment: 25,
32
+ removeIdcGroupMembership: 26,
33
+ revokeIdcAccountAssignment: 27,
34
+ deleteIdcUser: 28,
35
+ deleteIdcGroup: 29,
36
+ deleteIdcPermissionSet: 30,
37
+ deleteOu: 31,
38
+ createOrgPolicy: 32,
39
+ updateOrgPolicyContent: 33,
40
+ updateOrgPolicyDescription: 34,
41
+ attachOrgPolicy: 35,
42
+ detachOrgPolicy: 36,
43
+ deleteOrgPolicy: 37,
44
+ putAlternateContact: 38,
45
+ deleteAlternateContact: 39,
46
+ setIdcAccessControlAttributes: 40,
47
+ registerDelegatedAdministrator: 41,
48
+ deregisterDelegatedAdministrator: 42
36
49
  };
37
50
  function diffStates(props) {
38
51
  const operations = [];
@@ -99,6 +112,13 @@ function diffStates(props) {
99
112
  )
100
113
  });
101
114
  }
115
+ diffAlternateContacts({
116
+ operations,
117
+ accountId: nextAccount.id,
118
+ accountName: nextAccount.name,
119
+ currentContacts: currentAccount.alternateContacts ?? [],
120
+ nextContacts: nextAccount.alternateContacts ?? []
121
+ });
102
122
  continue;
103
123
  }
104
124
  if (currentAccount.id === pendingCreationId || nextAccount.id === pendingCreationId || currentAccount.parentId === pendingCreationId || nextAccount.parentId === pendingCreationId) {
@@ -131,6 +151,13 @@ function diffStates(props) {
131
151
  toOuId: nextAccount.parentId,
132
152
  toOuName
133
153
  });
154
+ diffAlternateContacts({
155
+ operations,
156
+ accountId: nextAccount.id,
157
+ accountName: nextAccount.name,
158
+ currentContacts: currentAccount.alternateContacts ?? [],
159
+ nextContacts: nextAccount.alternateContacts ?? []
160
+ });
134
161
  }
135
162
  const graveyardOrganizationalUnit = currentOrganization.organizationalUnitByName.get("Graveyard");
136
163
  for (const currentAccount of currentOrganization.accounts) {
@@ -192,13 +219,17 @@ function diffStates(props) {
192
219
  const addedOrganizationalUnits = [];
193
220
  const removedOrganizationalUnits = [];
194
221
  for (const nextOrganizationalUnit of nextOrganization.organizationalUnits) {
195
- if (currentOrganization.organizationalUnitByName.has(nextOrganizationalUnit.name)) {
222
+ if (currentOrganization.organizationalUnitByName.has(
223
+ nextOrganizationalUnit.name
224
+ )) {
196
225
  continue;
197
226
  }
198
227
  addedOrganizationalUnits.push(nextOrganizationalUnit);
199
228
  }
200
229
  for (const currentOrganizationalUnit of currentOrganization.organizationalUnits) {
201
- if (nextOrganization.organizationalUnitByName.has(currentOrganizationalUnit.name)) {
230
+ if (nextOrganization.organizationalUnitByName.has(
231
+ currentOrganizationalUnit.name
232
+ )) {
202
233
  continue;
203
234
  }
204
235
  removedOrganizationalUnits.push(currentOrganizationalUnit);
@@ -436,7 +467,9 @@ function diffStates(props) {
436
467
  });
437
468
  }
438
469
  for (const nextPermissionSet of props.next.identityCenter.permissionSets) {
439
- const currentPermissionSet = currentIdcView.permissionSetsByName.get(nextPermissionSet.name);
470
+ const currentPermissionSet = currentIdcView.permissionSetsByName.get(
471
+ nextPermissionSet.name
472
+ );
440
473
  if (currentPermissionSet == null) {
441
474
  operations.push({
442
475
  kind: "createIdcPermissionSet",
@@ -484,7 +517,9 @@ function diffStates(props) {
484
517
  const currentAwsManagedPolicies = new Set(
485
518
  currentPermissionSet?.awsManagedPolicies ?? []
486
519
  );
487
- const nextAwsManagedPolicies = new Set(nextPermissionSet.awsManagedPolicies);
520
+ const nextAwsManagedPolicies = new Set(
521
+ nextPermissionSet.awsManagedPolicies
522
+ );
488
523
  for (const managedPolicyArn of nextAwsManagedPolicies) {
489
524
  if (currentAwsManagedPolicies.has(managedPolicyArn)) {
490
525
  continue;
@@ -517,7 +552,10 @@ function diffStates(props) {
517
552
  policy
518
553
  ])
519
554
  );
520
- for (const [policyKey, customerManagedPolicy] of nextCustomerManagedPolicies) {
555
+ for (const [
556
+ policyKey,
557
+ customerManagedPolicy
558
+ ] of nextCustomerManagedPolicies) {
521
559
  if (currentCustomerManagedPolicies.has(policyKey)) {
522
560
  continue;
523
561
  }
@@ -528,7 +566,10 @@ function diffStates(props) {
528
566
  customerManagedPolicyPath: customerManagedPolicy.path
529
567
  });
530
568
  }
531
- for (const [policyKey, customerManagedPolicy] of currentCustomerManagedPolicies) {
569
+ for (const [
570
+ policyKey,
571
+ customerManagedPolicy
572
+ ] of currentCustomerManagedPolicies) {
532
573
  if (nextCustomerManagedPolicies.has(policyKey)) {
533
574
  continue;
534
575
  }
@@ -539,6 +580,23 @@ function diffStates(props) {
539
580
  customerManagedPolicyPath: customerManagedPolicy.path
540
581
  });
541
582
  }
583
+ const currentBoundary = currentPermissionSet?.permissionsBoundary ?? null;
584
+ const nextBoundary = nextPermissionSet.permissionsBoundary ?? null;
585
+ if (nextBoundary != null) {
586
+ if (currentBoundary == null || JSON.stringify(currentBoundary) !== JSON.stringify(nextBoundary)) {
587
+ operations.push({
588
+ kind: "putIdcPermissionSetPermissionsBoundary",
589
+ permissionSetName: nextPermissionSet.name,
590
+ permissionsBoundary: nextBoundary
591
+ });
592
+ }
593
+ }
594
+ if (nextBoundary == null && currentBoundary != null) {
595
+ operations.push({
596
+ kind: "deleteIdcPermissionSetPermissionsBoundary",
597
+ permissionSetName: nextPermissionSet.name
598
+ });
599
+ }
542
600
  if (currentPermissionSet != null && operations.length > permissionSetMutationStartIndex && permissionSetNamesWithDesiredAssignments.has(nextPermissionSet.name)) {
543
601
  operations.push({
544
602
  kind: "provisionIdcPermissionSet",
@@ -595,6 +653,204 @@ function diffStates(props) {
595
653
  permissionSetName: removedPermissionSetName
596
654
  });
597
655
  }
656
+ const currentAccessControlAttributes = props.current.identityCenter.accessControlAttributes ?? [];
657
+ const nextAccessControlAttributes = props.next.identityCenter.accessControlAttributes ?? [];
658
+ if (JSON.stringify(
659
+ [...currentAccessControlAttributes].sort(
660
+ (a, b) => a.key.localeCompare(b.key)
661
+ )
662
+ ) !== JSON.stringify(
663
+ [...nextAccessControlAttributes].sort(
664
+ (a, b) => a.key.localeCompare(b.key)
665
+ )
666
+ )) {
667
+ operations.push({
668
+ kind: "setIdcAccessControlAttributes",
669
+ attributes: nextAccessControlAttributes
670
+ });
671
+ }
672
+ const nextAccountNameById = new Map(
673
+ props.next.organization.accounts.map((account) => [
674
+ account.id,
675
+ account.name
676
+ ])
677
+ );
678
+ const currentAccountNameById = new Map(
679
+ props.current.organization.accounts.map((account) => [
680
+ account.id,
681
+ account.name
682
+ ])
683
+ );
684
+ const currentDelegatedAdmins = props.current.organization.delegatedAdministrators ?? [];
685
+ const nextDelegatedAdmins = props.next.organization.delegatedAdministrators ?? [];
686
+ const nextDelegatedAdminKeys = new Set(
687
+ nextDelegatedAdmins.map((da) => `${da.accountId}|${da.servicePrincipal}`)
688
+ );
689
+ const currentDelegatedAdminKeys = new Set(
690
+ currentDelegatedAdmins.map(
691
+ (da) => `${da.accountId}|${da.servicePrincipal}`
692
+ )
693
+ );
694
+ for (const nextDa of nextDelegatedAdmins) {
695
+ if (currentDelegatedAdminKeys.has(
696
+ `${nextDa.accountId}|${nextDa.servicePrincipal}`
697
+ )) {
698
+ continue;
699
+ }
700
+ operations.push({
701
+ kind: "registerDelegatedAdministrator",
702
+ accountId: nextDa.accountId,
703
+ accountName: nextAccountNameById.get(nextDa.accountId) ?? nextDa.accountId,
704
+ servicePrincipal: nextDa.servicePrincipal
705
+ });
706
+ }
707
+ for (const currentDa of currentDelegatedAdmins) {
708
+ if (nextDelegatedAdminKeys.has(
709
+ `${currentDa.accountId}|${currentDa.servicePrincipal}`
710
+ )) {
711
+ continue;
712
+ }
713
+ operations.push({
714
+ kind: "deregisterDelegatedAdministrator",
715
+ accountId: currentDa.accountId,
716
+ accountName: currentAccountNameById.get(currentDa.accountId) ?? currentDa.accountId,
717
+ servicePrincipal: currentDa.servicePrincipal
718
+ });
719
+ }
720
+ const currentPolicies = props.current.organization.policies ?? [];
721
+ const nextPolicies = props.next.organization.policies ?? [];
722
+ const currentPolicyAttachments = props.current.organization.policyAttachments ?? [];
723
+ const nextPolicyAttachments = props.next.organization.policyAttachments ?? [];
724
+ const currentPoliciesByName = new Map(
725
+ currentPolicies.map((p) => [`${p.type}|${p.name}`, p])
726
+ );
727
+ const nextPoliciesByName = new Map(
728
+ nextPolicies.map((p) => [`${p.type}|${p.name}`, p])
729
+ );
730
+ const currentAttachmentsByKey = new Set(
731
+ currentPolicyAttachments.map((a) => `${a.policyId}|${a.targetId}`)
732
+ );
733
+ const nextPoliciesByPendingId = /* @__PURE__ */ new Map();
734
+ for (const nextPolicy of nextPolicies) {
735
+ const currentPolicy = currentPoliciesByName.get(
736
+ `${nextPolicy.type}|${nextPolicy.name}`
737
+ );
738
+ if (currentPolicy == null) {
739
+ operations.push({
740
+ kind: "createOrgPolicy",
741
+ policyName: nextPolicy.name,
742
+ policyType: nextPolicy.type,
743
+ description: nextPolicy.description,
744
+ content: nextPolicy.content
745
+ });
746
+ nextPoliciesByPendingId.set(nextPolicy.id, nextPolicy);
747
+ continue;
748
+ }
749
+ if (normalizeJsonContent(currentPolicy.content) !== normalizeJsonContent(nextPolicy.content)) {
750
+ operations.push({
751
+ kind: "updateOrgPolicyContent",
752
+ policyId: currentPolicy.id,
753
+ policyName: currentPolicy.name,
754
+ content: nextPolicy.content
755
+ });
756
+ }
757
+ if (currentPolicy.description !== nextPolicy.description) {
758
+ operations.push({
759
+ kind: "updateOrgPolicyDescription",
760
+ policyId: currentPolicy.id,
761
+ policyName: currentPolicy.name,
762
+ description: nextPolicy.description
763
+ });
764
+ }
765
+ }
766
+ const nextPoliciesById = new Map(nextPolicies.map((p) => [p.id, p]));
767
+ const currentPoliciesById = new Map(currentPolicies.map((p) => [p.id, p]));
768
+ const nextOuNameById = new Map(
769
+ props.next.organization.organizationalUnits.map((ou) => [ou.id, ou.name])
770
+ );
771
+ const currentOuNameById = new Map(
772
+ props.current.organization.organizationalUnits.map((ou) => [
773
+ ou.id,
774
+ ou.name
775
+ ])
776
+ );
777
+ function resolveNextTargetName(targetId, targetType) {
778
+ if (targetType === "ROOT") return "root";
779
+ if (targetType === "ORGANIZATIONAL_UNIT")
780
+ return nextOuNameById.get(targetId) ?? "unknown";
781
+ return nextAccountNameById.get(targetId) ?? "unknown";
782
+ }
783
+ function resolveCurrentTargetName(targetId, targetType) {
784
+ if (targetType === "ROOT") return "root";
785
+ if (targetType === "ORGANIZATIONAL_UNIT")
786
+ return currentOuNameById.get(targetId) ?? "unknown";
787
+ return currentAccountNameById.get(targetId) ?? "unknown";
788
+ }
789
+ for (const nextAttachment of nextPolicyAttachments) {
790
+ if (nextAttachment.policyId === pendingCreationId) {
791
+ continue;
792
+ }
793
+ if (nextAttachment.targetId === pendingCreationId) {
794
+ continue;
795
+ }
796
+ const attachmentKey = `${nextAttachment.policyId}|${nextAttachment.targetId}`;
797
+ if (currentAttachmentsByKey.has(attachmentKey)) {
798
+ continue;
799
+ }
800
+ const policy = nextPoliciesById.get(nextAttachment.policyId) ?? currentPoliciesById.get(nextAttachment.policyId);
801
+ if (policy == null) {
802
+ continue;
803
+ }
804
+ operations.push({
805
+ kind: "attachOrgPolicy",
806
+ policyId: nextAttachment.policyId,
807
+ policyName: policy.name,
808
+ targetId: nextAttachment.targetId,
809
+ targetName: resolveNextTargetName(
810
+ nextAttachment.targetId,
811
+ nextAttachment.targetType
812
+ )
813
+ });
814
+ }
815
+ const nextAttachmentKeys = new Set(
816
+ nextPolicyAttachments.filter(
817
+ (a) => a.policyId !== pendingCreationId && a.targetId !== pendingCreationId
818
+ ).map((a) => `${a.policyId}|${a.targetId}`)
819
+ );
820
+ const nextPolicyIds = new Set(
821
+ nextPolicies.filter((p) => p.id !== pendingCreationId).map((p) => p.id)
822
+ );
823
+ for (const currentAttachment of currentPolicyAttachments) {
824
+ const attachmentKey = `${currentAttachment.policyId}|${currentAttachment.targetId}`;
825
+ const policyBeingDeleted = !nextPolicyIds.has(currentAttachment.policyId) && currentPoliciesById.has(currentAttachment.policyId);
826
+ if (nextAttachmentKeys.has(attachmentKey) && !policyBeingDeleted) {
827
+ continue;
828
+ }
829
+ const policy = currentPoliciesById.get(currentAttachment.policyId);
830
+ if (policy == null) {
831
+ continue;
832
+ }
833
+ operations.push({
834
+ kind: "detachOrgPolicy",
835
+ policyId: currentAttachment.policyId,
836
+ policyName: policy.name,
837
+ targetId: currentAttachment.targetId,
838
+ targetName: resolveCurrentTargetName(
839
+ currentAttachment.targetId,
840
+ currentAttachment.targetType
841
+ )
842
+ });
843
+ }
844
+ for (const currentPolicy of currentPolicies) {
845
+ if (nextPoliciesByName.has(`${currentPolicy.type}|${currentPolicy.name}`)) {
846
+ continue;
847
+ }
848
+ operations.push({
849
+ kind: "deleteOrgPolicy",
850
+ policyId: currentPolicy.id,
851
+ policyName: currentPolicy.name
852
+ });
853
+ }
598
854
  operations.sort((left, right) => {
599
855
  const priorityComparison = getOperationExecutionPriority(left) - getOperationExecutionPriority(right);
600
856
  if (priorityComparison !== 0) {
@@ -830,8 +1086,41 @@ function getOperationSortKey(operation) {
830
1086
  operation.principalName
831
1087
  ].join("|");
832
1088
  }
1089
+ if (operation.kind === "createOrgPolicy") {
1090
+ return `${operation.kind}|${operation.policyType}|${operation.policyName}`;
1091
+ }
1092
+ if (operation.kind === "updateOrgPolicyContent" || operation.kind === "updateOrgPolicyDescription" || operation.kind === "deleteOrgPolicy") {
1093
+ return `${operation.kind}|${operation.policyName}`;
1094
+ }
1095
+ if (operation.kind === "attachOrgPolicy" || operation.kind === "detachOrgPolicy") {
1096
+ return [operation.kind, operation.policyName, operation.targetName].join(
1097
+ "|"
1098
+ );
1099
+ }
1100
+ if (operation.kind === "putAlternateContact" || operation.kind === "deleteAlternateContact") {
1101
+ return [operation.kind, operation.accountName, operation.contactType].join(
1102
+ "|"
1103
+ );
1104
+ }
1105
+ if (operation.kind === "setIdcAccessControlAttributes") {
1106
+ return operation.kind;
1107
+ }
1108
+ if (operation.kind === "registerDelegatedAdministrator" || operation.kind === "deregisterDelegatedAdministrator") {
1109
+ return [
1110
+ operation.kind,
1111
+ operation.accountName,
1112
+ operation.servicePrincipal
1113
+ ].join("|");
1114
+ }
833
1115
  return "zzzz";
834
1116
  }
1117
+ function normalizeJsonContent(content) {
1118
+ try {
1119
+ return JSON.stringify(sortJsonValue(JSON.parse(content)));
1120
+ } catch {
1121
+ return content;
1122
+ }
1123
+ }
835
1124
  function normalizeAccountTags(tags) {
836
1125
  if (tags == null || tags.length === 0) {
837
1126
  return [];
@@ -949,7 +1238,9 @@ function normalizeIdentityCenterState(props) {
949
1238
  };
950
1239
  }
951
1240
  function createNormalizedIdcMembershipKey(props) {
952
- return [props.membership.groupDisplayName, props.membership.userName].join("|");
1241
+ return [props.membership.groupDisplayName, props.membership.userName].join(
1242
+ "|"
1243
+ );
953
1244
  }
954
1245
  function resolveAssignmentPrincipalName(props) {
955
1246
  if (props.principalType === "GROUP") {
@@ -1016,6 +1307,37 @@ function isResolvableOrganizationalUnitId(props) {
1016
1307
  }
1017
1308
  return props.organizationalUnitNameById.has(props.organizationalUnitId);
1018
1309
  }
1310
+ function diffAlternateContacts(props) {
1311
+ const currentByType = new Map(
1312
+ props.currentContacts.map((c) => [c.contactType, c])
1313
+ );
1314
+ const nextByType = new Map(props.nextContacts.map((c) => [c.contactType, c]));
1315
+ for (const next of props.nextContacts) {
1316
+ const current = currentByType.get(next.contactType);
1317
+ if (current == null || current.name !== next.name || current.email !== next.email || current.phone !== next.phone || current.title !== next.title) {
1318
+ props.operations.push({
1319
+ kind: "putAlternateContact",
1320
+ accountId: props.accountId,
1321
+ accountName: props.accountName,
1322
+ contactType: next.contactType,
1323
+ name: next.name,
1324
+ email: next.email,
1325
+ phone: next.phone,
1326
+ title: next.title
1327
+ });
1328
+ }
1329
+ }
1330
+ for (const current of props.currentContacts) {
1331
+ if (!nextByType.has(current.contactType)) {
1332
+ props.operations.push({
1333
+ kind: "deleteAlternateContact",
1334
+ accountId: props.accountId,
1335
+ accountName: props.accountName,
1336
+ contactType: current.contactType
1337
+ });
1338
+ }
1339
+ }
1340
+ }
1019
1341
  export {
1020
1342
  diffStates
1021
1343
  };
@@ -44,7 +44,9 @@ const scanResponseSchema = v.strictObject({
44
44
  users: v.number(),
45
45
  groups: v.number(),
46
46
  permissionSets: v.number(),
47
- accountAssignments: v.number()
47
+ accountAssignments: v.number(),
48
+ policies: v.number(),
49
+ policyAttachments: v.number()
48
50
  }),
49
51
  state: stateSchema
50
52
  });
@@ -139,7 +141,7 @@ async function handler(event) {
139
141
  return validateResponse(response);
140
142
  }
141
143
  if (request.action === "scan") {
142
- const response = await handleScan({ s3Client, bucket, organizationsClient, ssoAdminClient, identityStoreClient });
144
+ const response = await handleScan({ s3Client, bucket, organizationsClient, ssoAdminClient, identityStoreClient, accountClient });
143
145
  return validateResponse(response);
144
146
  }
145
147
  if (request.action === "getStateUrl") {
@@ -228,7 +230,7 @@ function isS3PreconditionFailed(error) {
228
230
  async function handleScan(props) {
229
231
  const identityCenterInstanceArn = process.env.IDENTITY_CENTER_INSTANCE_ARN || void 0;
230
232
  const [organization, identityCenter] = await Promise.all([
231
- scanOrganization({ organizationsClient: props.organizationsClient }),
233
+ scanOrganization({ organizationsClient: props.organizationsClient, accountClient: props.accountClient }),
232
234
  scanIdentityCenter({
233
235
  ssoAdminClient: props.ssoAdminClient,
234
236
  identityStoreClient: props.identityStoreClient,
@@ -255,7 +257,9 @@ async function handleScan(props) {
255
257
  users: state.identityCenter.users.length,
256
258
  groups: state.identityCenter.groups.length,
257
259
  permissionSets: state.identityCenter.permissionSets.length,
258
- accountAssignments: state.identityCenter.accountAssignments.length
260
+ accountAssignments: state.identityCenter.accountAssignments.length,
261
+ policies: state.organization.policies?.length ?? 0,
262
+ policyAttachments: state.organization.policyAttachments?.length ?? 0
259
263
  },
260
264
  state
261
265
  };
@@ -31,7 +31,9 @@ const scanResponseSchema = v.strictObject({
31
31
  users: v.number(),
32
32
  groups: v.number(),
33
33
  permissionSets: v.number(),
34
- accountAssignments: v.number()
34
+ accountAssignments: v.number(),
35
+ policies: v.number(),
36
+ policyAttachments: v.number()
35
37
  }),
36
38
  state: stateSchema
37
39
  });
@@ -209,7 +211,8 @@ function buildEmptyStateForError() {
209
211
  groupMemberships: [],
210
212
  permissionSets: [],
211
213
  accountAssignments: [],
212
- accessRoles: []
214
+ accessRoles: [],
215
+ accessControlAttributes: []
213
216
  }
214
217
  };
215
218
  }