@fjall/components-infrastructure 0.96.0 → 0.99.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.
Files changed (209) hide show
  1. package/dist/lib/app.d.ts +68 -1
  2. package/dist/lib/app.js +113 -4
  3. package/dist/lib/config/aws/__t17fixture.d.ts +1 -0
  4. package/dist/lib/config/aws/__t17fixture.js +3 -0
  5. package/dist/lib/config/aws/__t17fixtureType.d.ts +2 -0
  6. package/dist/lib/config/aws/__t17fixtureType.js +1 -0
  7. package/dist/lib/config/aws/alarmTopic.js +8 -4
  8. package/dist/lib/config/aws/cloudTrail.js +1 -1
  9. package/dist/lib/config/aws/disasterRecovery.js +11 -16
  10. package/dist/lib/config/aws/ecrDefaultImage.d.ts +0 -1
  11. package/dist/lib/config/aws/ecrDefaultImage.js +13 -23
  12. package/dist/lib/config/aws/identityCenter.d.ts +10 -3
  13. package/dist/lib/config/aws/identityCenter.js +101 -37
  14. package/dist/lib/config/aws/identityCenterGroupMembership.js +8 -2
  15. package/dist/lib/config/aws/identityCenterMembership.d.ts +11 -0
  16. package/dist/lib/config/aws/identityCenterMembership.js +61 -0
  17. package/dist/lib/config/aws/index.d.ts +1 -1
  18. package/dist/lib/config/aws/index.js +1 -1
  19. package/dist/lib/config/aws/ipam.js +6 -11
  20. package/dist/lib/config/aws/oidcConnector.js +5 -1
  21. package/dist/lib/config/aws/scpPreset.js +4 -1
  22. package/dist/lib/patterns/aws/_eslint_test_tmp/leak.d.ts +1 -0
  23. package/dist/lib/patterns/aws/_eslint_test_tmp/leak.js +4 -0
  24. package/dist/lib/patterns/aws/account.js +2 -4
  25. package/dist/lib/patterns/aws/apexDomainPattern.js +10 -10
  26. package/dist/lib/patterns/aws/bastionFactory.d.ts +10 -0
  27. package/dist/lib/patterns/aws/bastionFactory.js +29 -0
  28. package/dist/lib/patterns/aws/buildkite.d.ts +2 -2
  29. package/dist/lib/patterns/aws/buildkite.js +51 -97
  30. package/dist/lib/patterns/aws/cdn.js +1 -1
  31. package/dist/lib/patterns/aws/clickhouseDatabase.d.ts +172 -0
  32. package/dist/lib/patterns/aws/clickhouseDatabase.js +600 -0
  33. package/dist/lib/patterns/aws/compute.d.ts +4 -6
  34. package/dist/lib/patterns/aws/compute.js +7 -13
  35. package/dist/lib/patterns/aws/computeEcs.d.ts +93 -5
  36. package/dist/lib/patterns/aws/computeEcs.js +867 -37
  37. package/dist/lib/patterns/aws/computeEcsTypes.d.ts +528 -25
  38. package/dist/lib/patterns/aws/computeEcsTypes.js +10 -0
  39. package/dist/lib/patterns/aws/computeLambda.d.ts +0 -5
  40. package/dist/lib/patterns/aws/computeLambda.js +1 -2
  41. package/dist/lib/patterns/aws/database.d.ts +50 -8
  42. package/dist/lib/patterns/aws/database.js +183 -27
  43. package/dist/lib/patterns/aws/domain.js +6 -4
  44. package/dist/lib/patterns/aws/index.d.ts +1 -0
  45. package/dist/lib/patterns/aws/index.js +1 -0
  46. package/dist/lib/patterns/aws/interfaces/compute.d.ts +7 -1
  47. package/dist/lib/patterns/aws/interfaces/database.d.ts +187 -8
  48. package/dist/lib/patterns/aws/interfaces/database.js +17 -3
  49. package/dist/lib/patterns/aws/interfaces/index.d.ts +2 -1
  50. package/dist/lib/patterns/aws/interfaces/index.js +3 -1
  51. package/dist/lib/patterns/aws/interfaces/messaging.d.ts +7 -0
  52. package/dist/lib/patterns/aws/interfaces/migrationContributor.d.ts +47 -0
  53. package/dist/lib/patterns/aws/interfaces/migrationContributor.js +9 -0
  54. package/dist/lib/patterns/aws/messaging.d.ts +66 -10
  55. package/dist/lib/patterns/aws/messaging.js +115 -20
  56. package/dist/lib/patterns/aws/network.js +16 -7
  57. package/dist/lib/patterns/aws/organisation.d.ts +4 -0
  58. package/dist/lib/patterns/aws/organisation.js +22 -4
  59. package/dist/lib/patterns/aws/storage.d.ts +1 -2
  60. package/dist/lib/patterns/aws/storage.js +3 -2
  61. package/dist/lib/patterns/aws/vpcPeer.js +3 -1
  62. package/dist/lib/resources/aws/analytics/clickhouse.js +18 -9
  63. package/dist/lib/resources/aws/analytics/clickhouseAlarms.d.ts +24 -9
  64. package/dist/lib/resources/aws/analytics/clickhouseAlarms.js +61 -10
  65. package/dist/lib/resources/aws/analytics/clickhouseConstants.d.ts +3 -3
  66. package/dist/lib/resources/aws/analytics/clickhouseConstants.js +3 -3
  67. package/dist/lib/resources/aws/analytics/clickhouseTypes.d.ts +7 -1
  68. package/dist/lib/resources/aws/analytics/clickhouseUserData.d.ts +1 -1
  69. package/dist/lib/resources/aws/analytics/clickhouseUserData.js +53 -3
  70. package/dist/lib/resources/aws/base/awsStack.js +4 -2
  71. package/dist/lib/resources/aws/compute/__tmp__/regression-shape.d.ts +2 -0
  72. package/dist/lib/resources/aws/compute/__tmp__/regression-shape.js +11 -0
  73. package/dist/lib/resources/aws/compute/asgInlineLifecycleHook.d.ts +52 -0
  74. package/dist/lib/resources/aws/compute/asgInlineLifecycleHook.js +60 -0
  75. package/dist/lib/resources/aws/compute/blockDeviceVolume.d.ts +8 -0
  76. package/dist/lib/resources/aws/compute/blockDeviceVolume.js +10 -0
  77. package/dist/lib/resources/aws/compute/ec2.d.ts +132 -12
  78. package/dist/lib/resources/aws/compute/ec2.js +163 -23
  79. package/dist/lib/resources/aws/compute/ec2GracefulTerminationHandler.d.ts +41 -0
  80. package/dist/lib/resources/aws/compute/ec2GracefulTerminationHandler.js +194 -0
  81. package/dist/lib/resources/aws/compute/ec2GracefulTerminationLambda.source.cjs +458 -0
  82. package/dist/lib/resources/aws/compute/ecs.d.ts +27 -1
  83. package/dist/lib/resources/aws/compute/ecs.js +42 -2
  84. package/dist/lib/resources/aws/compute/ecsConstants.d.ts +9 -0
  85. package/dist/lib/resources/aws/compute/ecsConstants.js +16 -0
  86. package/dist/lib/resources/aws/compute/ecsImages.js +32 -20
  87. package/dist/lib/resources/aws/compute/ecsLifecycleHookMigration.d.ts +96 -0
  88. package/dist/lib/resources/aws/compute/ecsLifecycleHookMigration.js +113 -0
  89. package/dist/lib/resources/aws/compute/ecsNetworking.d.ts +2 -1
  90. package/dist/lib/resources/aws/compute/ecsNetworking.js +18 -6
  91. package/dist/lib/resources/aws/compute/ecsServiceFactory.d.ts +13 -4
  92. package/dist/lib/resources/aws/compute/ecsServiceFactory.js +155 -33
  93. package/dist/lib/resources/aws/compute/ecsTaskDefinition.d.ts +31 -1
  94. package/dist/lib/resources/aws/compute/ecsTaskDefinition.js +102 -6
  95. package/dist/lib/resources/aws/compute/ecsTypes.d.ts +173 -13
  96. package/dist/lib/resources/aws/compute/ecsValidation.d.ts +9 -0
  97. package/dist/lib/resources/aws/compute/ecsValidation.js +63 -0
  98. package/dist/lib/resources/aws/compute/index.d.ts +2 -0
  99. package/dist/lib/resources/aws/compute/index.js +2 -0
  100. package/dist/lib/resources/aws/compute/lambda.d.ts +7 -13
  101. package/dist/lib/resources/aws/compute/lambda.js +30 -38
  102. package/dist/lib/resources/aws/compute/lifecycleHookLambda.source.cjs +192 -0
  103. package/dist/lib/resources/aws/compute/persistentDataVolume.d.ts +104 -0
  104. package/dist/lib/resources/aws/compute/persistentDataVolume.js +245 -0
  105. package/dist/lib/resources/aws/compute/persistentDataVolumeLambda.source.cjs +398 -0
  106. package/dist/lib/resources/aws/compute/samApplication.d.ts +15 -0
  107. package/dist/lib/resources/aws/compute/samApplication.js +27 -0
  108. package/dist/lib/resources/aws/database/clickhouseConstants.d.ts +159 -0
  109. package/dist/lib/resources/aws/database/clickhouseConstants.js +181 -0
  110. package/dist/lib/resources/aws/database/clickhouseSchemas.d.ts +71 -0
  111. package/dist/lib/resources/aws/database/clickhouseSchemas.js +157 -0
  112. package/dist/lib/resources/aws/database/clickhouseSecurityGroup.d.ts +14 -0
  113. package/dist/lib/resources/aws/database/clickhouseSecurityGroup.js +23 -0
  114. package/dist/lib/resources/aws/database/clickhouseUserData.d.ts +69 -0
  115. package/dist/lib/resources/aws/database/clickhouseUserData.js +371 -0
  116. package/dist/lib/resources/aws/database/clickhouseXmlRenderer.d.ts +56 -0
  117. package/dist/lib/resources/aws/database/clickhouseXmlRenderer.js +112 -0
  118. package/dist/lib/resources/aws/database/rdsAurora.d.ts +8 -1
  119. package/dist/lib/resources/aws/database/rdsAurora.js +42 -32
  120. package/dist/lib/resources/aws/database/rdsAuroraGlobal.d.ts +15 -2
  121. package/dist/lib/resources/aws/database/rdsAuroraGlobal.js +39 -43
  122. package/dist/lib/resources/aws/database/rdsDefaults.d.ts +6 -0
  123. package/dist/lib/resources/aws/database/rdsDefaults.js +7 -1
  124. package/dist/lib/resources/aws/database/rdsHelpers.d.ts +3 -3
  125. package/dist/lib/resources/aws/database/rdsHelpers.js +1 -0
  126. package/dist/lib/resources/aws/database/rdsInstance.d.ts +8 -1
  127. package/dist/lib/resources/aws/database/rdsInstance.js +51 -34
  128. package/dist/lib/resources/aws/database/rdsProxyOutput.d.ts +1 -1
  129. package/dist/lib/resources/aws/database/rdsProxyOutput.js +1 -1
  130. package/dist/lib/resources/aws/iam/delegationRole.js +1 -1
  131. package/dist/lib/resources/aws/iam/identityCenter/groupMembership.d.ts +9 -0
  132. package/dist/lib/resources/aws/iam/identityCenter/groupMembership.js +12 -0
  133. package/dist/lib/resources/aws/iam/identityCenter/index.d.ts +1 -0
  134. package/dist/lib/resources/aws/iam/identityCenter/index.js +1 -0
  135. package/dist/lib/resources/aws/iam/identityCenter/permissionSet.d.ts +1 -0
  136. package/dist/lib/resources/aws/iam/identityCenter/permissionSet.js +1 -0
  137. package/dist/lib/resources/aws/logging/logGroup.d.ts +0 -8
  138. package/dist/lib/resources/aws/logging/logGroup.js +0 -11
  139. package/dist/lib/resources/aws/messaging/defaultEventBus.d.ts +7 -0
  140. package/dist/lib/resources/aws/messaging/defaultEventBus.js +21 -0
  141. package/dist/lib/resources/aws/messaging/eventBridgeRule.d.ts +96 -0
  142. package/dist/lib/resources/aws/messaging/eventBridgeRule.js +110 -0
  143. package/dist/lib/resources/aws/messaging/eventTargets.d.ts +84 -0
  144. package/dist/lib/resources/aws/messaging/eventTargets.js +152 -0
  145. package/dist/lib/resources/aws/messaging/eventbridge.d.ts +25 -2
  146. package/dist/lib/resources/aws/messaging/eventbridge.js +22 -10
  147. package/dist/lib/resources/aws/messaging/index.d.ts +5 -0
  148. package/dist/lib/resources/aws/messaging/index.js +2 -0
  149. package/dist/lib/resources/aws/messaging/schedule.d.ts +118 -0
  150. package/dist/lib/resources/aws/messaging/schedule.js +64 -0
  151. package/dist/lib/resources/aws/messaging/sns.d.ts +2 -1
  152. package/dist/lib/resources/aws/messaging/sqs.d.ts +2 -1
  153. package/dist/lib/resources/aws/messaging/subscription.d.ts +112 -0
  154. package/dist/lib/resources/aws/messaging/subscription.js +67 -0
  155. package/dist/lib/resources/aws/messaging/utils.d.ts +6 -0
  156. package/dist/lib/resources/aws/messaging/utils.js +10 -0
  157. package/dist/lib/resources/aws/monitoring/clickhouseAlarms.d.ts +60 -0
  158. package/dist/lib/resources/aws/monitoring/clickhouseAlarms.js +139 -0
  159. package/dist/lib/resources/aws/monitoring/index.d.ts +2 -0
  160. package/dist/lib/resources/aws/monitoring/index.js +2 -0
  161. package/dist/lib/resources/aws/monitoring/scheduleAlarms.d.ts +47 -0
  162. package/dist/lib/resources/aws/monitoring/scheduleAlarms.js +106 -0
  163. package/dist/lib/resources/aws/networking/crossAccountDelegationRecord.js +6 -4
  164. package/dist/lib/resources/aws/networking/crossAccountReturnRoutes.js +17 -13
  165. package/dist/lib/resources/aws/networking/dnsRecord/dnsRecordBase.js +7 -5
  166. package/dist/lib/resources/aws/networking/domainCertificate.d.ts +2 -2
  167. package/dist/lib/resources/aws/networking/domainCertificate.js +6 -4
  168. package/dist/lib/resources/aws/networking/hostedZone.js +6 -5
  169. package/dist/lib/resources/aws/networking/serviceDiscovery.d.ts +96 -0
  170. package/dist/lib/resources/aws/networking/serviceDiscovery.js +96 -0
  171. package/dist/lib/resources/aws/networking/vpc.d.ts +4 -1
  172. package/dist/lib/resources/aws/networking/vpc.js +4 -1
  173. package/dist/lib/resources/aws/networking/vpcPeeringConnection.js +21 -3
  174. package/dist/lib/resources/aws/organisation/costAllocationTagActivator.d.ts +16 -5
  175. package/dist/lib/resources/aws/organisation/costAllocationTagActivator.js +17 -3
  176. package/dist/lib/resources/aws/organisation/index.d.ts +1 -1
  177. package/dist/lib/resources/aws/organisation/organisationPolicy.d.ts +2 -0
  178. package/dist/lib/resources/aws/organisation/organisationPolicy.js +3 -2
  179. package/dist/lib/resources/aws/secrets/secret.d.ts +7 -0
  180. package/dist/lib/resources/aws/secrets/secret.js +4 -3
  181. package/dist/lib/resources/aws/storage/bucketDeployment.d.ts +16 -0
  182. package/dist/lib/resources/aws/storage/bucketDeployment.js +17 -0
  183. package/dist/lib/resources/aws/storage/ecr.js +5 -5
  184. package/dist/lib/resources/aws/storage/index.d.ts +1 -0
  185. package/dist/lib/resources/aws/storage/index.js +1 -0
  186. package/dist/lib/resources/aws/storage/s3.js +10 -3
  187. package/dist/lib/resources/aws/utilities/customResource.js +18 -9
  188. package/dist/lib/synth_dump.d.ts +1 -0
  189. package/dist/lib/synth_dump.js +42 -0
  190. package/dist/lib/utils/cdkContext.d.ts +2 -0
  191. package/dist/lib/utils/cdkContext.js +4 -2
  192. package/dist/lib/utils/connections.js +6 -0
  193. package/dist/lib/utils/connector.d.ts +12 -0
  194. package/dist/lib/utils/costAllocationTags.d.ts +9 -0
  195. package/dist/lib/utils/costAllocationTags.js +11 -1
  196. package/dist/lib/utils/databaseTypes.d.ts +14 -0
  197. package/dist/lib/utils/getConfig.d.ts +2 -0
  198. package/dist/lib/utils/getConfig.js +2 -0
  199. package/dist/lib/utils/index.d.ts +1 -0
  200. package/dist/lib/utils/index.js +1 -0
  201. package/dist/lib/utils/manifestWriter.d.ts +6 -89
  202. package/dist/lib/utils/manifestWriter.js +36 -23
  203. package/dist/lib/utils/migrationVersionResolvers.d.ts +2 -0
  204. package/dist/lib/utils/migrationVersionResolvers.js +2 -0
  205. package/dist/lib/utils/orgConfigParser.js +2 -1
  206. package/dist/lib/utils/resolveAlertsTopic.d.ts +14 -0
  207. package/dist/lib/utils/resolveAlertsTopic.js +30 -0
  208. package/dist/lib/utils/validationLogger.js +6 -3
  209. package/package.json +22 -19
@@ -5,6 +5,7 @@ import { AwsCustomResource } from "../../resources/aws/utilities/awsCustomResour
5
5
  import { stripAndCamelCase } from "../../utils/stripAndCamelCase.js";
6
6
  import { Group, PermissionSet, Assignment } from "../../resources/aws/iam/identityCenter/index.js";
7
7
  import { ManagedPolicy, Role } from "../../resources/aws/iam/index.js";
8
+ import { IdentityCenterMembership } from "./identityCenterMembership.js";
8
9
  const defaultPermissionSets = {
9
10
  AdministratorAccess: {
10
11
  Policy: "arn:aws:iam::aws:policy/AdministratorAccess",
@@ -19,15 +20,64 @@ const defaultPermissionSets = {
19
20
  Description: "Permission set for associated Billing policy"
20
21
  }
21
22
  };
23
+ // CreateAccountAssignment has a 15-outstanding-async limit. Batching at 8
24
+ // keeps headroom for CFN's internal retries within the same backoff window.
25
+ const ASSIGNMENT_BATCH_SIZE = 8;
22
26
  export class IdentityCenter extends NestedStack {
23
27
  identityStoreId;
24
28
  identityCenterArn;
25
29
  listInstancesRole;
30
+ accountsConfig;
31
+ customTags;
32
+ groupIds = {};
26
33
  constructor(scope, id, props) {
27
34
  super(scope, id, props);
35
+ this.accountsConfig = props.accounts;
36
+ this.customTags = props.tags;
28
37
  this.createListInstancesRole();
29
38
  this.listIdentityCenterInstance();
30
- this.createPermissionSets(props);
39
+ for (const [name, config] of Object.entries(defaultPermissionSets)) {
40
+ this.createPermissionSetAndAssignments(name, config, {
41
+ managedPolicies: [config.Policy]
42
+ });
43
+ }
44
+ }
45
+ declarePermissionSets(customs) {
46
+ for (const [name, config] of Object.entries(customs)) {
47
+ if (this.groupIds[name] !== undefined) {
48
+ throw new Error(`Permission set "${name}" already declared (collision with default or prior declaration)`);
49
+ }
50
+ const primaryPolicy = config.managedPolicies[0];
51
+ if (primaryPolicy === undefined) {
52
+ throw new Error(`Permission set "${name}" must declare at least one managed policy.`);
53
+ }
54
+ this.createPermissionSetAndAssignments(name, {
55
+ Policy: primaryPolicy,
56
+ ...(config.description !== undefined && {
57
+ Description: config.description
58
+ })
59
+ }, {
60
+ managedPolicies: config.managedPolicies,
61
+ ...(config.sessionDuration !== undefined && {
62
+ sessionDuration: config.sessionDuration
63
+ })
64
+ });
65
+ }
66
+ }
67
+ assignGroupMembers(members) {
68
+ for (const [groupName, emails] of Object.entries(members)) {
69
+ if (emails.length === 0)
70
+ continue;
71
+ const groupId = this.groupIds[groupName];
72
+ if (groupId === undefined) {
73
+ throw new Error(`Cannot assign members to undeclared group "${groupName}". Declare via declarePermissionSets() first, or use one of the defaults: AdministratorAccess, ReadOnlyAccess, Billing.`);
74
+ }
75
+ new IdentityCenterMembership(this, `${groupName}Members`, {
76
+ identityStoreId: this.identityStoreId,
77
+ groupId,
78
+ groupMembers: emails
79
+ });
80
+ }
31
81
  }
32
82
  createListInstancesRole() {
33
83
  this.listInstancesRole = new Role(this, "IdentityCenterCustomResourceRole", {
@@ -49,15 +99,17 @@ export class IdentityCenter extends NestedStack {
49
99
  });
50
100
  }
51
101
  listIdentityCenterInstance() {
52
- const customResource = new AwsCustomResource(this, "ListIdentityCenterInstanceResource", {
53
- onCreate: {
54
- service: "sso-admin",
55
- action: "ListInstancesCommand",
56
- parameters: {
57
- MaxResults: 1
58
- },
59
- physicalResourceId: customResources.PhysicalResourceId.of("listIdentityCenterInstance")
102
+ const listInstancesCall = {
103
+ service: "sso-admin",
104
+ action: "listInstances",
105
+ parameters: {
106
+ MaxResults: 1
60
107
  },
108
+ physicalResourceId: customResources.PhysicalResourceId.of("listIdentityCenterInstance")
109
+ };
110
+ const customResource = new AwsCustomResource(this, "ListIdentityCenterInstanceResource", {
111
+ onCreate: listInstancesCall,
112
+ onUpdate: listInstancesCall,
61
113
  role: this.listInstancesRole,
62
114
  resourceType: "Custom::IamIdentityCenter"
63
115
  });
@@ -74,37 +126,49 @@ export class IdentityCenter extends NestedStack {
74
126
  exportName: "identityStoreId"
75
127
  });
76
128
  }
77
- createPermissionSets(props) {
78
- for (const [name, config] of Object.entries(defaultPermissionSets)) {
79
- const group = new Group(this, `${name}Group`, {
80
- displayName: name,
81
- identityStoreId: this.identityStoreId,
82
- description: `Group for associated ${name} permission set`
83
- });
84
- const permissionSet = new PermissionSet(this, `PermissionSet${name}`, {
85
- name: name,
129
+ createPermissionSetAndAssignments(name, config, permissionSetOverrides) {
130
+ const group = new Group(this, `${name}Group`, {
131
+ displayName: name,
132
+ identityStoreId: this.identityStoreId,
133
+ description: `Group for associated ${name} permission set`
134
+ });
135
+ this.groupIds[name] = group.getGroupId();
136
+ const permissionSet = new PermissionSet(this, `PermissionSet${name}`, {
137
+ name: name,
138
+ instanceArn: this.identityCenterArn,
139
+ ...(config.Description !== undefined && {
140
+ description: config.Description
141
+ }),
142
+ managedPolicies: permissionSetOverrides.managedPolicies,
143
+ ...(permissionSetOverrides.sessionDuration !== undefined && {
144
+ sessionDuration: permissionSetOverrides.sessionDuration
145
+ }),
146
+ ...(this.customTags !== undefined && { tags: this.customTags })
147
+ });
148
+ permissionSet.node.addDependency(group);
149
+ new CfnOutput(this, `${name}GroupId`, {
150
+ key: `${name}GroupId`,
151
+ value: group.getGroupId(),
152
+ exportName: `${name}GroupId`
153
+ });
154
+ const assignments = [];
155
+ for (const [accountName, accountId] of Object.entries(this.accountsConfig)) {
156
+ const assignment = new Assignment(this, `${stripAndCamelCase(accountName)}${name}Assignment`, {
86
157
  instanceArn: this.identityCenterArn,
87
- description: config.Description,
88
- managedPolicies: [config.Policy],
89
- tags: props.tags
90
- });
91
- permissionSet.node.addDependency(group);
92
- new CfnOutput(this, `${name}GroupId`, {
93
- key: `${name}GroupId`,
94
- value: group.getGroupId(),
95
- exportName: `${name}GroupId`
158
+ permissionSetArn: permissionSet.getPermissionSetArn(),
159
+ principalType: "GROUP",
160
+ principalId: group.getGroupId(),
161
+ targetType: "AWS_ACCOUNT",
162
+ targetId: accountId
96
163
  });
97
- for (const [accountName, accountId] of Object.entries(props.accounts)) {
98
- const assignment = new Assignment(this, `${stripAndCamelCase(accountName)}${name}Assignment`, {
99
- instanceArn: this.identityCenterArn,
100
- permissionSetArn: permissionSet.getPermissionSetArn(),
101
- principalType: "GROUP",
102
- principalId: group.getGroupId(),
103
- targetType: "AWS_ACCOUNT",
104
- targetId: accountId
105
- });
106
- assignment.node.addDependency(permissionSet);
164
+ assignment.node.addDependency(permissionSet);
165
+ if (assignments.length >= ASSIGNMENT_BATCH_SIZE) {
166
+ const dependency = assignments[assignments.length - ASSIGNMENT_BATCH_SIZE];
167
+ if (dependency !== undefined) {
168
+ assignment.node.addDependency(dependency);
169
+ }
107
170
  }
171
+ assignments.push(assignment);
108
172
  }
109
173
  }
110
174
  }
@@ -74,7 +74,10 @@ export class IdentityCenterGroupMembership extends NestedStack {
74
74
  UserId: userId
75
75
  }
76
76
  },
77
- physicalResourceId: customResources.PhysicalResourceId.of(`recreateGroupMembership${memberSuffix}`)
77
+ physicalResourceId: customResources.PhysicalResourceId.of(`recreateGroupMembership${memberSuffix}`),
78
+ // createGroupMembership throws ConflictException when the
79
+ // membership already exists — keeps onUpdate idempotent.
80
+ ignoreErrorCodesMatching: "ConflictException"
78
81
  },
79
82
  resourceType: IDENTITY_CENTER_USERS_RESOURCE_TYPE
80
83
  });
@@ -86,7 +89,10 @@ export class IdentityCenterGroupMembership extends NestedStack {
86
89
  parameters: {
87
90
  IdentityStoreId: identityStoreId,
88
91
  MembershipId: groupMembershipId.getResponseField("MembershipId")
89
- }
92
+ },
93
+ // deleteGroupMembership throws ResourceNotFoundException when
94
+ // the membership is already gone — keeps onDelete idempotent.
95
+ ignoreErrorCodesMatching: "ResourceNotFoundException"
90
96
  },
91
97
  resourceType: IDENTITY_CENTER_USERS_RESOURCE_TYPE
92
98
  });
@@ -0,0 +1,11 @@
1
+ import { NestedStack, type NestedStackProps } from "aws-cdk-lib";
2
+ import { type Construct } from "constructs";
3
+ export interface IdentityCenterMembershipProps extends NestedStackProps {
4
+ identityStoreId: string;
5
+ groupId: string;
6
+ groupMembers: string[];
7
+ }
8
+ export declare function validateIdentityCenterMembershipProps(props: IdentityCenterMembershipProps): void;
9
+ export declare class IdentityCenterMembership extends NestedStack {
10
+ constructor(scope: Construct, id: string, props: IdentityCenterMembershipProps);
11
+ }
@@ -0,0 +1,61 @@
1
+ import * as customResources from "aws-cdk-lib/custom-resources";
2
+ import { NestedStack } from "aws-cdk-lib";
3
+ import { AwsCustomResource } from "../../resources/aws/utilities/awsCustomResource.js";
4
+ import { GroupMembership } from "../../resources/aws/iam/identityCenter/groupMembership.js";
5
+ import { stripAndCamelCase } from "../../utils/stripAndCamelCase.js";
6
+ const IDENTITY_STORE_SERVICE = "identityStore";
7
+ export function validateIdentityCenterMembershipProps(props) {
8
+ if (!props.identityStoreId) {
9
+ throw new Error("IdentityCenterMembership requires identityStoreId from the parent IdentityCenter construct.");
10
+ }
11
+ if (!props.groupId) {
12
+ throw new Error("IdentityCenterMembership requires groupId from a Group construct.");
13
+ }
14
+ if (props.groupMembers.length === 0) {
15
+ throw new Error("IdentityCenterMembership requires at least one member email. Empty groups should be skipped at the caller.");
16
+ }
17
+ const seen = new Set();
18
+ for (const member of props.groupMembers) {
19
+ if (!member.includes("@")) {
20
+ throw new Error(`IdentityCenterMembership: "${member}" is not a valid email — Identity Center memberships look up users by UserName (email)`);
21
+ }
22
+ if (seen.has(member)) {
23
+ throw new Error(`Duplicate member "${member}" in IdentityCenterMembership groupMembers.`);
24
+ }
25
+ seen.add(member);
26
+ }
27
+ }
28
+ export class IdentityCenterMembership extends NestedStack {
29
+ constructor(scope, id, props) {
30
+ super(scope, id, props);
31
+ validateIdentityCenterMembershipProps(props);
32
+ for (const member of props.groupMembers) {
33
+ const [localPart] = member.split("@");
34
+ const suffix = stripAndCamelCase(localPart ?? member);
35
+ const listUsersCall = {
36
+ service: IDENTITY_STORE_SERVICE,
37
+ action: "listUsers",
38
+ parameters: {
39
+ IdentityStoreId: props.identityStoreId,
40
+ Filters: [
41
+ {
42
+ AttributePath: "UserName",
43
+ AttributeValue: member
44
+ }
45
+ ]
46
+ },
47
+ physicalResourceId: customResources.PhysicalResourceId.of(`listUsers${suffix}`)
48
+ };
49
+ const userLookup = new AwsCustomResource(this, `User${suffix}`, {
50
+ onCreate: listUsersCall,
51
+ onUpdate: listUsersCall
52
+ });
53
+ const userId = userLookup.getResponseField("Users.0.UserId");
54
+ new GroupMembership(this, `Membership${suffix}`, {
55
+ identityStoreId: props.identityStoreId,
56
+ groupId: props.groupId,
57
+ userId
58
+ });
59
+ }
60
+ }
61
+ }
@@ -1,7 +1,7 @@
1
1
  export * from "./identityCenter.js";
2
+ export * from "./identityCenterMembership.js";
2
3
  export * from "./ipam.js";
3
4
  export * from "./ecrDefaultImage.js";
4
- export * from "./eventBus.js";
5
5
  export * from "./oidcConnector.js";
6
6
  export * from "./platform.js";
7
7
  export * from "./accountMonitoringRole.js";
@@ -1,7 +1,7 @@
1
1
  export * from "./identityCenter.js";
2
+ export * from "./identityCenterMembership.js";
2
3
  export * from "./ipam.js";
3
4
  export * from "./ecrDefaultImage.js";
4
- export * from "./eventBus.js";
5
5
  export * from "./oidcConnector.js";
6
6
  export * from "./platform.js";
7
7
  export * from "./accountMonitoringRole.js";
@@ -2,13 +2,6 @@ import { CfnOutput } from "aws-cdk-lib";
2
2
  import { Construct } from "constructs";
3
3
  import { Ipam as IpamClass } from "../../resources/aws/networking/ipam.js";
4
4
  import { getConfig } from "../../utils/getConfig.js";
5
- function operatingRegions(regions) {
6
- const operationRegionArray = [];
7
- for (const region of regions) {
8
- operationRegionArray.push({ regionName: region });
9
- }
10
- return operationRegionArray;
11
- }
12
5
  export class Ipam extends Construct {
13
6
  privateDefaultScopeId;
14
7
  constructor(scope, id, props) {
@@ -18,8 +11,10 @@ export class Ipam extends Construct {
18
11
  const regions = props?.regions || config.allRegions;
19
12
  // Fallback to at least the current region if no regions configured
20
13
  const operationalRegions = regions.length > 0 ? regions : [config.region];
21
- const ipam = new IpamClass(this, "ipam", {
22
- operatingRegions: operatingRegions(operationalRegions),
14
+ const ipam = new IpamClass(this, "Ipam", {
15
+ operatingRegions: operationalRegions.map((regionName) => ({
16
+ regionName
17
+ })),
23
18
  tags: [
24
19
  {
25
20
  key: "fjall:costAllocation:environment",
@@ -28,8 +23,8 @@ export class Ipam extends Construct {
28
23
  ]
29
24
  });
30
25
  this.privateDefaultScopeId = ipam.attrPrivateDefaultScopeId;
31
- new CfnOutput(this, "privateDefaultScopeId", {
32
- key: "privateDefaultScopeId",
26
+ new CfnOutput(this, "IpamPrivateDefaultScopeId", {
27
+ key: "IpamPrivateDefaultScopeId",
33
28
  value: ipam.attrPrivateDefaultScopeId,
34
29
  exportName: "IpamPrivateDefaultScopeId"
35
30
  });
@@ -1,6 +1,7 @@
1
1
  import { CfnOutput, Duration } from "aws-cdk-lib";
2
2
  import * as iam from "aws-cdk-lib/aws-iam";
3
3
  import { Construct } from "constructs";
4
+ import { Role } from "../../resources/aws/iam/role.js";
4
5
  export class OidcConnector extends Construct {
5
6
  deployRoleArn;
6
7
  constructor(scope, id, props) {
@@ -10,10 +11,13 @@ export class OidcConnector extends Construct {
10
11
  const provider = new iam.OpenIdConnectProvider(this, "OidcProvider", {
11
12
  url: issuerUrl,
12
13
  clientIds: ["sts.amazonaws.com"],
14
+ // Placeholder thumbprint. CDK auto-resolves at deploy time for known IdPs;
15
+ // STS trust is enforced via the OIDC issuer URL + `:sub` / `:aud` match.
13
16
  thumbprints: ["0000000000000000000000000000000000000000"]
14
17
  });
15
- const deployRole = new iam.Role(this, "DeployRole", {
18
+ const deployRole = new Role(this, "DeployRole", {
16
19
  roleName: `FjallDeploy${props.fjallOrgId}`,
20
+ path: "/fjall/",
17
21
  maxSessionDuration: Duration.hours(1),
18
22
  assumedBy: new iam.FederatedPrincipal(provider.openIdConnectProviderArn, {
19
23
  StringEquals: { [`${issuerDomain}:aud`]: "sts.amazonaws.com" },
@@ -1,7 +1,10 @@
1
1
  import { Construct } from "constructs";
2
2
  import { OrganisationPolicy } from "../../resources/aws/organisation/organisationPolicy.js";
3
+ // Path prefix `/fjall/` is load-bearing: without it, a member-account admin
4
+ // could bypass protect-* SCPs by creating a `FjallDeploy*` role at the
5
+ // default path.
3
6
  const EXEMPT_ROLE_PATTERNS = [
4
- "arn:aws:iam::*:role/FjallDeploy*",
7
+ "arn:aws:iam::*:role/fjall/FjallDeploy*",
5
8
  "arn:aws:iam::*:role/OrganizationAccountAccessRole"
6
9
  ];
7
10
  const SCP_BYTE_LIMIT = 5120;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,4 @@
1
+ import { Port } from "aws-cdk-lib/aws-ec2";
2
+ import { PrivateDnsNamespace } from "aws-cdk-lib/aws-servicediscovery";
3
+ const _x = Port.tcp(9000);
4
+ const _y = PrivateDnsNamespace;
@@ -1,5 +1,5 @@
1
1
  import { CfnOutput, Stack } from "aws-cdk-lib";
2
- import { EcrDefaultImage, DefaultEventBus, SharedAlarmTopic } from "../../config/aws/index.js";
2
+ import { EcrDefaultImage, SharedAlarmTopic } from "../../config/aws/index.js";
3
3
  import { ManagementEventsTrail } from "../../config/aws/cloudTrail.js";
4
4
  import { OidcConnector } from "../../config/aws/oidcConnector.js";
5
5
  import { AccountMonitoringRole } from "../../config/aws/accountMonitoringRole.js";
@@ -42,7 +42,6 @@ export class Account extends Stack {
42
42
  exportName: "AccountId",
43
43
  description: "AWS Account ID for this account"
44
44
  });
45
- const eventBus = new DefaultEventBus(this, "EventBus");
46
45
  new SharedAlarmTopic(this, "AlarmTopic");
47
46
  const isStandaloneAccount = this.constructor === Account;
48
47
  const ipamPoolId = this.node.tryGetContext("ipamPoolId");
@@ -71,8 +70,7 @@ export class Account extends Stack {
71
70
  });
72
71
  new EcrDefaultImage(this, "EcrDefaultImage", {
73
72
  region: this.resolvedRegion,
74
- accountId: this.account,
75
- eventBusArn: eventBus.defaultEventBusArn.value
73
+ accountId: this.account
76
74
  });
77
75
  const environment = config.environment ?? "unknown";
78
76
  if (config.disasterRecoveryRegion) {
@@ -5,6 +5,7 @@ import { DomainCertificate } from "../../resources/aws/networking/domainCertific
5
5
  import { NsRecord } from "../../resources/aws/networking/dnsRecord/index.js";
6
6
  import { composeTypedDnsRecords } from "./dnsRecordComposer.js";
7
7
  import { toPascalCase, getSafeZoneName } from "../../utils/capitaliseString.js";
8
+ import { resolveOrgId } from "../../utils/cdkContext.js";
8
9
  /**
9
10
  * Composition for `registrar: "route53"`. Creates (or imports) the apex
10
11
  * `HostedZone`, wires child-account delegations via `NsRecord` (pointing at
@@ -21,12 +22,15 @@ import { toPascalCase, getSafeZoneName } from "../../utils/capitaliseString.js";
21
22
  */
22
23
  export function composeApexDomain(scope, props) {
23
24
  const safeZone = toPascalCase(getSafeZoneName(props.zoneName));
25
+ // Gotcha: the cross-account DelegationRole imports CFN export `OrganisationId`
26
+ // from the org/account stack — only published when an `orgId` CDK context
27
+ // value is set. Single-account deploys would roll back at synth without
28
+ // this gate.
29
+ const inOrganisation = resolveOrgId(scope.node) !== undefined;
24
30
  const hostedZoneConstruct = new HostedZone(scope, `${safeZone}HostedZone`, {
25
31
  zoneName: props.zoneName,
26
32
  hostedZoneId: props.hostedZoneId,
27
- // Only create the delegation role on the create path; imported zones do
28
- // not manage IAM themselves.
29
- createDelegationRole: props.hostedZoneId === undefined,
33
+ createDelegationRole: props.hostedZoneId === undefined && inOrganisation,
30
34
  costAllocationEnvironment: props.costAllocationEnvironment,
31
35
  costAllocationDomain: props.zoneName
32
36
  });
@@ -52,13 +56,9 @@ export function composeApexDomain(scope, props) {
52
56
  const childZoneName = `${delegation.subdomain}.${props.zoneName}`;
53
57
  const safeChild = toPascalCase(getSafeZoneName(childZoneName));
54
58
  const childExports = getDomainExportNames(childZoneName);
55
- // The child account's stack publishes the hosted-zone nameservers under a
56
- // predictable output key (Phase 0 HostedZone emits `{safeZone}Nameservers`
57
- // as a joined comma-separated string — we split it back at deploy time
58
- // via Fn.split). The child HZ id import is declared as a cross-phase
59
- // dependency — if the child stack has not deployed, CFN fails at deploy.
60
- const nameserversExportName = childExports.hostedZoneId.replace(/-hosted-zone-id$/, "-nameservers");
61
- const nameserversToken = Fn.importValue(nameserversExportName);
59
+ // Cross-phase dependency: child stack must deploy first; CFN fails here
60
+ // at deploy if the nameservers export does not yet exist.
61
+ const nameserversToken = Fn.importValue(childExports.nameservers);
62
62
  new NsRecord(scope, `${safeZone}Delegation${safeChild}${index}`, {
63
63
  zone: hostedZoneConstruct.hostedZone,
64
64
  zoneName: props.zoneName,
@@ -0,0 +1,10 @@
1
+ import { type IVpc } from "aws-cdk-lib/aws-ec2";
2
+ import { Ec2Instance } from "../../resources/aws/compute/ec2.js";
3
+ import { type AwsStack } from "../../resources/index.js";
4
+ export interface BastionConfig {
5
+ instanceType?: string;
6
+ }
7
+ export interface BastionResult {
8
+ bastion: Ec2Instance;
9
+ }
10
+ export declare function createBastion(networkStack: AwsStack, appName: string, stackPrefix: string, vpc: IVpc, config: BastionConfig | true): BastionResult;
@@ -0,0 +1,29 @@
1
+ import { CfnOutput } from "aws-cdk-lib";
2
+ import { Ec2Instance } from "../../resources/aws/compute/ec2.js";
3
+ import { toPascalCase } from "../../utils/capitaliseString.js";
4
+ export function createBastion(networkStack, appName, stackPrefix, vpc, config) {
5
+ const instanceType = typeof config === "object" && config.instanceType
6
+ ? config.instanceType
7
+ : "t4g.micro";
8
+ const bastionId = `${stackPrefix}Bastion`;
9
+ const scope = networkStack.getStack();
10
+ const bastion = new Ec2Instance(scope, bastionId, {
11
+ serviceName: `${stackPrefix}Bastion`,
12
+ instanceType,
13
+ vpc,
14
+ enableSSH: false,
15
+ minCapacity: 1,
16
+ maxCapacity: 1
17
+ });
18
+ networkStack.addConstruct(bastion);
19
+ const outputPrefix = toPascalCase(appName);
20
+ new CfnOutput(scope, `${outputPrefix}BastionInstanceId`, {
21
+ value: bastion.getAutoScalingGroup().autoScalingGroupName,
22
+ description: "Bastion ASG name for SSM tunnel discovery"
23
+ });
24
+ new CfnOutput(scope, `${outputPrefix}BastionSecurityGroupId`, {
25
+ value: bastion.asgSecurityGroup.securityGroupId,
26
+ description: "Bastion security group ID"
27
+ });
28
+ return { bastion };
29
+ }
@@ -2,7 +2,7 @@ import { type StackProps, Stack } from "aws-cdk-lib";
2
2
  import { InstanceType } from "aws-cdk-lib/aws-ec2";
3
3
  import { type Construct } from "constructs";
4
4
  import { type KeyValue } from "../../types.js";
5
- declare enum agentRelease {
5
+ export declare enum AgentRelease {
6
6
  STABLE = "stable",
7
7
  BETA = "beta",
8
8
  EDGE = "edge"
@@ -11,7 +11,7 @@ export interface BuildkiteProps extends StackProps {
11
11
  accountId?: string;
12
12
  elasticStackVersion: string;
13
13
  keyName: string;
14
- buildkiteAgentRelease: agentRelease;
14
+ buildkiteAgentRelease: AgentRelease;
15
15
  buildkiteAgentTags: string;
16
16
  buildkiteAgentTimestampLines: boolean;
17
17
  buildkiteAgentExperiments: string;