@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/README.md CHANGED
@@ -25,7 +25,7 @@ npm install @beesolve/aws-accounts
25
25
  mkdir my-org && cd my-org
26
26
  npm init -y
27
27
  npm pkg set type=module
28
- npm install @beesolve/aws-accounts
28
+ npm install @beesolve/aws-accounts typescript
29
29
 
30
30
  # 2. Initialize git and add a .gitignore
31
31
  git init
@@ -1,13 +1,22 @@
1
- import { PutAccountNameCommand } from "@aws-sdk/client-account";
2
1
  import {
2
+ DeleteAlternateContactCommand,
3
+ PutAccountNameCommand,
4
+ PutAlternateContactCommand
5
+ } from "@aws-sdk/client-account";
6
+ import {
7
+ AttachPolicyCommand,
3
8
  CreateOrganizationalUnitCommand,
9
+ CreatePolicyCommand,
4
10
  DeleteOrganizationalUnitCommand,
11
+ DeletePolicyCommand,
12
+ DetachPolicyCommand,
5
13
  ListAccountsForParentCommand,
6
14
  ListOrganizationalUnitsForParentCommand,
7
15
  MoveAccountCommand,
8
16
  TagResourceCommand,
9
17
  UntagResourceCommand,
10
- UpdateOrganizationalUnitCommand
18
+ UpdateOrganizationalUnitCommand,
19
+ UpdatePolicyCommand
11
20
  } from "@aws-sdk/client-organizations";
12
21
  import {
13
22
  CreateGroupMembershipCommand,
@@ -35,6 +44,7 @@ import {
35
44
  DetachManagedPolicyFromPermissionSetCommand,
36
45
  ProvisionPermissionSetCommand,
37
46
  PutInlinePolicyToPermissionSetCommand,
47
+ UpdateInstanceAccessControlAttributeConfigurationCommand,
38
48
  UpdatePermissionSetCommand
39
49
  } from "@aws-sdk/client-sso-admin";
40
50
  import { createAccountAndMoveToOu } from "./accountCreation.js";
@@ -42,6 +52,7 @@ import { assertUnreachable, delay } from "./helpers.js";
42
52
  import {
43
53
  addGroupMembershipToWorkingState,
44
54
  addAccountAssignmentToWorkingState,
55
+ addOrgPolicyAttachmentToWorkingState,
45
56
  createGroupMembershipKey,
46
57
  moveAccountInWorkingState,
47
58
  removeAccountAssignmentFromWorkingState,
@@ -50,12 +61,15 @@ import {
50
61
  removeIdcPermissionSetFromWorkingState,
51
62
  removeIdcUserFromWorkingState,
52
63
  removeOrganizationalUnitFromWorkingState,
64
+ removeOrgPolicyAttachmentFromWorkingState,
65
+ removeOrgPolicyFromWorkingState,
53
66
  renameOrganizationalUnitInWorkingState,
54
67
  upsertIdcGroupInWorkingState,
55
68
  upsertIdcPermissionSetInWorkingState,
56
69
  upsertIdcUserInWorkingState,
57
70
  upsertAccountInWorkingState,
58
- upsertOrganizationalUnitInWorkingState
71
+ upsertOrganizationalUnitInWorkingState,
72
+ upsertOrgPolicyInWorkingState
59
73
  } from "./state.js";
60
74
  async function executeOperation(props) {
61
75
  const operation = props.operation;
@@ -909,6 +923,218 @@ async function executeOperation(props) {
909
923
  }
910
924
  });
911
925
  }
926
+ if (operation.kind === "createOrgPolicy") {
927
+ props.logger.log(
928
+ `Creating org policy "${operation.policyName}" (${operation.policyType})...`
929
+ );
930
+ const response = await props.organizationsClient.send(
931
+ new CreatePolicyCommand({
932
+ Name: operation.policyName,
933
+ Description: operation.description.length > 0 ? operation.description : void 0,
934
+ Content: operation.content,
935
+ Type: operation.policyType
936
+ })
937
+ );
938
+ const policy = response.Policy?.PolicySummary;
939
+ if (policy?.Id == null || policy.Arn == null) {
940
+ throw new Error(
941
+ `CreatePolicy for "${operation.policyName}" returned incomplete data.`
942
+ );
943
+ }
944
+ props.logger.log(`Done: "${operation.policyName}"`);
945
+ return upsertOrgPolicyInWorkingState({
946
+ workingState: props.state,
947
+ policy: {
948
+ id: policy.Id,
949
+ arn: policy.Arn,
950
+ name: operation.policyName,
951
+ description: operation.description,
952
+ type: operation.policyType,
953
+ content: operation.content
954
+ }
955
+ });
956
+ }
957
+ if (operation.kind === "updateOrgPolicyContent") {
958
+ props.logger.log(`Updating org policy content "${operation.policyName}"...`);
959
+ await props.organizationsClient.send(
960
+ new UpdatePolicyCommand({
961
+ PolicyId: operation.policyId,
962
+ Content: operation.content
963
+ })
964
+ );
965
+ props.logger.log(`Done: "${operation.policyName}"`);
966
+ const currentPolicy = props.state.organization.policiesById[operation.policyId];
967
+ if (currentPolicy == null) {
968
+ return props.state;
969
+ }
970
+ return upsertOrgPolicyInWorkingState({
971
+ workingState: props.state,
972
+ policy: { ...currentPolicy, content: operation.content }
973
+ });
974
+ }
975
+ if (operation.kind === "updateOrgPolicyDescription") {
976
+ props.logger.log(
977
+ `Updating org policy description "${operation.policyName}"...`
978
+ );
979
+ await props.organizationsClient.send(
980
+ new UpdatePolicyCommand({
981
+ PolicyId: operation.policyId,
982
+ Description: operation.description
983
+ })
984
+ );
985
+ props.logger.log(`Done: "${operation.policyName}"`);
986
+ const currentPolicy = props.state.organization.policiesById[operation.policyId];
987
+ if (currentPolicy == null) {
988
+ return props.state;
989
+ }
990
+ return upsertOrgPolicyInWorkingState({
991
+ workingState: props.state,
992
+ policy: { ...currentPolicy, description: operation.description }
993
+ });
994
+ }
995
+ if (operation.kind === "attachOrgPolicy") {
996
+ props.logger.log(
997
+ `Attaching org policy "${operation.policyName}" to "${operation.targetName}"...`
998
+ );
999
+ const resolvedPolicyId = resolvePolicyId({
1000
+ state: props.state,
1001
+ policyId: operation.policyId,
1002
+ policyName: operation.policyName
1003
+ });
1004
+ await props.organizationsClient.send(
1005
+ new AttachPolicyCommand({
1006
+ PolicyId: resolvedPolicyId,
1007
+ TargetId: operation.targetId
1008
+ })
1009
+ );
1010
+ props.logger.log(`Done: "${operation.policyName}" -> "${operation.targetName}"`);
1011
+ const targetType = operation.targetId === props.context.organization.rootId ? "ROOT" : props.state.organization.organizationalUnitsById[operation.targetId] != null ? "ORGANIZATIONAL_UNIT" : "ACCOUNT";
1012
+ return addOrgPolicyAttachmentToWorkingState({
1013
+ workingState: props.state,
1014
+ attachment: {
1015
+ policyId: resolvedPolicyId,
1016
+ targetId: operation.targetId,
1017
+ targetType
1018
+ }
1019
+ });
1020
+ }
1021
+ if (operation.kind === "detachOrgPolicy") {
1022
+ props.logger.log(
1023
+ `Detaching org policy "${operation.policyName}" from "${operation.targetName}"...`
1024
+ );
1025
+ await props.organizationsClient.send(
1026
+ new DetachPolicyCommand({
1027
+ PolicyId: operation.policyId,
1028
+ TargetId: operation.targetId
1029
+ })
1030
+ );
1031
+ props.logger.log(`Done: "${operation.policyName}" x "${operation.targetName}"`);
1032
+ return removeOrgPolicyAttachmentFromWorkingState({
1033
+ workingState: props.state,
1034
+ policyId: operation.policyId,
1035
+ targetId: operation.targetId
1036
+ });
1037
+ }
1038
+ if (operation.kind === "deleteOrgPolicy") {
1039
+ props.logger.log(`Deleting org policy "${operation.policyName}"...`);
1040
+ await props.organizationsClient.send(
1041
+ new DeletePolicyCommand({ PolicyId: operation.policyId })
1042
+ );
1043
+ props.logger.log(`Done: "${operation.policyName}"`);
1044
+ return removeOrgPolicyFromWorkingState({
1045
+ workingState: props.state,
1046
+ policyId: operation.policyId
1047
+ });
1048
+ }
1049
+ if (operation.kind === "putAlternateContact") {
1050
+ props.logger.log(
1051
+ `Setting ${operation.contactType} alternate contact for "${operation.accountName}" (${operation.accountId})...`
1052
+ );
1053
+ await props.accountClient.send(
1054
+ new PutAlternateContactCommand({
1055
+ AccountId: operation.accountId,
1056
+ AlternateContactType: operation.contactType,
1057
+ Name: operation.name,
1058
+ EmailAddress: operation.email,
1059
+ PhoneNumber: operation.phone,
1060
+ Title: operation.title
1061
+ })
1062
+ );
1063
+ props.logger.log(`Done: ${operation.contactType} contact for "${operation.accountName}"`);
1064
+ const account = props.state.organization.accountsById[operation.accountId];
1065
+ if (account == null) {
1066
+ throw new Error(
1067
+ `Could not resolve account (${operation.accountId}) in working state.`
1068
+ );
1069
+ }
1070
+ const updatedContacts = [
1071
+ ...(account.alternateContacts ?? []).filter(
1072
+ (c) => c.contactType !== operation.contactType
1073
+ ),
1074
+ {
1075
+ contactType: operation.contactType,
1076
+ name: operation.name,
1077
+ email: operation.email,
1078
+ phone: operation.phone,
1079
+ title: operation.title
1080
+ }
1081
+ ];
1082
+ return upsertAccountInWorkingState({
1083
+ workingState: props.state,
1084
+ account: { ...account, alternateContacts: updatedContacts }
1085
+ });
1086
+ }
1087
+ if (operation.kind === "deleteAlternateContact") {
1088
+ props.logger.log(
1089
+ `Deleting ${operation.contactType} alternate contact for "${operation.accountName}" (${operation.accountId})...`
1090
+ );
1091
+ await props.accountClient.send(
1092
+ new DeleteAlternateContactCommand({
1093
+ AccountId: operation.accountId,
1094
+ AlternateContactType: operation.contactType
1095
+ })
1096
+ );
1097
+ props.logger.log(`Done: removed ${operation.contactType} contact for "${operation.accountName}"`);
1098
+ const account = props.state.organization.accountsById[operation.accountId];
1099
+ if (account == null) {
1100
+ throw new Error(
1101
+ `Could not resolve account (${operation.accountId}) in working state.`
1102
+ );
1103
+ }
1104
+ return upsertAccountInWorkingState({
1105
+ workingState: props.state,
1106
+ account: {
1107
+ ...account,
1108
+ alternateContacts: (account.alternateContacts ?? []).filter(
1109
+ (c) => c.contactType !== operation.contactType
1110
+ )
1111
+ }
1112
+ });
1113
+ }
1114
+ if (operation.kind === "setIdcAccessControlAttributes") {
1115
+ props.logger.log(
1116
+ `Setting IdC access control attributes (${operation.attributes.length} attribute(s))...`
1117
+ );
1118
+ await props.ssoAdminClient.send(
1119
+ new UpdateInstanceAccessControlAttributeConfigurationCommand({
1120
+ InstanceArn: props.state.identityCenter.instanceArn,
1121
+ InstanceAccessControlAttributeConfiguration: {
1122
+ AccessControlAttributes: operation.attributes.map((attr) => ({
1123
+ Key: attr.key,
1124
+ Value: { Source: attr.source }
1125
+ }))
1126
+ }
1127
+ })
1128
+ );
1129
+ props.logger.log(`Done: access control attributes updated`);
1130
+ return {
1131
+ ...props.state,
1132
+ identityCenter: {
1133
+ ...props.state.identityCenter,
1134
+ accessControlAttributes: operation.attributes
1135
+ }
1136
+ };
1137
+ }
912
1138
  assertUnreachable(operation, "Unsupported operation kind in apply.");
913
1139
  }
914
1140
  function resolveAssignmentDependencies(props) {
@@ -969,6 +1195,16 @@ function resolveGroupByDisplayName(props) {
969
1195
  }
970
1196
  return group;
971
1197
  }
1198
+ function resolvePolicyId(props) {
1199
+ if (props.policyId !== "__pending_creation__") return props.policyId;
1200
+ const policy = props.state.organization.policiesByName[props.policyName];
1201
+ if (policy == null) {
1202
+ throw new Error(
1203
+ `Could not resolve policy "${props.policyName}" in working state.`
1204
+ );
1205
+ }
1206
+ return policy.id;
1207
+ }
972
1208
  function resolvePermissionSetByName(props) {
973
1209
  const permissionSet = props.state.identityCenter.permissionSetsByName[props.permissionSetName];
974
1210
  if (permissionSet == null) {