@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.
@@ -9,7 +9,7 @@ import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
9
9
  import { OrganizationsClient as OrganizationsClient3 } from "@aws-sdk/client-organizations";
10
10
  import { SSOAdminClient as SSOAdminClient3 } from "@aws-sdk/client-sso-admin";
11
11
  import { IdentitystoreClient as IdentitystoreClient3 } from "@aws-sdk/client-identitystore";
12
- import { AccountClient as AccountClient2 } from "@aws-sdk/client-account";
12
+ import { AccountClient as AccountClient3 } from "@aws-sdk/client-account";
13
13
 
14
14
  // node_modules/valibot/dist/index.mjs
15
15
  var store$4;
@@ -806,6 +806,79 @@ var revokeIdcAccountAssignmentOperationSchema = strictObject({
806
806
  principalType: picklist(["GROUP", "USER"]),
807
807
  principalName: string()
808
808
  });
809
+ var setIdcAccessControlAttributesOperationSchema = strictObject({
810
+ kind: literal("setIdcAccessControlAttributes"),
811
+ attributes: array(
812
+ strictObject({
813
+ key: string(),
814
+ source: array(string())
815
+ })
816
+ )
817
+ });
818
+ var alternateContactTypeSchema = picklist([
819
+ "BILLING",
820
+ "OPERATIONS",
821
+ "SECURITY"
822
+ ]);
823
+ var putAlternateContactOperationSchema = strictObject({
824
+ kind: literal("putAlternateContact"),
825
+ accountId: string(),
826
+ accountName: string(),
827
+ contactType: alternateContactTypeSchema,
828
+ name: string(),
829
+ email: string(),
830
+ phone: string(),
831
+ title: optional(string())
832
+ });
833
+ var deleteAlternateContactOperationSchema = strictObject({
834
+ kind: literal("deleteAlternateContact"),
835
+ accountId: string(),
836
+ accountName: string(),
837
+ contactType: alternateContactTypeSchema
838
+ });
839
+ var createOrgPolicyOperationSchema = strictObject({
840
+ kind: literal("createOrgPolicy"),
841
+ policyName: string(),
842
+ policyType: picklist([
843
+ "SERVICE_CONTROL_POLICY",
844
+ "RESOURCE_CONTROL_POLICY",
845
+ "TAG_POLICY",
846
+ "AISERVICES_OPT_OUT_POLICY"
847
+ ]),
848
+ description: string(),
849
+ content: string()
850
+ });
851
+ var updateOrgPolicyContentOperationSchema = strictObject({
852
+ kind: literal("updateOrgPolicyContent"),
853
+ policyId: string(),
854
+ policyName: string(),
855
+ content: string()
856
+ });
857
+ var updateOrgPolicyDescriptionOperationSchema = strictObject({
858
+ kind: literal("updateOrgPolicyDescription"),
859
+ policyId: string(),
860
+ policyName: string(),
861
+ description: string()
862
+ });
863
+ var attachOrgPolicyOperationSchema = strictObject({
864
+ kind: literal("attachOrgPolicy"),
865
+ policyId: string(),
866
+ policyName: string(),
867
+ targetId: string(),
868
+ targetName: string()
869
+ });
870
+ var detachOrgPolicyOperationSchema = strictObject({
871
+ kind: literal("detachOrgPolicy"),
872
+ policyId: string(),
873
+ policyName: string(),
874
+ targetId: string(),
875
+ targetName: string()
876
+ });
877
+ var deleteOrgPolicyOperationSchema = strictObject({
878
+ kind: literal("deleteOrgPolicy"),
879
+ policyId: string(),
880
+ policyName: string()
881
+ });
809
882
  var operationSchema = variant("kind", [
810
883
  moveAccountOperationSchema,
811
884
  createOuOperationSchema,
@@ -835,7 +908,16 @@ var operationSchema = variant("kind", [
835
908
  detachIdcCustomerManagedPolicyReferenceFromPermissionSetOperationSchema,
836
909
  provisionIdcPermissionSetOperationSchema,
837
910
  grantIdcAccountAssignmentOperationSchema,
838
- revokeIdcAccountAssignmentOperationSchema
911
+ revokeIdcAccountAssignmentOperationSchema,
912
+ createOrgPolicyOperationSchema,
913
+ updateOrgPolicyContentOperationSchema,
914
+ updateOrgPolicyDescriptionOperationSchema,
915
+ attachOrgPolicyOperationSchema,
916
+ detachOrgPolicyOperationSchema,
917
+ deleteOrgPolicyOperationSchema,
918
+ putAlternateContactOperationSchema,
919
+ deleteAlternateContactOperationSchema,
920
+ setIdcAccessControlAttributesOperationSchema
839
921
  ]);
840
922
  var unsupportedDiffKindSchema = picklist([
841
923
  "ambiguousOuRename",
@@ -885,10 +967,36 @@ var organizationalUnitSchema = strictObject({
885
967
  arn: nonEmptyString,
886
968
  name: nonEmptyString
887
969
  });
970
+ var orgPolicyTypeSchema = picklist([
971
+ "SERVICE_CONTROL_POLICY",
972
+ "RESOURCE_CONTROL_POLICY",
973
+ "TAG_POLICY",
974
+ "AISERVICES_OPT_OUT_POLICY"
975
+ ]);
976
+ var orgPolicySchema = strictObject({
977
+ id: nonEmptyString,
978
+ arn: nonEmptyString,
979
+ name: nonEmptyString,
980
+ description: string(),
981
+ type: orgPolicyTypeSchema,
982
+ content: nonEmptyString
983
+ });
984
+ var orgPolicyAttachmentSchema = strictObject({
985
+ policyId: nonEmptyString,
986
+ targetId: nonEmptyString,
987
+ targetType: picklist(["ROOT", "ORGANIZATIONAL_UNIT", "ACCOUNT"])
988
+ });
888
989
  var accountTagSchema = strictObject({
889
990
  key: nonEmptyString,
890
991
  value: string()
891
992
  });
993
+ var alternateContactSchema = strictObject({
994
+ contactType: picklist(["BILLING", "OPERATIONS", "SECURITY"]),
995
+ name: string(),
996
+ email: string(),
997
+ phone: string(),
998
+ title: optional(string())
999
+ });
892
1000
  var accountSchema = strictObject({
893
1001
  id: nonEmptyString,
894
1002
  arn: nonEmptyString,
@@ -896,7 +1004,8 @@ var accountSchema = strictObject({
896
1004
  email: nonEmptyString,
897
1005
  status: nonEmptyString,
898
1006
  parentId: nonEmptyString,
899
- tags: array(accountTagSchema)
1007
+ tags: array(accountTagSchema),
1008
+ alternateContacts: optional(array(alternateContactSchema))
900
1009
  });
901
1010
  var userSchema = strictObject({
902
1011
  userId: nonEmptyString,
@@ -940,13 +1049,19 @@ var accessRoleSchema = strictObject({
940
1049
  principalType: principalTypeSchema,
941
1050
  roleName: nonEmptyString
942
1051
  });
1052
+ var accessControlAttributeSchema = strictObject({
1053
+ key: nonEmptyString,
1054
+ source: array(nonEmptyString)
1055
+ });
943
1056
  var stateSchema = strictObject({
944
1057
  version: nonEmptyString,
945
1058
  generatedAt: nonEmptyString,
946
1059
  organization: strictObject({
947
1060
  rootId: nonEmptyString,
948
1061
  organizationalUnits: array(organizationalUnitSchema),
949
- accounts: array(accountSchema)
1062
+ accounts: array(accountSchema),
1063
+ policies: optional(array(orgPolicySchema)),
1064
+ policyAttachments: optional(array(orgPolicyAttachmentSchema))
950
1065
  }),
951
1066
  identityCenter: strictObject({
952
1067
  instanceArn: nonEmptyString,
@@ -956,10 +1071,13 @@ var stateSchema = strictObject({
956
1071
  groupMemberships: array(groupMembershipSchema),
957
1072
  permissionSets: array(permissionSetSchema),
958
1073
  accountAssignments: array(accountAssignmentSchema),
959
- accessRoles: array(accessRoleSchema)
1074
+ accessRoles: array(accessRoleSchema),
1075
+ accessControlAttributes: array(accessControlAttributeSchema)
960
1076
  })
961
1077
  });
962
1078
  function createWorkingState(props) {
1079
+ const policies = props.state.organization.policies ?? [];
1080
+ const policyAttachments = props.state.organization.policyAttachments ?? [];
963
1081
  return {
964
1082
  version: props.state.version,
965
1083
  generatedAt: props.state.generatedAt,
@@ -973,6 +1091,13 @@ function createWorkingState(props) {
973
1091
  accountsByName: toRecordByProperty(
974
1092
  props.state.organization.accounts,
975
1093
  "name"
1094
+ ),
1095
+ policiesById: toRecordByProperty(policies, "id"),
1096
+ policiesByName: toRecordByProperty(policies, "name"),
1097
+ policyAttachments: structuredClone(policyAttachments),
1098
+ policyAttachmentsByKey: toRecordByProperty(
1099
+ policyAttachments,
1100
+ createOrgPolicyAttachmentKey
976
1101
  )
977
1102
  },
978
1103
  identityCenter: createWorkingIdentityCenterState({
@@ -989,7 +1114,11 @@ function materializeWorkingState(props) {
989
1114
  organizationalUnits: Object.values(
990
1115
  props.workingState.organization.organizationalUnitsById
991
1116
  ),
992
- accounts: Object.values(props.workingState.organization.accountsById)
1117
+ accounts: Object.values(props.workingState.organization.accountsById),
1118
+ policies: Object.values(props.workingState.organization.policiesById),
1119
+ policyAttachments: structuredClone(
1120
+ props.workingState.organization.policyAttachments
1121
+ )
993
1122
  },
994
1123
  identityCenter: {
995
1124
  instanceArn: props.workingState.identityCenter.instanceArn,
@@ -1007,6 +1136,9 @@ function materializeWorkingState(props) {
1007
1136
  ),
1008
1137
  accessRoles: structuredClone(
1009
1138
  props.workingState.identityCenter.accessRoles
1139
+ ),
1140
+ accessControlAttributes: structuredClone(
1141
+ props.workingState.identityCenter.accessControlAttributes
1010
1142
  )
1011
1143
  }
1012
1144
  };
@@ -1361,6 +1493,101 @@ function removeAccountAssignmentFromWorkingState(props) {
1361
1493
  })
1362
1494
  };
1363
1495
  }
1496
+ function createOrgPolicyAttachmentKey(props) {
1497
+ return [props.policyId, props.targetId].join("|");
1498
+ }
1499
+ function upsertOrgPolicyInWorkingState(props) {
1500
+ const currentPolicy = props.workingState.organization.policiesById[props.policy.id];
1501
+ if (currentPolicy != null && currentPolicy.id === props.policy.id && currentPolicy.arn === props.policy.arn && currentPolicy.name === props.policy.name && currentPolicy.description === props.policy.description && currentPolicy.type === props.policy.type && currentPolicy.content === props.policy.content) {
1502
+ return props.workingState;
1503
+ }
1504
+ const remainingPolicies = Object.values(
1505
+ props.workingState.organization.policiesById
1506
+ ).filter((p) => p.id !== props.policy.id);
1507
+ const nextPolicies = [...remainingPolicies, props.policy];
1508
+ return {
1509
+ ...props.workingState,
1510
+ organization: {
1511
+ ...props.workingState.organization,
1512
+ policiesById: toRecordByProperty(nextPolicies, "id"),
1513
+ policiesByName: toRecordByProperty(nextPolicies, "name")
1514
+ }
1515
+ };
1516
+ }
1517
+ function removeOrgPolicyFromWorkingState(props) {
1518
+ if (props.workingState.organization.policiesById[props.policyId] == null) {
1519
+ return props.workingState;
1520
+ }
1521
+ const nextPolicies = Object.values(
1522
+ props.workingState.organization.policiesById
1523
+ ).filter((p) => p.id !== props.policyId);
1524
+ const nextAttachments = props.workingState.organization.policyAttachments.filter(
1525
+ (a) => a.policyId !== props.policyId
1526
+ );
1527
+ return {
1528
+ ...props.workingState,
1529
+ organization: {
1530
+ ...props.workingState.organization,
1531
+ policiesById: toRecordByProperty(nextPolicies, "id"),
1532
+ policiesByName: toRecordByProperty(nextPolicies, "name"),
1533
+ policyAttachments: nextAttachments,
1534
+ policyAttachmentsByKey: toRecordByProperty(
1535
+ nextAttachments,
1536
+ createOrgPolicyAttachmentKey
1537
+ )
1538
+ }
1539
+ };
1540
+ }
1541
+ function addOrgPolicyAttachmentToWorkingState(props) {
1542
+ const key = createOrgPolicyAttachmentKey({
1543
+ policyId: props.attachment.policyId,
1544
+ targetId: props.attachment.targetId
1545
+ });
1546
+ if (props.workingState.organization.policyAttachmentsByKey[key] != null) {
1547
+ return props.workingState;
1548
+ }
1549
+ const nextAttachments = [
1550
+ ...props.workingState.organization.policyAttachments,
1551
+ props.attachment
1552
+ ];
1553
+ return {
1554
+ ...props.workingState,
1555
+ organization: {
1556
+ ...props.workingState.organization,
1557
+ policyAttachments: nextAttachments,
1558
+ policyAttachmentsByKey: toRecordByProperty(
1559
+ nextAttachments,
1560
+ createOrgPolicyAttachmentKey
1561
+ )
1562
+ }
1563
+ };
1564
+ }
1565
+ function removeOrgPolicyAttachmentFromWorkingState(props) {
1566
+ const key = createOrgPolicyAttachmentKey({
1567
+ policyId: props.policyId,
1568
+ targetId: props.targetId
1569
+ });
1570
+ if (props.workingState.organization.policyAttachmentsByKey[key] == null) {
1571
+ return props.workingState;
1572
+ }
1573
+ const nextAttachments = props.workingState.organization.policyAttachments.filter(
1574
+ (a) => createOrgPolicyAttachmentKey({
1575
+ policyId: a.policyId,
1576
+ targetId: a.targetId
1577
+ }) !== key
1578
+ );
1579
+ return {
1580
+ ...props.workingState,
1581
+ organization: {
1582
+ ...props.workingState.organization,
1583
+ policyAttachments: nextAttachments,
1584
+ policyAttachmentsByKey: toRecordByProperty(
1585
+ nextAttachments,
1586
+ createOrgPolicyAttachmentKey
1587
+ )
1588
+ }
1589
+ };
1590
+ }
1364
1591
  function createAccessRoleName(assignment) {
1365
1592
  return `AWSReservedSSO_${assignment.permissionSetArn.split("/").at(-1) ?? "PermissionSet"}_${assignment.accountId}`;
1366
1593
  }
@@ -1395,7 +1622,10 @@ function createWorkingIdentityCenterState(props) {
1395
1622
  ),
1396
1623
  accessRoles: createAccessRoles({
1397
1624
  accountAssignments
1398
- })
1625
+ }),
1626
+ accessControlAttributes: structuredClone(
1627
+ props.identityCenter.accessControlAttributes ?? []
1628
+ )
1399
1629
  };
1400
1630
  }
1401
1631
  function materializeWorkingIdentityCenterState(props) {
@@ -1409,7 +1639,10 @@ function materializeWorkingIdentityCenterState(props) {
1409
1639
  accountAssignments: structuredClone(
1410
1640
  props.identityCenter.accountAssignments
1411
1641
  ),
1412
- accessRoles: structuredClone(props.identityCenter.accessRoles)
1642
+ accessRoles: structuredClone(props.identityCenter.accessRoles),
1643
+ accessControlAttributes: structuredClone(
1644
+ props.identityCenter.accessControlAttributes
1645
+ )
1413
1646
  };
1414
1647
  }
1415
1648
  function createAccessRoles(props) {
@@ -1448,11 +1681,15 @@ import {
1448
1681
  ListUsersCommand
1449
1682
  } from "@aws-sdk/client-identitystore";
1450
1683
  import {
1684
+ DescribeOrganizationCommand,
1685
+ DescribePolicyCommand,
1451
1686
  ListAccountsCommand,
1452
1687
  ListOrganizationalUnitsForParentCommand,
1453
1688
  ListParentsCommand,
1689
+ ListPoliciesCommand,
1454
1690
  ListRootsCommand,
1455
- ListTagsForResourceCommand
1691
+ ListTagsForResourceCommand,
1692
+ ListTargetsForPolicyCommand
1456
1693
  } from "@aws-sdk/client-organizations";
1457
1694
  import {
1458
1695
  DescribePermissionSetCommand,
@@ -1460,16 +1697,24 @@ import {
1460
1697
  ListAccountAssignmentsCommand,
1461
1698
  ListAccountsForProvisionedPermissionSetCommand,
1462
1699
  ListCustomerManagedPolicyReferencesInPermissionSetCommand,
1700
+ DescribeInstanceAccessControlAttributeConfigurationCommand,
1463
1701
  ListInstancesCommand,
1464
1702
  ListManagedPoliciesInPermissionSetCommand,
1465
1703
  ListPermissionSetsCommand
1466
1704
  } from "@aws-sdk/client-sso-admin";
1705
+ import {
1706
+ GetAlternateContactCommand
1707
+ } from "@aws-sdk/client-account";
1467
1708
  async function scanOrganization(props) {
1468
- const roots = await props.organizationsClient.send(new ListRootsCommand({}));
1469
- const root = roots.Roots?.[0];
1709
+ const [rootsResponse, orgResponse] = await Promise.all([
1710
+ props.organizationsClient.send(new ListRootsCommand({})),
1711
+ props.organizationsClient.send(new DescribeOrganizationCommand({}))
1712
+ ]);
1713
+ const root = rootsResponse.Roots?.[0];
1470
1714
  if (root?.Id == null) {
1471
1715
  throw new Error("No organization root found.");
1472
1716
  }
1717
+ const managementAccountId = orgResponse.Organization?.MasterAccountId;
1473
1718
  const organizationalUnits = await collectOrganizationalUnits({
1474
1719
  organizationsClient: props.organizationsClient,
1475
1720
  parentId: root.Id
@@ -1493,6 +1738,11 @@ async function scanOrganization(props) {
1493
1738
  ResourceId: account.Id
1494
1739
  })
1495
1740
  );
1741
+ const alternateContacts = await scanAlternateContacts({
1742
+ accountClient: props.accountClient,
1743
+ accountId: account.Id,
1744
+ isManagementAccount: account.Id === managementAccountId
1745
+ });
1496
1746
  accounts.push({
1497
1747
  id: account.Id,
1498
1748
  arn: account.Arn,
@@ -1510,17 +1760,95 @@ async function scanOrganization(props) {
1510
1760
  value: tag.Value ?? ""
1511
1761
  }
1512
1762
  ];
1513
- })
1763
+ }),
1764
+ alternateContacts: alternateContacts.length > 0 ? alternateContacts : void 0
1514
1765
  });
1515
1766
  }
1516
1767
  nextToken = response.NextToken;
1517
1768
  } while (nextToken != null);
1769
+ const { policies, policyAttachments } = await scanOrganizationPolicies({
1770
+ organizationsClient: props.organizationsClient
1771
+ });
1518
1772
  return {
1519
1773
  rootId: root.Id,
1520
1774
  organizationalUnits,
1521
- accounts
1775
+ accounts,
1776
+ policies,
1777
+ policyAttachments
1522
1778
  };
1523
1779
  }
1780
+ var ORG_POLICY_TYPES = [
1781
+ "SERVICE_CONTROL_POLICY",
1782
+ "RESOURCE_CONTROL_POLICY",
1783
+ "TAG_POLICY",
1784
+ "AISERVICES_OPT_OUT_POLICY"
1785
+ ];
1786
+ async function scanOrganizationPolicies(props) {
1787
+ const policies = [];
1788
+ const policyAttachments = [];
1789
+ for (const policyType of ORG_POLICY_TYPES) {
1790
+ let nextToken;
1791
+ const policyIds = [];
1792
+ do {
1793
+ const response = await props.organizationsClient.send(
1794
+ new ListPoliciesCommand({ Filter: policyType, NextToken: nextToken })
1795
+ );
1796
+ for (const summary of response.Policies ?? []) {
1797
+ if (summary.Id == null || summary.AwsManaged === true) {
1798
+ continue;
1799
+ }
1800
+ policyIds.push(summary.Id);
1801
+ }
1802
+ nextToken = response.NextToken;
1803
+ } while (nextToken != null);
1804
+ for (const policyId of policyIds) {
1805
+ const describeResponse = await props.organizationsClient.send(
1806
+ new DescribePolicyCommand({ PolicyId: policyId })
1807
+ );
1808
+ const policy = describeResponse.Policy;
1809
+ if (policy?.PolicySummary?.Id == null || policy.PolicySummary.Arn == null || policy.PolicySummary.Name == null) {
1810
+ continue;
1811
+ }
1812
+ const content = policy.Content;
1813
+ if (content == null || content.length === 0) {
1814
+ continue;
1815
+ }
1816
+ policies.push({
1817
+ id: policy.PolicySummary.Id,
1818
+ arn: policy.PolicySummary.Arn,
1819
+ name: policy.PolicySummary.Name,
1820
+ description: policy.PolicySummary.Description ?? "",
1821
+ type: policyType,
1822
+ content
1823
+ });
1824
+ let targetsNextToken;
1825
+ do {
1826
+ const targetsResponse = await props.organizationsClient.send(
1827
+ new ListTargetsForPolicyCommand({
1828
+ PolicyId: policyId,
1829
+ NextToken: targetsNextToken
1830
+ })
1831
+ );
1832
+ for (const target of targetsResponse.Targets ?? []) {
1833
+ if (target.TargetId == null || target.Type == null) {
1834
+ continue;
1835
+ }
1836
+ const targetType = target.Type;
1837
+ if (targetType !== "ROOT" && targetType !== "ORGANIZATIONAL_UNIT" && targetType !== "ACCOUNT") {
1838
+ continue;
1839
+ }
1840
+ policyAttachments.push({
1841
+ policyId,
1842
+ targetId: target.TargetId,
1843
+ targetType
1844
+ });
1845
+ }
1846
+ targetsNextToken = targetsResponse.NextToken;
1847
+ } while (targetsNextToken != null);
1848
+ }
1849
+ }
1850
+ return { policies, policyAttachments };
1851
+ }
1524
1852
  async function collectOrganizationalUnits(props) {
1525
1853
  const children = [];
1526
1854
  let nextToken;
@@ -1563,7 +1891,7 @@ async function scanIdentityCenter(props) {
1563
1891
  instances,
1564
1892
  requestedInstanceArn: props.requestedInstanceArn
1565
1893
  });
1566
- const [users, groups, permissionSets] = await Promise.all([
1894
+ const [users, groups, permissionSets, accessControlAttributes] = await Promise.all([
1567
1895
  listIdentityStoreUsers({
1568
1896
  identityStoreClient: props.identityStoreClient,
1569
1897
  identityStoreId: instance.identityStoreId
@@ -1575,6 +1903,10 @@ async function scanIdentityCenter(props) {
1575
1903
  listPermissionSets({
1576
1904
  ssoAdminClient: props.ssoAdminClient,
1577
1905
  instanceArn: instance.instanceArn
1906
+ }),
1907
+ scanAccessControlAttributes({
1908
+ ssoAdminClient: props.ssoAdminClient,
1909
+ instanceArn: instance.instanceArn
1578
1910
  })
1579
1911
  ]);
1580
1912
  const groupMemberships = await listGroupMemberships({
@@ -1599,9 +1931,30 @@ async function scanIdentityCenter(props) {
1599
1931
  groupMemberships,
1600
1932
  permissionSets,
1601
1933
  accountAssignments,
1602
- accessRoles
1934
+ accessRoles,
1935
+ accessControlAttributes
1603
1936
  };
1604
1937
  }
1938
+ async function scanAccessControlAttributes(props) {
1939
+ let response;
1940
+ try {
1941
+ response = await props.ssoAdminClient.send(
1942
+ new DescribeInstanceAccessControlAttributeConfigurationCommand({
1943
+ InstanceArn: props.instanceArn
1944
+ })
1945
+ );
1946
+ } catch (err) {
1947
+ if (err != null && typeof err === "object" && "name" in err && err.name === "ResourceNotFoundException") {
1948
+ return [];
1949
+ }
1950
+ throw err;
1951
+ }
1952
+ const attributes = response.InstanceAccessControlAttributeConfiguration?.AccessControlAttributes ?? [];
1953
+ return attributes.filter((attr) => attr.Key != null).map((attr) => ({
1954
+ key: attr.Key,
1955
+ source: attr.Value?.Source ?? []
1956
+ }));
1957
+ }
1605
1958
  function selectIdentityCenterInstance(props) {
1606
1959
  if (props.requestedInstanceArn != null) {
1607
1960
  const selected2 = props.instances.find(
@@ -1892,18 +2245,63 @@ async function listAccountsForPermissionSet(props) {
1892
2245
  } while (nextToken != null);
1893
2246
  return accountIds;
1894
2247
  }
2248
+ var ALTERNATE_CONTACT_TYPES = [
2249
+ "BILLING",
2250
+ "OPERATIONS",
2251
+ "SECURITY"
2252
+ ];
2253
+ async function scanAlternateContacts(props) {
2254
+ const results = await Promise.all(
2255
+ ALTERNATE_CONTACT_TYPES.map(async (contactType) => {
2256
+ try {
2257
+ const response = await props.accountClient.send(
2258
+ new GetAlternateContactCommand({
2259
+ AccountId: props.isManagementAccount ? void 0 : props.accountId,
2260
+ AlternateContactType: contactType
2261
+ })
2262
+ );
2263
+ const c = response.AlternateContact;
2264
+ if (c == null || c.EmailAddress == null || c.Name == null) {
2265
+ return null;
2266
+ }
2267
+ return {
2268
+ contactType,
2269
+ name: c.Name,
2270
+ email: c.EmailAddress,
2271
+ phone: c.PhoneNumber ?? "",
2272
+ ...c.Title != null ? { title: c.Title } : {}
2273
+ };
2274
+ } catch (error) {
2275
+ if (error != null && typeof error === "object" && "name" in error && error.name === "ResourceNotFoundException") {
2276
+ return null;
2277
+ }
2278
+ throw error;
2279
+ }
2280
+ })
2281
+ );
2282
+ return results.filter((c) => c != null);
2283
+ }
1895
2284
 
1896
2285
  // src/applyLogic.ts
1897
- import { PutAccountNameCommand } from "@aws-sdk/client-account";
1898
2286
  import {
2287
+ DeleteAlternateContactCommand,
2288
+ PutAccountNameCommand,
2289
+ PutAlternateContactCommand
2290
+ } from "@aws-sdk/client-account";
2291
+ import {
2292
+ AttachPolicyCommand,
1899
2293
  CreateOrganizationalUnitCommand,
2294
+ CreatePolicyCommand,
1900
2295
  DeleteOrganizationalUnitCommand,
2296
+ DeletePolicyCommand,
2297
+ DetachPolicyCommand,
1901
2298
  ListAccountsForParentCommand,
1902
2299
  ListOrganizationalUnitsForParentCommand as ListOrganizationalUnitsForParentCommand2,
1903
2300
  MoveAccountCommand as MoveAccountCommand2,
1904
2301
  TagResourceCommand,
1905
2302
  UntagResourceCommand,
1906
- UpdateOrganizationalUnitCommand
2303
+ UpdateOrganizationalUnitCommand,
2304
+ UpdatePolicyCommand
1907
2305
  } from "@aws-sdk/client-organizations";
1908
2306
  import {
1909
2307
  CreateGroupMembershipCommand,
@@ -1931,6 +2329,7 @@ import {
1931
2329
  DetachManagedPolicyFromPermissionSetCommand,
1932
2330
  ProvisionPermissionSetCommand,
1933
2331
  PutInlinePolicyToPermissionSetCommand,
2332
+ UpdateInstanceAccessControlAttributeConfigurationCommand,
1934
2333
  UpdatePermissionSetCommand
1935
2334
  } from "@aws-sdk/client-sso-admin";
1936
2335
 
@@ -2920,6 +3319,218 @@ async function executeOperation(props) {
2920
3319
  }
2921
3320
  });
2922
3321
  }
3322
+ if (operation.kind === "createOrgPolicy") {
3323
+ props.logger.log(
3324
+ `Creating org policy "${operation.policyName}" (${operation.policyType})...`
3325
+ );
3326
+ const response = await props.organizationsClient.send(
3327
+ new CreatePolicyCommand({
3328
+ Name: operation.policyName,
3329
+ Description: operation.description.length > 0 ? operation.description : void 0,
3330
+ Content: operation.content,
3331
+ Type: operation.policyType
3332
+ })
3333
+ );
3334
+ const policy = response.Policy?.PolicySummary;
3335
+ if (policy?.Id == null || policy.Arn == null) {
3336
+ throw new Error(
3337
+ `CreatePolicy for "${operation.policyName}" returned incomplete data.`
3338
+ );
3339
+ }
3340
+ props.logger.log(`Done: "${operation.policyName}"`);
3341
+ return upsertOrgPolicyInWorkingState({
3342
+ workingState: props.state,
3343
+ policy: {
3344
+ id: policy.Id,
3345
+ arn: policy.Arn,
3346
+ name: operation.policyName,
3347
+ description: operation.description,
3348
+ type: operation.policyType,
3349
+ content: operation.content
3350
+ }
3351
+ });
3352
+ }
3353
+ if (operation.kind === "updateOrgPolicyContent") {
3354
+ props.logger.log(`Updating org policy content "${operation.policyName}"...`);
3355
+ await props.organizationsClient.send(
3356
+ new UpdatePolicyCommand({
3357
+ PolicyId: operation.policyId,
3358
+ Content: operation.content
3359
+ })
3360
+ );
3361
+ props.logger.log(`Done: "${operation.policyName}"`);
3362
+ const currentPolicy = props.state.organization.policiesById[operation.policyId];
3363
+ if (currentPolicy == null) {
3364
+ return props.state;
3365
+ }
3366
+ return upsertOrgPolicyInWorkingState({
3367
+ workingState: props.state,
3368
+ policy: { ...currentPolicy, content: operation.content }
3369
+ });
3370
+ }
3371
+ if (operation.kind === "updateOrgPolicyDescription") {
3372
+ props.logger.log(
3373
+ `Updating org policy description "${operation.policyName}"...`
3374
+ );
3375
+ await props.organizationsClient.send(
3376
+ new UpdatePolicyCommand({
3377
+ PolicyId: operation.policyId,
3378
+ Description: operation.description
3379
+ })
3380
+ );
3381
+ props.logger.log(`Done: "${operation.policyName}"`);
3382
+ const currentPolicy = props.state.organization.policiesById[operation.policyId];
3383
+ if (currentPolicy == null) {
3384
+ return props.state;
3385
+ }
3386
+ return upsertOrgPolicyInWorkingState({
3387
+ workingState: props.state,
3388
+ policy: { ...currentPolicy, description: operation.description }
3389
+ });
3390
+ }
3391
+ if (operation.kind === "attachOrgPolicy") {
3392
+ props.logger.log(
3393
+ `Attaching org policy "${operation.policyName}" to "${operation.targetName}"...`
3394
+ );
3395
+ const resolvedPolicyId = resolvePolicyId({
3396
+ state: props.state,
3397
+ policyId: operation.policyId,
3398
+ policyName: operation.policyName
3399
+ });
3400
+ await props.organizationsClient.send(
3401
+ new AttachPolicyCommand({
3402
+ PolicyId: resolvedPolicyId,
3403
+ TargetId: operation.targetId
3404
+ })
3405
+ );
3406
+ props.logger.log(`Done: "${operation.policyName}" -> "${operation.targetName}"`);
3407
+ const targetType = operation.targetId === props.context.organization.rootId ? "ROOT" : props.state.organization.organizationalUnitsById[operation.targetId] != null ? "ORGANIZATIONAL_UNIT" : "ACCOUNT";
3408
+ return addOrgPolicyAttachmentToWorkingState({
3409
+ workingState: props.state,
3410
+ attachment: {
3411
+ policyId: resolvedPolicyId,
3412
+ targetId: operation.targetId,
3413
+ targetType
3414
+ }
3415
+ });
3416
+ }
3417
+ if (operation.kind === "detachOrgPolicy") {
3418
+ props.logger.log(
3419
+ `Detaching org policy "${operation.policyName}" from "${operation.targetName}"...`
3420
+ );
3421
+ await props.organizationsClient.send(
3422
+ new DetachPolicyCommand({
3423
+ PolicyId: operation.policyId,
3424
+ TargetId: operation.targetId
3425
+ })
3426
+ );
3427
+ props.logger.log(`Done: "${operation.policyName}" x "${operation.targetName}"`);
3428
+ return removeOrgPolicyAttachmentFromWorkingState({
3429
+ workingState: props.state,
3430
+ policyId: operation.policyId,
3431
+ targetId: operation.targetId
3432
+ });
3433
+ }
3434
+ if (operation.kind === "deleteOrgPolicy") {
3435
+ props.logger.log(`Deleting org policy "${operation.policyName}"...`);
3436
+ await props.organizationsClient.send(
3437
+ new DeletePolicyCommand({ PolicyId: operation.policyId })
3438
+ );
3439
+ props.logger.log(`Done: "${operation.policyName}"`);
3440
+ return removeOrgPolicyFromWorkingState({
3441
+ workingState: props.state,
3442
+ policyId: operation.policyId
3443
+ });
3444
+ }
3445
+ if (operation.kind === "putAlternateContact") {
3446
+ props.logger.log(
3447
+ `Setting ${operation.contactType} alternate contact for "${operation.accountName}" (${operation.accountId})...`
3448
+ );
3449
+ await props.accountClient.send(
3450
+ new PutAlternateContactCommand({
3451
+ AccountId: operation.accountId,
3452
+ AlternateContactType: operation.contactType,
3453
+ Name: operation.name,
3454
+ EmailAddress: operation.email,
3455
+ PhoneNumber: operation.phone,
3456
+ Title: operation.title
3457
+ })
3458
+ );
3459
+ props.logger.log(`Done: ${operation.contactType} contact for "${operation.accountName}"`);
3460
+ const account = props.state.organization.accountsById[operation.accountId];
3461
+ if (account == null) {
3462
+ throw new Error(
3463
+ `Could not resolve account (${operation.accountId}) in working state.`
3464
+ );
3465
+ }
3466
+ const updatedContacts = [
3467
+ ...(account.alternateContacts ?? []).filter(
3468
+ (c) => c.contactType !== operation.contactType
3469
+ ),
3470
+ {
3471
+ contactType: operation.contactType,
3472
+ name: operation.name,
3473
+ email: operation.email,
3474
+ phone: operation.phone,
3475
+ title: operation.title
3476
+ }
3477
+ ];
3478
+ return upsertAccountInWorkingState({
3479
+ workingState: props.state,
3480
+ account: { ...account, alternateContacts: updatedContacts }
3481
+ });
3482
+ }
3483
+ if (operation.kind === "deleteAlternateContact") {
3484
+ props.logger.log(
3485
+ `Deleting ${operation.contactType} alternate contact for "${operation.accountName}" (${operation.accountId})...`
3486
+ );
3487
+ await props.accountClient.send(
3488
+ new DeleteAlternateContactCommand({
3489
+ AccountId: operation.accountId,
3490
+ AlternateContactType: operation.contactType
3491
+ })
3492
+ );
3493
+ props.logger.log(`Done: removed ${operation.contactType} contact for "${operation.accountName}"`);
3494
+ const account = props.state.organization.accountsById[operation.accountId];
3495
+ if (account == null) {
3496
+ throw new Error(
3497
+ `Could not resolve account (${operation.accountId}) in working state.`
3498
+ );
3499
+ }
3500
+ return upsertAccountInWorkingState({
3501
+ workingState: props.state,
3502
+ account: {
3503
+ ...account,
3504
+ alternateContacts: (account.alternateContacts ?? []).filter(
3505
+ (c) => c.contactType !== operation.contactType
3506
+ )
3507
+ }
3508
+ });
3509
+ }
3510
+ if (operation.kind === "setIdcAccessControlAttributes") {
3511
+ props.logger.log(
3512
+ `Setting IdC access control attributes (${operation.attributes.length} attribute(s))...`
3513
+ );
3514
+ await props.ssoAdminClient.send(
3515
+ new UpdateInstanceAccessControlAttributeConfigurationCommand({
3516
+ InstanceArn: props.state.identityCenter.instanceArn,
3517
+ InstanceAccessControlAttributeConfiguration: {
3518
+ AccessControlAttributes: operation.attributes.map((attr) => ({
3519
+ Key: attr.key,
3520
+ Value: { Source: attr.source }
3521
+ }))
3522
+ }
3523
+ })
3524
+ );
3525
+ props.logger.log(`Done: access control attributes updated`);
3526
+ return {
3527
+ ...props.state,
3528
+ identityCenter: {
3529
+ ...props.state.identityCenter,
3530
+ accessControlAttributes: operation.attributes
3531
+ }
3532
+ };
3533
+ }
2923
3534
  assertUnreachable(operation, "Unsupported operation kind in apply.");
2924
3535
  }
2925
3536
  function resolveAssignmentDependencies(props) {
@@ -2980,6 +3591,16 @@ function resolveGroupByDisplayName(props) {
2980
3591
  }
2981
3592
  return group;
2982
3593
  }
3594
+ function resolvePolicyId(props) {
3595
+ if (props.policyId !== "__pending_creation__") return props.policyId;
3596
+ const policy = props.state.organization.policiesByName[props.policyName];
3597
+ if (policy == null) {
3598
+ throw new Error(
3599
+ `Could not resolve policy "${props.policyName}" in working state.`
3600
+ );
3601
+ }
3602
+ return policy.id;
3603
+ }
2983
3604
  function resolvePermissionSetByName(props) {
2984
3605
  const permissionSet = props.state.identityCenter.permissionSetsByName[props.permissionSetName];
2985
3606
  if (permissionSet == null) {
@@ -3269,7 +3890,9 @@ var scanResponseSchema = strictObject({
3269
3890
  users: number(),
3270
3891
  groups: number(),
3271
3892
  permissionSets: number(),
3272
- accountAssignments: number()
3893
+ accountAssignments: number(),
3894
+ policies: number(),
3895
+ policyAttachments: number()
3273
3896
  }),
3274
3897
  state: stateSchema
3275
3898
  });
@@ -3339,7 +3962,7 @@ var s3Client = new S3Client({});
3339
3962
  var organizationsClient = new OrganizationsClient3({});
3340
3963
  var ssoAdminClient = new SSOAdminClient3({});
3341
3964
  var identityStoreClient = new IdentitystoreClient3({});
3342
- var accountClient = new AccountClient2({});
3965
+ var accountClient = new AccountClient3({});
3343
3966
  async function handler(event) {
3344
3967
  try {
3345
3968
  const parseResult = safeParse(lambdaRequestSchema, event);
@@ -3364,7 +3987,7 @@ async function handler(event) {
3364
3987
  return validateResponse(response);
3365
3988
  }
3366
3989
  if (request.action === "scan") {
3367
- const response = await handleScan({ s3Client, bucket, organizationsClient, ssoAdminClient, identityStoreClient });
3990
+ const response = await handleScan({ s3Client, bucket, organizationsClient, ssoAdminClient, identityStoreClient, accountClient });
3368
3991
  return validateResponse(response);
3369
3992
  }
3370
3993
  if (request.action === "getStateUrl") {
@@ -3453,7 +4076,7 @@ function isS3PreconditionFailed(error) {
3453
4076
  async function handleScan(props) {
3454
4077
  const identityCenterInstanceArn = process.env.IDENTITY_CENTER_INSTANCE_ARN || void 0;
3455
4078
  const [organization, identityCenter] = await Promise.all([
3456
- scanOrganization({ organizationsClient: props.organizationsClient }),
4079
+ scanOrganization({ organizationsClient: props.organizationsClient, accountClient: props.accountClient }),
3457
4080
  scanIdentityCenter({
3458
4081
  ssoAdminClient: props.ssoAdminClient,
3459
4082
  identityStoreClient: props.identityStoreClient,
@@ -3480,7 +4103,9 @@ async function handleScan(props) {
3480
4103
  users: state.identityCenter.users.length,
3481
4104
  groups: state.identityCenter.groups.length,
3482
4105
  permissionSets: state.identityCenter.permissionSets.length,
3483
- accountAssignments: state.identityCenter.accountAssignments.length
4106
+ accountAssignments: state.identityCenter.accountAssignments.length,
4107
+ policies: state.organization.policies?.length ?? 0,
4108
+ policyAttachments: state.organization.policyAttachments?.length ?? 0
3484
4109
  },
3485
4110
  state
3486
4111
  };