@beesolve/aws-accounts 1.1.0 → 1.2.0

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.
package/dist/diff.js CHANGED
@@ -32,7 +32,16 @@ const operationExecutionPriority = {
32
32
  deleteIdcUser: 26,
33
33
  deleteIdcGroup: 27,
34
34
  deleteIdcPermissionSet: 28,
35
- deleteOu: 29
35
+ deleteOu: 29,
36
+ createOrgPolicy: 30,
37
+ updateOrgPolicyContent: 31,
38
+ updateOrgPolicyDescription: 32,
39
+ attachOrgPolicy: 33,
40
+ detachOrgPolicy: 34,
41
+ deleteOrgPolicy: 35,
42
+ putAlternateContact: 36,
43
+ deleteAlternateContact: 37,
44
+ setIdcAccessControlAttributes: 38
36
45
  };
37
46
  function diffStates(props) {
38
47
  const operations = [];
@@ -99,6 +108,13 @@ function diffStates(props) {
99
108
  )
100
109
  });
101
110
  }
111
+ diffAlternateContacts({
112
+ operations,
113
+ accountId: nextAccount.id,
114
+ accountName: nextAccount.name,
115
+ currentContacts: currentAccount.alternateContacts ?? [],
116
+ nextContacts: nextAccount.alternateContacts ?? []
117
+ });
102
118
  continue;
103
119
  }
104
120
  if (currentAccount.id === pendingCreationId || nextAccount.id === pendingCreationId || currentAccount.parentId === pendingCreationId || nextAccount.parentId === pendingCreationId) {
@@ -131,6 +147,13 @@ function diffStates(props) {
131
147
  toOuId: nextAccount.parentId,
132
148
  toOuName
133
149
  });
150
+ diffAlternateContacts({
151
+ operations,
152
+ accountId: nextAccount.id,
153
+ accountName: nextAccount.name,
154
+ currentContacts: currentAccount.alternateContacts ?? [],
155
+ nextContacts: nextAccount.alternateContacts ?? []
156
+ });
134
157
  }
135
158
  const graveyardOrganizationalUnit = currentOrganization.organizationalUnitByName.get("Graveyard");
136
159
  for (const currentAccount of currentOrganization.accounts) {
@@ -192,13 +215,17 @@ function diffStates(props) {
192
215
  const addedOrganizationalUnits = [];
193
216
  const removedOrganizationalUnits = [];
194
217
  for (const nextOrganizationalUnit of nextOrganization.organizationalUnits) {
195
- if (currentOrganization.organizationalUnitByName.has(nextOrganizationalUnit.name)) {
218
+ if (currentOrganization.organizationalUnitByName.has(
219
+ nextOrganizationalUnit.name
220
+ )) {
196
221
  continue;
197
222
  }
198
223
  addedOrganizationalUnits.push(nextOrganizationalUnit);
199
224
  }
200
225
  for (const currentOrganizationalUnit of currentOrganization.organizationalUnits) {
201
- if (nextOrganization.organizationalUnitByName.has(currentOrganizationalUnit.name)) {
226
+ if (nextOrganization.organizationalUnitByName.has(
227
+ currentOrganizationalUnit.name
228
+ )) {
202
229
  continue;
203
230
  }
204
231
  removedOrganizationalUnits.push(currentOrganizationalUnit);
@@ -436,7 +463,9 @@ function diffStates(props) {
436
463
  });
437
464
  }
438
465
  for (const nextPermissionSet of props.next.identityCenter.permissionSets) {
439
- const currentPermissionSet = currentIdcView.permissionSetsByName.get(nextPermissionSet.name);
466
+ const currentPermissionSet = currentIdcView.permissionSetsByName.get(
467
+ nextPermissionSet.name
468
+ );
440
469
  if (currentPermissionSet == null) {
441
470
  operations.push({
442
471
  kind: "createIdcPermissionSet",
@@ -484,7 +513,9 @@ function diffStates(props) {
484
513
  const currentAwsManagedPolicies = new Set(
485
514
  currentPermissionSet?.awsManagedPolicies ?? []
486
515
  );
487
- const nextAwsManagedPolicies = new Set(nextPermissionSet.awsManagedPolicies);
516
+ const nextAwsManagedPolicies = new Set(
517
+ nextPermissionSet.awsManagedPolicies
518
+ );
488
519
  for (const managedPolicyArn of nextAwsManagedPolicies) {
489
520
  if (currentAwsManagedPolicies.has(managedPolicyArn)) {
490
521
  continue;
@@ -517,7 +548,10 @@ function diffStates(props) {
517
548
  policy
518
549
  ])
519
550
  );
520
- for (const [policyKey, customerManagedPolicy] of nextCustomerManagedPolicies) {
551
+ for (const [
552
+ policyKey,
553
+ customerManagedPolicy
554
+ ] of nextCustomerManagedPolicies) {
521
555
  if (currentCustomerManagedPolicies.has(policyKey)) {
522
556
  continue;
523
557
  }
@@ -528,7 +562,10 @@ function diffStates(props) {
528
562
  customerManagedPolicyPath: customerManagedPolicy.path
529
563
  });
530
564
  }
531
- for (const [policyKey, customerManagedPolicy] of currentCustomerManagedPolicies) {
565
+ for (const [
566
+ policyKey,
567
+ customerManagedPolicy
568
+ ] of currentCustomerManagedPolicies) {
532
569
  if (nextCustomerManagedPolicies.has(policyKey)) {
533
570
  continue;
534
571
  }
@@ -595,6 +632,157 @@ function diffStates(props) {
595
632
  permissionSetName: removedPermissionSetName
596
633
  });
597
634
  }
635
+ const currentAccessControlAttributes = props.current.identityCenter.accessControlAttributes ?? [];
636
+ const nextAccessControlAttributes = props.next.identityCenter.accessControlAttributes ?? [];
637
+ if (JSON.stringify(
638
+ [...currentAccessControlAttributes].sort(
639
+ (a, b) => a.key.localeCompare(b.key)
640
+ )
641
+ ) !== JSON.stringify(
642
+ [...nextAccessControlAttributes].sort(
643
+ (a, b) => a.key.localeCompare(b.key)
644
+ )
645
+ )) {
646
+ operations.push({
647
+ kind: "setIdcAccessControlAttributes",
648
+ attributes: nextAccessControlAttributes
649
+ });
650
+ }
651
+ const currentPolicies = props.current.organization.policies ?? [];
652
+ const nextPolicies = props.next.organization.policies ?? [];
653
+ const currentPolicyAttachments = props.current.organization.policyAttachments ?? [];
654
+ const nextPolicyAttachments = props.next.organization.policyAttachments ?? [];
655
+ const currentPoliciesByName = new Map(
656
+ currentPolicies.map((p) => [`${p.type}|${p.name}`, p])
657
+ );
658
+ const nextPoliciesByName = new Map(
659
+ nextPolicies.map((p) => [`${p.type}|${p.name}`, p])
660
+ );
661
+ const currentAttachmentsByKey = new Set(
662
+ currentPolicyAttachments.map((a) => `${a.policyId}|${a.targetId}`)
663
+ );
664
+ const nextPoliciesByPendingId = /* @__PURE__ */ new Map();
665
+ for (const nextPolicy of nextPolicies) {
666
+ const currentPolicy = currentPoliciesByName.get(
667
+ `${nextPolicy.type}|${nextPolicy.name}`
668
+ );
669
+ if (currentPolicy == null) {
670
+ operations.push({
671
+ kind: "createOrgPolicy",
672
+ policyName: nextPolicy.name,
673
+ policyType: nextPolicy.type,
674
+ description: nextPolicy.description,
675
+ content: nextPolicy.content
676
+ });
677
+ nextPoliciesByPendingId.set(nextPolicy.id, nextPolicy);
678
+ continue;
679
+ }
680
+ if (normalizeJsonContent(currentPolicy.content) !== normalizeJsonContent(nextPolicy.content)) {
681
+ operations.push({
682
+ kind: "updateOrgPolicyContent",
683
+ policyId: currentPolicy.id,
684
+ policyName: currentPolicy.name,
685
+ content: nextPolicy.content
686
+ });
687
+ }
688
+ if (currentPolicy.description !== nextPolicy.description) {
689
+ operations.push({
690
+ kind: "updateOrgPolicyDescription",
691
+ policyId: currentPolicy.id,
692
+ policyName: currentPolicy.name,
693
+ description: nextPolicy.description
694
+ });
695
+ }
696
+ }
697
+ const nextPoliciesById = new Map(nextPolicies.map((p) => [p.id, p]));
698
+ const currentPoliciesById = new Map(currentPolicies.map((p) => [p.id, p]));
699
+ const nextOuNameById = new Map(
700
+ props.next.organization.organizationalUnits.map((ou) => [ou.id, ou.name])
701
+ );
702
+ const nextAccountNameById = new Map(
703
+ props.next.organization.accounts.map((account) => [account.id, account.name])
704
+ );
705
+ const currentOuNameById = new Map(
706
+ props.current.organization.organizationalUnits.map((ou) => [ou.id, ou.name])
707
+ );
708
+ const currentAccountNameById = new Map(
709
+ props.current.organization.accounts.map((account) => [account.id, account.name])
710
+ );
711
+ function resolveNextTargetName(targetId, targetType) {
712
+ if (targetType === "ROOT") return "root";
713
+ if (targetType === "ORGANIZATIONAL_UNIT") return nextOuNameById.get(targetId) ?? "unknown";
714
+ return nextAccountNameById.get(targetId) ?? "unknown";
715
+ }
716
+ function resolveCurrentTargetName(targetId, targetType) {
717
+ if (targetType === "ROOT") return "root";
718
+ if (targetType === "ORGANIZATIONAL_UNIT") return currentOuNameById.get(targetId) ?? "unknown";
719
+ return currentAccountNameById.get(targetId) ?? "unknown";
720
+ }
721
+ for (const nextAttachment of nextPolicyAttachments) {
722
+ if (nextAttachment.policyId === pendingCreationId) {
723
+ continue;
724
+ }
725
+ if (nextAttachment.targetId === pendingCreationId) {
726
+ continue;
727
+ }
728
+ const attachmentKey = `${nextAttachment.policyId}|${nextAttachment.targetId}`;
729
+ if (currentAttachmentsByKey.has(attachmentKey)) {
730
+ continue;
731
+ }
732
+ const policy = nextPoliciesById.get(nextAttachment.policyId) ?? currentPoliciesById.get(nextAttachment.policyId);
733
+ if (policy == null) {
734
+ continue;
735
+ }
736
+ operations.push({
737
+ kind: "attachOrgPolicy",
738
+ policyId: nextAttachment.policyId,
739
+ policyName: policy.name,
740
+ targetId: nextAttachment.targetId,
741
+ targetName: resolveNextTargetName(
742
+ nextAttachment.targetId,
743
+ nextAttachment.targetType
744
+ )
745
+ });
746
+ }
747
+ const nextAttachmentKeys = new Set(
748
+ nextPolicyAttachments.filter(
749
+ (a) => a.policyId !== pendingCreationId && a.targetId !== pendingCreationId
750
+ ).map((a) => `${a.policyId}|${a.targetId}`)
751
+ );
752
+ const nextPolicyIds = new Set(
753
+ nextPolicies.filter((p) => p.id !== pendingCreationId).map((p) => p.id)
754
+ );
755
+ for (const currentAttachment of currentPolicyAttachments) {
756
+ const attachmentKey = `${currentAttachment.policyId}|${currentAttachment.targetId}`;
757
+ const policyBeingDeleted = !nextPolicyIds.has(currentAttachment.policyId) && currentPoliciesById.has(currentAttachment.policyId);
758
+ if (nextAttachmentKeys.has(attachmentKey) && !policyBeingDeleted) {
759
+ continue;
760
+ }
761
+ const policy = currentPoliciesById.get(currentAttachment.policyId);
762
+ if (policy == null) {
763
+ continue;
764
+ }
765
+ operations.push({
766
+ kind: "detachOrgPolicy",
767
+ policyId: currentAttachment.policyId,
768
+ policyName: policy.name,
769
+ targetId: currentAttachment.targetId,
770
+ targetName: resolveCurrentTargetName(
771
+ currentAttachment.targetId,
772
+ currentAttachment.targetType
773
+ )
774
+ });
775
+ }
776
+ for (const currentPolicy of currentPolicies) {
777
+ if (nextPoliciesByName.has(`${currentPolicy.type}|${currentPolicy.name}`)) {
778
+ continue;
779
+ }
780
+ operations.push({
781
+ kind: "deleteOrgPolicy",
782
+ policyId: currentPolicy.id,
783
+ policyName: currentPolicy.name
784
+ });
785
+ }
598
786
  operations.sort((left, right) => {
599
787
  const priorityComparison = getOperationExecutionPriority(left) - getOperationExecutionPriority(right);
600
788
  if (priorityComparison !== 0) {
@@ -830,8 +1018,34 @@ function getOperationSortKey(operation) {
830
1018
  operation.principalName
831
1019
  ].join("|");
832
1020
  }
1021
+ if (operation.kind === "createOrgPolicy") {
1022
+ return `${operation.kind}|${operation.policyType}|${operation.policyName}`;
1023
+ }
1024
+ if (operation.kind === "updateOrgPolicyContent" || operation.kind === "updateOrgPolicyDescription" || operation.kind === "deleteOrgPolicy") {
1025
+ return `${operation.kind}|${operation.policyName}`;
1026
+ }
1027
+ if (operation.kind === "attachOrgPolicy" || operation.kind === "detachOrgPolicy") {
1028
+ return [operation.kind, operation.policyName, operation.targetName].join(
1029
+ "|"
1030
+ );
1031
+ }
1032
+ if (operation.kind === "putAlternateContact" || operation.kind === "deleteAlternateContact") {
1033
+ return [operation.kind, operation.accountName, operation.contactType].join(
1034
+ "|"
1035
+ );
1036
+ }
1037
+ if (operation.kind === "setIdcAccessControlAttributes") {
1038
+ return operation.kind;
1039
+ }
833
1040
  return "zzzz";
834
1041
  }
1042
+ function normalizeJsonContent(content) {
1043
+ try {
1044
+ return JSON.stringify(sortJsonValue(JSON.parse(content)));
1045
+ } catch {
1046
+ return content;
1047
+ }
1048
+ }
835
1049
  function normalizeAccountTags(tags) {
836
1050
  if (tags == null || tags.length === 0) {
837
1051
  return [];
@@ -949,7 +1163,9 @@ function normalizeIdentityCenterState(props) {
949
1163
  };
950
1164
  }
951
1165
  function createNormalizedIdcMembershipKey(props) {
952
- return [props.membership.groupDisplayName, props.membership.userName].join("|");
1166
+ return [props.membership.groupDisplayName, props.membership.userName].join(
1167
+ "|"
1168
+ );
953
1169
  }
954
1170
  function resolveAssignmentPrincipalName(props) {
955
1171
  if (props.principalType === "GROUP") {
@@ -1016,6 +1232,37 @@ function isResolvableOrganizationalUnitId(props) {
1016
1232
  }
1017
1233
  return props.organizationalUnitNameById.has(props.organizationalUnitId);
1018
1234
  }
1235
+ function diffAlternateContacts(props) {
1236
+ const currentByType = new Map(
1237
+ props.currentContacts.map((c) => [c.contactType, c])
1238
+ );
1239
+ const nextByType = new Map(props.nextContacts.map((c) => [c.contactType, c]));
1240
+ for (const next of props.nextContacts) {
1241
+ const current = currentByType.get(next.contactType);
1242
+ if (current == null || current.name !== next.name || current.email !== next.email || current.phone !== next.phone || current.title !== next.title) {
1243
+ props.operations.push({
1244
+ kind: "putAlternateContact",
1245
+ accountId: props.accountId,
1246
+ accountName: props.accountName,
1247
+ contactType: next.contactType,
1248
+ name: next.name,
1249
+ email: next.email,
1250
+ phone: next.phone,
1251
+ title: next.title
1252
+ });
1253
+ }
1254
+ }
1255
+ for (const current of props.currentContacts) {
1256
+ if (!nextByType.has(current.contactType)) {
1257
+ props.operations.push({
1258
+ kind: "deleteAlternateContact",
1259
+ accountId: props.accountId,
1260
+ accountName: props.accountName,
1261
+ contactType: current.contactType
1262
+ });
1263
+ }
1264
+ }
1265
+ }
1019
1266
  export {
1020
1267
  diffStates
1021
1268
  };
@@ -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
  }
@@ -167,6 +167,79 @@ const revokeIdcAccountAssignmentOperationSchema = v.strictObject({
167
167
  principalType: v.picklist(["GROUP", "USER"]),
168
168
  principalName: v.string()
169
169
  });
170
+ const setIdcAccessControlAttributesOperationSchema = v.strictObject({
171
+ kind: v.literal("setIdcAccessControlAttributes"),
172
+ attributes: v.array(
173
+ v.strictObject({
174
+ key: v.string(),
175
+ source: v.array(v.string())
176
+ })
177
+ )
178
+ });
179
+ const alternateContactTypeSchema = v.picklist([
180
+ "BILLING",
181
+ "OPERATIONS",
182
+ "SECURITY"
183
+ ]);
184
+ const putAlternateContactOperationSchema = v.strictObject({
185
+ kind: v.literal("putAlternateContact"),
186
+ accountId: v.string(),
187
+ accountName: v.string(),
188
+ contactType: alternateContactTypeSchema,
189
+ name: v.string(),
190
+ email: v.string(),
191
+ phone: v.string(),
192
+ title: v.optional(v.string())
193
+ });
194
+ const deleteAlternateContactOperationSchema = v.strictObject({
195
+ kind: v.literal("deleteAlternateContact"),
196
+ accountId: v.string(),
197
+ accountName: v.string(),
198
+ contactType: alternateContactTypeSchema
199
+ });
200
+ const createOrgPolicyOperationSchema = v.strictObject({
201
+ kind: v.literal("createOrgPolicy"),
202
+ policyName: v.string(),
203
+ policyType: v.picklist([
204
+ "SERVICE_CONTROL_POLICY",
205
+ "RESOURCE_CONTROL_POLICY",
206
+ "TAG_POLICY",
207
+ "AISERVICES_OPT_OUT_POLICY"
208
+ ]),
209
+ description: v.string(),
210
+ content: v.string()
211
+ });
212
+ const updateOrgPolicyContentOperationSchema = v.strictObject({
213
+ kind: v.literal("updateOrgPolicyContent"),
214
+ policyId: v.string(),
215
+ policyName: v.string(),
216
+ content: v.string()
217
+ });
218
+ const updateOrgPolicyDescriptionOperationSchema = v.strictObject({
219
+ kind: v.literal("updateOrgPolicyDescription"),
220
+ policyId: v.string(),
221
+ policyName: v.string(),
222
+ description: v.string()
223
+ });
224
+ const attachOrgPolicyOperationSchema = v.strictObject({
225
+ kind: v.literal("attachOrgPolicy"),
226
+ policyId: v.string(),
227
+ policyName: v.string(),
228
+ targetId: v.string(),
229
+ targetName: v.string()
230
+ });
231
+ const detachOrgPolicyOperationSchema = v.strictObject({
232
+ kind: v.literal("detachOrgPolicy"),
233
+ policyId: v.string(),
234
+ policyName: v.string(),
235
+ targetId: v.string(),
236
+ targetName: v.string()
237
+ });
238
+ const deleteOrgPolicyOperationSchema = v.strictObject({
239
+ kind: v.literal("deleteOrgPolicy"),
240
+ policyId: v.string(),
241
+ policyName: v.string()
242
+ });
170
243
  const operationSchema = v.variant("kind", [
171
244
  moveAccountOperationSchema,
172
245
  createOuOperationSchema,
@@ -196,7 +269,16 @@ const operationSchema = v.variant("kind", [
196
269
  detachIdcCustomerManagedPolicyReferenceFromPermissionSetOperationSchema,
197
270
  provisionIdcPermissionSetOperationSchema,
198
271
  grantIdcAccountAssignmentOperationSchema,
199
- revokeIdcAccountAssignmentOperationSchema
272
+ revokeIdcAccountAssignmentOperationSchema,
273
+ createOrgPolicyOperationSchema,
274
+ updateOrgPolicyContentOperationSchema,
275
+ updateOrgPolicyDescriptionOperationSchema,
276
+ attachOrgPolicyOperationSchema,
277
+ detachOrgPolicyOperationSchema,
278
+ deleteOrgPolicyOperationSchema,
279
+ putAlternateContactOperationSchema,
280
+ deleteAlternateContactOperationSchema,
281
+ setIdcAccessControlAttributesOperationSchema
200
282
  ]);
201
283
  const unsupportedDiffKindSchema = v.picklist([
202
284
  "ambiguousOuRename",