@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.
@@ -153,6 +153,22 @@ const provisionIdcPermissionSetOperationSchema = v.strictObject({
153
153
  permissionSetName: v.string(),
154
154
  targetScope: v.literal("ALL_PROVISIONED_ACCOUNTS")
155
155
  });
156
+ const permissionsBoundaryOperationValueSchema = v.union([
157
+ v.strictObject({ managedPolicyArn: v.string() }),
158
+ v.strictObject({
159
+ customerManagedPolicyName: v.string(),
160
+ customerManagedPolicyPath: v.string()
161
+ })
162
+ ]);
163
+ const putIdcPermissionSetPermissionsBoundaryOperationSchema = v.strictObject({
164
+ kind: v.literal("putIdcPermissionSetPermissionsBoundary"),
165
+ permissionSetName: v.string(),
166
+ permissionsBoundary: permissionsBoundaryOperationValueSchema
167
+ });
168
+ const deleteIdcPermissionSetPermissionsBoundaryOperationSchema = v.strictObject({
169
+ kind: v.literal("deleteIdcPermissionSetPermissionsBoundary"),
170
+ permissionSetName: v.string()
171
+ });
156
172
  const grantIdcAccountAssignmentOperationSchema = v.strictObject({
157
173
  kind: v.literal("grantIdcAccountAssignment"),
158
174
  accountName: v.string(),
@@ -167,6 +183,92 @@ const revokeIdcAccountAssignmentOperationSchema = v.strictObject({
167
183
  principalType: v.picklist(["GROUP", "USER"]),
168
184
  principalName: v.string()
169
185
  });
186
+ const setIdcAccessControlAttributesOperationSchema = v.strictObject({
187
+ kind: v.literal("setIdcAccessControlAttributes"),
188
+ attributes: v.array(
189
+ v.strictObject({
190
+ key: v.string(),
191
+ source: v.array(v.string())
192
+ })
193
+ )
194
+ });
195
+ const alternateContactTypeSchema = v.picklist([
196
+ "BILLING",
197
+ "OPERATIONS",
198
+ "SECURITY"
199
+ ]);
200
+ const putAlternateContactOperationSchema = v.strictObject({
201
+ kind: v.literal("putAlternateContact"),
202
+ accountId: v.string(),
203
+ accountName: v.string(),
204
+ contactType: alternateContactTypeSchema,
205
+ name: v.string(),
206
+ email: v.string(),
207
+ phone: v.string(),
208
+ title: v.optional(v.string())
209
+ });
210
+ const deleteAlternateContactOperationSchema = v.strictObject({
211
+ kind: v.literal("deleteAlternateContact"),
212
+ accountId: v.string(),
213
+ accountName: v.string(),
214
+ contactType: alternateContactTypeSchema
215
+ });
216
+ const registerDelegatedAdministratorOperationSchema = v.strictObject({
217
+ kind: v.literal("registerDelegatedAdministrator"),
218
+ accountId: v.string(),
219
+ accountName: v.string(),
220
+ servicePrincipal: v.string()
221
+ });
222
+ const deregisterDelegatedAdministratorOperationSchema = v.strictObject({
223
+ kind: v.literal("deregisterDelegatedAdministrator"),
224
+ accountId: v.string(),
225
+ accountName: v.string(),
226
+ servicePrincipal: v.string()
227
+ });
228
+ const createOrgPolicyOperationSchema = v.strictObject({
229
+ kind: v.literal("createOrgPolicy"),
230
+ policyName: v.string(),
231
+ policyType: v.picklist([
232
+ "SERVICE_CONTROL_POLICY",
233
+ "RESOURCE_CONTROL_POLICY",
234
+ "TAG_POLICY",
235
+ "AISERVICES_OPT_OUT_POLICY",
236
+ "BACKUP_POLICY"
237
+ ]),
238
+ description: v.string(),
239
+ content: v.string()
240
+ });
241
+ const updateOrgPolicyContentOperationSchema = v.strictObject({
242
+ kind: v.literal("updateOrgPolicyContent"),
243
+ policyId: v.string(),
244
+ policyName: v.string(),
245
+ content: v.string()
246
+ });
247
+ const updateOrgPolicyDescriptionOperationSchema = v.strictObject({
248
+ kind: v.literal("updateOrgPolicyDescription"),
249
+ policyId: v.string(),
250
+ policyName: v.string(),
251
+ description: v.string()
252
+ });
253
+ const attachOrgPolicyOperationSchema = v.strictObject({
254
+ kind: v.literal("attachOrgPolicy"),
255
+ policyId: v.string(),
256
+ policyName: v.string(),
257
+ targetId: v.string(),
258
+ targetName: v.string()
259
+ });
260
+ const detachOrgPolicyOperationSchema = v.strictObject({
261
+ kind: v.literal("detachOrgPolicy"),
262
+ policyId: v.string(),
263
+ policyName: v.string(),
264
+ targetId: v.string(),
265
+ targetName: v.string()
266
+ });
267
+ const deleteOrgPolicyOperationSchema = v.strictObject({
268
+ kind: v.literal("deleteOrgPolicy"),
269
+ policyId: v.string(),
270
+ policyName: v.string()
271
+ });
170
272
  const operationSchema = v.variant("kind", [
171
273
  moveAccountOperationSchema,
172
274
  createOuOperationSchema,
@@ -195,8 +297,21 @@ const operationSchema = v.variant("kind", [
195
297
  attachIdcCustomerManagedPolicyReferenceToPermissionSetOperationSchema,
196
298
  detachIdcCustomerManagedPolicyReferenceFromPermissionSetOperationSchema,
197
299
  provisionIdcPermissionSetOperationSchema,
300
+ putIdcPermissionSetPermissionsBoundaryOperationSchema,
301
+ deleteIdcPermissionSetPermissionsBoundaryOperationSchema,
198
302
  grantIdcAccountAssignmentOperationSchema,
199
- revokeIdcAccountAssignmentOperationSchema
303
+ revokeIdcAccountAssignmentOperationSchema,
304
+ createOrgPolicyOperationSchema,
305
+ updateOrgPolicyContentOperationSchema,
306
+ updateOrgPolicyDescriptionOperationSchema,
307
+ attachOrgPolicyOperationSchema,
308
+ detachOrgPolicyOperationSchema,
309
+ deleteOrgPolicyOperationSchema,
310
+ putAlternateContactOperationSchema,
311
+ deleteAlternateContactOperationSchema,
312
+ setIdcAccessControlAttributesOperationSchema,
313
+ registerDelegatedAdministratorOperationSchema,
314
+ deregisterDelegatedAdministratorOperationSchema
200
315
  ]);
201
316
  const unsupportedDiffKindSchema = v.picklist([
202
317
  "ambiguousOuRename",
package/dist/scanLogic.js CHANGED
@@ -4,31 +4,46 @@ import {
4
4
  ListUsersCommand
5
5
  } from "@aws-sdk/client-identitystore";
6
6
  import {
7
+ DescribeOrganizationCommand,
8
+ DescribePolicyCommand,
7
9
  ListAccountsCommand,
10
+ ListDelegatedAdministratorsCommand,
11
+ ListDelegatedServicesForAccountCommand,
8
12
  ListOrganizationalUnitsForParentCommand,
9
13
  ListParentsCommand,
14
+ ListPoliciesCommand,
10
15
  ListRootsCommand,
11
- ListTagsForResourceCommand
16
+ ListTagsForResourceCommand,
17
+ ListTargetsForPolicyCommand
12
18
  } from "@aws-sdk/client-organizations";
13
19
  import {
14
20
  DescribePermissionSetCommand,
15
21
  GetInlinePolicyForPermissionSetCommand,
22
+ GetPermissionsBoundaryForPermissionSetCommand,
16
23
  ListAccountAssignmentsCommand,
17
24
  ListAccountsForProvisionedPermissionSetCommand,
18
25
  ListCustomerManagedPolicyReferencesInPermissionSetCommand,
26
+ DescribeInstanceAccessControlAttributeConfigurationCommand,
19
27
  ListInstancesCommand,
20
28
  ListManagedPoliciesInPermissionSetCommand,
21
29
  ListPermissionSetsCommand
22
30
  } from "@aws-sdk/client-sso-admin";
31
+ import {
32
+ GetAlternateContactCommand
33
+ } from "@aws-sdk/client-account";
23
34
  import {
24
35
  createAccessRoleName
25
36
  } from "./state.js";
26
37
  async function scanOrganization(props) {
27
- const roots = await props.organizationsClient.send(new ListRootsCommand({}));
28
- const root = roots.Roots?.[0];
38
+ const [rootsResponse, orgResponse] = await Promise.all([
39
+ props.organizationsClient.send(new ListRootsCommand({})),
40
+ props.organizationsClient.send(new DescribeOrganizationCommand({}))
41
+ ]);
42
+ const root = rootsResponse.Roots?.[0];
29
43
  if (root?.Id == null) {
30
44
  throw new Error("No organization root found.");
31
45
  }
46
+ const managementAccountId = orgResponse.Organization?.MasterAccountId;
32
47
  const organizationalUnits = await collectOrganizationalUnits({
33
48
  organizationsClient: props.organizationsClient,
34
49
  parentId: root.Id
@@ -52,6 +67,11 @@ async function scanOrganization(props) {
52
67
  ResourceId: account.Id
53
68
  })
54
69
  );
70
+ const alternateContacts = await scanAlternateContacts({
71
+ accountClient: props.accountClient,
72
+ accountId: account.Id,
73
+ isManagementAccount: account.Id === managementAccountId
74
+ });
55
75
  accounts.push({
56
76
  id: account.Id,
57
77
  arn: account.Arn,
@@ -69,17 +89,138 @@ async function scanOrganization(props) {
69
89
  value: tag.Value ?? ""
70
90
  }
71
91
  ];
72
- })
92
+ }),
93
+ alternateContacts: alternateContacts.length > 0 ? alternateContacts : void 0
73
94
  });
74
95
  }
75
96
  nextToken = response.NextToken;
76
97
  } while (nextToken != null);
98
+ const [{ policies, policyAttachments }, delegatedAdministrators] = await Promise.all([
99
+ scanOrganizationPolicies({
100
+ organizationsClient: props.organizationsClient
101
+ }),
102
+ scanDelegatedAdministrators({
103
+ organizationsClient: props.organizationsClient
104
+ })
105
+ ]);
77
106
  return {
78
107
  rootId: root.Id,
79
108
  organizationalUnits,
80
- accounts
109
+ accounts,
110
+ policies,
111
+ policyAttachments,
112
+ delegatedAdministrators: delegatedAdministrators.length > 0 ? delegatedAdministrators : void 0
81
113
  };
82
114
  }
115
+ async function scanDelegatedAdministrators(props) {
116
+ const accountIds = new Array();
117
+ let nextToken;
118
+ do {
119
+ const response = await props.organizationsClient.send(
120
+ new ListDelegatedAdministratorsCommand({ NextToken: nextToken })
121
+ );
122
+ for (const admin of response.DelegatedAdministrators ?? []) {
123
+ if (admin.Id == null) {
124
+ continue;
125
+ }
126
+ accountIds.push(admin.Id);
127
+ }
128
+ nextToken = response.NextToken;
129
+ } while (nextToken != null);
130
+ const results = [];
131
+ for (const accountId of accountIds) {
132
+ let servicesNextToken;
133
+ do {
134
+ const response = await props.organizationsClient.send(
135
+ new ListDelegatedServicesForAccountCommand({
136
+ AccountId: accountId,
137
+ NextToken: servicesNextToken
138
+ })
139
+ );
140
+ for (const service of response.DelegatedServices ?? []) {
141
+ if (service.ServicePrincipal == null) {
142
+ continue;
143
+ }
144
+ results.push({ accountId, servicePrincipal: service.ServicePrincipal });
145
+ }
146
+ servicesNextToken = response.NextToken;
147
+ } while (servicesNextToken != null);
148
+ }
149
+ return results;
150
+ }
151
+ const ORG_POLICY_TYPES = [
152
+ "SERVICE_CONTROL_POLICY",
153
+ "RESOURCE_CONTROL_POLICY",
154
+ "TAG_POLICY",
155
+ "AISERVICES_OPT_OUT_POLICY",
156
+ "BACKUP_POLICY"
157
+ ];
158
+ async function scanOrganizationPolicies(props) {
159
+ const policies = [];
160
+ const policyAttachments = [];
161
+ for (const policyType of ORG_POLICY_TYPES) {
162
+ let nextToken;
163
+ const policyIds = [];
164
+ do {
165
+ const response = await props.organizationsClient.send(
166
+ new ListPoliciesCommand({ Filter: policyType, NextToken: nextToken })
167
+ );
168
+ for (const summary of response.Policies ?? []) {
169
+ if (summary.Id == null || summary.AwsManaged === true) {
170
+ continue;
171
+ }
172
+ policyIds.push(summary.Id);
173
+ }
174
+ nextToken = response.NextToken;
175
+ } while (nextToken != null);
176
+ for (const policyId of policyIds) {
177
+ const describeResponse = await props.organizationsClient.send(
178
+ new DescribePolicyCommand({ PolicyId: policyId })
179
+ );
180
+ const policy = describeResponse.Policy;
181
+ if (policy?.PolicySummary?.Id == null || policy.PolicySummary.Arn == null || policy.PolicySummary.Name == null) {
182
+ continue;
183
+ }
184
+ const content = policy.Content;
185
+ if (content == null || content.length === 0) {
186
+ continue;
187
+ }
188
+ policies.push({
189
+ id: policy.PolicySummary.Id,
190
+ arn: policy.PolicySummary.Arn,
191
+ name: policy.PolicySummary.Name,
192
+ description: policy.PolicySummary.Description ?? "",
193
+ type: policyType,
194
+ content
195
+ });
196
+ let targetsNextToken;
197
+ do {
198
+ const targetsResponse = await props.organizationsClient.send(
199
+ new ListTargetsForPolicyCommand({
200
+ PolicyId: policyId,
201
+ NextToken: targetsNextToken
202
+ })
203
+ );
204
+ for (const target of targetsResponse.Targets ?? []) {
205
+ if (target.TargetId == null || target.Type == null) {
206
+ continue;
207
+ }
208
+ const targetType = target.Type;
209
+ if (targetType !== "ROOT" && targetType !== "ORGANIZATIONAL_UNIT" && targetType !== "ACCOUNT") {
210
+ continue;
211
+ }
212
+ policyAttachments.push({
213
+ policyId,
214
+ targetId: target.TargetId,
215
+ targetType
216
+ });
217
+ }
218
+ targetsNextToken = targetsResponse.NextToken;
219
+ } while (targetsNextToken != null);
220
+ }
221
+ }
222
+ return { policies, policyAttachments };
223
+ }
83
224
  async function collectOrganizationalUnits(props) {
84
225
  const children = [];
85
226
  let nextToken;
@@ -122,7 +263,7 @@ async function scanIdentityCenter(props) {
122
263
  instances,
123
264
  requestedInstanceArn: props.requestedInstanceArn
124
265
  });
125
- const [users, groups, permissionSets] = await Promise.all([
266
+ const [users, groups, permissionSets, accessControlAttributes] = await Promise.all([
126
267
  listIdentityStoreUsers({
127
268
  identityStoreClient: props.identityStoreClient,
128
269
  identityStoreId: instance.identityStoreId
@@ -134,6 +275,10 @@ async function scanIdentityCenter(props) {
134
275
  listPermissionSets({
135
276
  ssoAdminClient: props.ssoAdminClient,
136
277
  instanceArn: instance.instanceArn
278
+ }),
279
+ scanAccessControlAttributes({
280
+ ssoAdminClient: props.ssoAdminClient,
281
+ instanceArn: instance.instanceArn
137
282
  })
138
283
  ]);
139
284
  const groupMemberships = await listGroupMemberships({
@@ -158,9 +303,30 @@ async function scanIdentityCenter(props) {
158
303
  groupMemberships,
159
304
  permissionSets,
160
305
  accountAssignments,
161
- accessRoles
306
+ accessRoles,
307
+ accessControlAttributes
162
308
  };
163
309
  }
310
+ async function scanAccessControlAttributes(props) {
311
+ let response;
312
+ try {
313
+ response = await props.ssoAdminClient.send(
314
+ new DescribeInstanceAccessControlAttributeConfigurationCommand({
315
+ InstanceArn: props.instanceArn
316
+ })
317
+ );
318
+ } catch (err) {
319
+ if (err != null && typeof err === "object" && "name" in err && err.name === "ResourceNotFoundException") {
320
+ return [];
321
+ }
322
+ throw err;
323
+ }
324
+ const attributes = response.InstanceAccessControlAttributeConfiguration?.AccessControlAttributes ?? [];
325
+ return attributes.filter((attr) => attr.Key != null).map((attr) => ({
326
+ key: attr.Key,
327
+ source: attr.Value?.Source ?? []
328
+ }));
329
+ }
164
330
  function selectIdentityCenterInstance(props) {
165
331
  if (props.requestedInstanceArn != null) {
166
332
  const selected2 = props.instances.find(
@@ -311,7 +477,8 @@ async function listPermissionSets(props) {
311
477
  const [
312
478
  inlinePolicy,
313
479
  awsManagedPolicies,
314
- customerManagedPolicies
480
+ customerManagedPolicies,
481
+ permissionsBoundary
315
482
  ] = await Promise.all([
316
483
  getInlinePolicyForPermissionSet({
317
484
  ssoAdminClient: props.ssoAdminClient,
@@ -327,6 +494,11 @@ async function listPermissionSets(props) {
327
494
  ssoAdminClient: props.ssoAdminClient,
328
495
  instanceArn: props.instanceArn,
329
496
  permissionSetArn: permissionSet.PermissionSetArn
497
+ }),
498
+ getPermissionsBoundaryForPermissionSet({
499
+ ssoAdminClient: props.ssoAdminClient,
500
+ instanceArn: props.instanceArn,
501
+ permissionSetArn: permissionSet.PermissionSetArn
330
502
  })
331
503
  ]);
332
504
  return {
@@ -336,7 +508,8 @@ async function listPermissionSets(props) {
336
508
  sessionDuration: permissionSet.SessionDuration ?? null,
337
509
  inlinePolicy,
338
510
  awsManagedPolicies,
339
- customerManagedPolicies
511
+ customerManagedPolicies,
512
+ permissionsBoundary
340
513
  };
341
514
  })
342
515
  );
@@ -354,6 +527,29 @@ async function getInlinePolicyForPermissionSet(props) {
354
527
  const inlinePolicy = response.InlinePolicy?.trim();
355
528
  return inlinePolicy != null && inlinePolicy.length > 0 ? inlinePolicy : null;
356
529
  }
530
+ async function getPermissionsBoundaryForPermissionSet(props) {
531
+ const response = await props.ssoAdminClient.send(
532
+ new GetPermissionsBoundaryForPermissionSetCommand({
533
+ InstanceArn: props.instanceArn,
534
+ PermissionSetArn: props.permissionSetArn
535
+ })
536
+ );
537
+ const boundary = response.PermissionsBoundary;
538
+ if (boundary == null) {
539
+ return null;
540
+ }
541
+ if (boundary.ManagedPolicyArn != null) {
542
+ return { managedPolicyArn: boundary.ManagedPolicyArn };
543
+ }
544
+ const ref = boundary.CustomerManagedPolicyReference;
545
+ if (ref?.Name != null) {
546
+ return {
547
+ customerManagedPolicyName: ref.Name,
548
+ customerManagedPolicyPath: ref.Path ?? "/"
549
+ };
550
+ }
551
+ return null;
552
+ }
357
553
  async function listManagedPoliciesInPermissionSet(props) {
358
554
  const managedPolicies = [];
359
555
  let nextToken;
@@ -451,6 +647,38 @@ async function listAccountsForPermissionSet(props) {
451
647
  } while (nextToken != null);
452
648
  return accountIds;
453
649
  }
650
+ const ALTERNATE_CONTACT_TYPES = ["BILLING", "OPERATIONS", "SECURITY"];
651
+ async function scanAlternateContacts(props) {
652
+ const results = await Promise.all(
653
+ ALTERNATE_CONTACT_TYPES.map(async (contactType) => {
654
+ try {
655
+ const response = await props.accountClient.send(
656
+ new GetAlternateContactCommand({
657
+ AccountId: props.isManagementAccount ? void 0 : props.accountId,
658
+ AlternateContactType: contactType
659
+ })
660
+ );
661
+ const c = response.AlternateContact;
662
+ if (c == null || c.EmailAddress == null || c.Name == null) {
663
+ return null;
664
+ }
665
+ return {
666
+ contactType,
667
+ name: c.Name,
668
+ email: c.EmailAddress,
669
+ phone: c.PhoneNumber ?? "",
670
+ ...c.Title != null ? { title: c.Title } : {}
671
+ };
672
+ } catch (error) {
673
+ if (error != null && typeof error === "object" && "name" in error && error.name === "ResourceNotFoundException") {
674
+ return null;
675
+ }
676
+ throw error;
677
+ }
678
+ })
679
+ );
680
+ return results.filter((c) => c != null);
681
+ }
454
682
  export {
455
683
  scanIdentityCenter,
456
684
  scanOrganization