Skip to content

Commit 4185ef3

Browse files
hickeydh-awsDustin Hickey
andauthored
added pagination (#789)
Co-authored-by: Dustin Hickey <hickeydh@amazon.amazon.com>
1 parent 52f6dcc commit 4185ef3

File tree

4 files changed

+143
-35
lines changed
  • reference-artifacts/Custom-Scripts/Developer-Scripts/src
  • src
    • deployments/cdk
    • lib/custom-resources
      • cdk-security-hub-accept-invites/runtime/src
      • cdk-security-hub-disable-controls/runtime/src

4 files changed

+143
-35
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import * as fs from 'fs';
2+
import * as aws from 'aws-sdk';
3+
import * as dynamodb from 'aws-sdk/clients/dynamodb';
4+
import { resourceLimits } from 'node:worker_threads';
5+
const DEV_FILE_PATH = '../../../src/deployments/cdk/';
6+
const DEV_OUTPUTS_FILE_PATH = `${DEV_FILE_PATH}outputs.json`;
7+
8+
const env = process.env;
9+
const acceleratorPrefix = env.ACCELERATOR_PREFIX || 'ASEA-';
10+
const documentClient = new aws.DynamoDB.DocumentClient();
11+
12+
export const scanDDBTable = async (tableName: string) => {
13+
const items: dynamodb.ItemList = [];
14+
let token: dynamodb.Key | undefined;
15+
const props: dynamodb.ScanInput = {
16+
TableName: tableName,
17+
};
18+
do {
19+
const response = await documentClient.scan(props).promise();
20+
token = response.LastEvaluatedKey;
21+
props.ExclusiveStartKey = token!;
22+
items.push(...response.Items!);
23+
} while (token);
24+
25+
return items;
26+
};
27+
28+
export const loadOutputs = (items: dynamodb.ItemList) => {
29+
return items.reduce((outputList: any, item) => {
30+
const output = JSON.parse(item.outputValue as string);
31+
outputList.push(...output);
32+
return outputList;
33+
}, []);
34+
};
35+
36+
export const loadConfigs = (items: dynamodb.ItemList) => {
37+
return items.map(item => {
38+
return { id: item.id, value: JSON.parse(item.value as string) };
39+
});
40+
};
41+
42+
export const writeConfigs = (configList: any) => {
43+
for (const config of configList) {
44+
if (config.id.includes('accounts/0')) {
45+
config.id = 'accounts';
46+
}
47+
if (config.id !== 'accounts-items-count') {
48+
fs.writeFileSync(`${DEV_FILE_PATH}${config.id}.json`, JSON.stringify(config.value, null, 2));
49+
}
50+
}
51+
};
52+
scanDDBTable(`${acceleratorPrefix}Outputs`)
53+
.then(outputs => loadOutputs(outputs))
54+
.then(parsedOutputs => fs.writeFileSync(DEV_OUTPUTS_FILE_PATH, JSON.stringify(parsedOutputs, null, 2)));
55+
56+
scanDDBTable(`${acceleratorPrefix}Parameters`)
57+
.then(params => loadConfigs(params))
58+
.then(configs => writeConfigs(configs));

src/deployments/cdk/toolkit.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -139,14 +139,24 @@ export class CdkToolkit {
139139
console.log(`There are no stacks to be deployed`);
140140
return [];
141141
}
142-
143-
let combinedOutputs: StackOutput[];
142+
let combinedOutputs: StackOutput[] = [];
144143
if (parallel) {
145-
// Deploy all stacks in parallel
146-
const promises = stacks.map(stack => this.deployStack(stack));
147-
// Wait for all promises to be fulfilled
148-
const outputsList = await fulfillAll(promises);
149-
combinedOutputs = outputsList.reduce((result, output) => [...result, ...output]);
144+
const pageSize = 900;
145+
let stackPromises: any[] = [];
146+
for (let i = 0; i < stacks.length; i++) {
147+
const stack = stacks[i];
148+
console.log(`deploying stack ${i + 1} of ${stacks.length}`);
149+
stackPromises.push(this.deployStack(stack));
150+
if (stackPromises.length > pageSize - 1 || i === stacks.length - 1) {
151+
const results = await Promise.all(stackPromises);
152+
console.log(`Deployed stacks ${i + 1} of ${stacks.length}`);
153+
stackPromises = [];
154+
if (results) {
155+
combinedOutputs.push(...results);
156+
}
157+
}
158+
}
159+
combinedOutputs = combinedOutputs.flat(2);
150160
} else {
151161
// Deploy all stacks sequentially
152162
combinedOutputs = [];
@@ -157,6 +167,7 @@ export class CdkToolkit {
157167
}
158168

159169
// Merge all stack outputs
170+
console.log(JSON.stringify(combinedOutputs, null, 4));
160171
return combinedOutputs;
161172
}
162173

src/lib/custom-resources/cdk-security-hub-accept-invites/runtime/src/index.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,20 @@ async function onCreate(event: CloudFormationCustomResourceEvent) {
3636
}
3737

3838
// Check for pending invitations from Master
39-
const invitations = await throttlingBackOff(() => hub.listInvitations().promise());
40-
if (!invitations.Invitations) {
39+
let token: string | undefined;
40+
let invitations: AWS.SecurityHub.InvitationList = [];
41+
do {
42+
const response = await throttlingBackOff(() => hub.listInvitations({ NextToken: token }).promise());
43+
if (response.Invitations) {
44+
invitations.push(...response.Invitations);
45+
}
46+
token = response.NextToken;
47+
} while (token);
48+
if (!invitations) {
4149
console.log(`No Security Hub invitations found`);
4250
} else {
4351
// Accepting Invitation from Master account
44-
const ownerInvitation = invitations.Invitations.find(x => x.AccountId === masterAccountId);
52+
const ownerInvitation = invitations.find(x => x.AccountId === masterAccountId);
4553
if (ownerInvitation) {
4654
const invitationId = ownerInvitation?.InvitationId!;
4755
await throttlingBackOff(() =>

src/lib/custom-resources/cdk-security-hub-disable-controls/runtime/src/index.ts

Lines changed: 56 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -30,26 +30,18 @@ async function onEvent(event: CloudFormationCustomResourceEvent) {
3030

3131
async function onCreate(event: CloudFormationCustomResourceEvent) {
3232
const standards = event.ResourceProperties.standards;
33-
const standardsResponse = await throttlingBackOff(() => hub.describeStandards().promise());
34-
const enabledStandardsResponse = await throttlingBackOff(() => hub.getEnabledStandards().promise());
33+
const standardsResponse = await describeStandards();
34+
const enabledStandardsResponse = await getEnabledStandards();
3535

3636
// Getting standards and disabling specific Controls for each standard
3737
for (const standard of standards) {
38-
const standardArn = standardsResponse.Standards?.find(x => x.Name === standard.name)?.StandardsArn;
39-
const standardSubscriptionArn = enabledStandardsResponse.StandardsSubscriptions?.find(
40-
s => s.StandardsArn === standardArn,
41-
)?.StandardsSubscriptionArn;
38+
const standardArn = standardsResponse?.find(x => x.Name === standard.name)?.StandardsArn;
39+
const standardSubscriptionArn = enabledStandardsResponse?.find(s => s.StandardsArn === standardArn)
40+
?.StandardsSubscriptionArn;
4241

43-
const standardControls = await throttlingBackOff(() =>
44-
hub
45-
.describeStandardsControls({
46-
StandardsSubscriptionArn: standardSubscriptionArn!,
47-
MaxResults: 100,
48-
})
49-
.promise(),
50-
);
42+
const standardControls = await describeStandardsControls(standardSubscriptionArn);
5143
for (const disableControl of standard['controls-to-disable']) {
52-
const standardControl = standardControls.Controls?.find(x => x.ControlId === disableControl);
44+
const standardControl = standardControls?.find(x => x.ControlId === disableControl);
5345
if (!standardControl) {
5446
console.log(`Control "${disableControl}" not found for Standard "${standard.name}"`);
5547
continue;
@@ -87,16 +79,9 @@ async function onUpdate(event: CloudFormationCustomResourceUpdateEvent) {
8779
s => s.StandardsArn === standardArn,
8880
)?.StandardsSubscriptionArn;
8981

90-
const standardControls = await throttlingBackOff(() =>
91-
hub
92-
.describeStandardsControls({
93-
StandardsSubscriptionArn: standardSubscriptionArn!,
94-
MaxResults: 100,
95-
})
96-
.promise(),
97-
);
82+
const standardControls = await describeStandardsControls(standardSubscriptionArn);
9883
for (const disableControl of standard['controls-to-disable'] || []) {
99-
const standardControl = standardControls.Controls?.find(x => x.ControlId === disableControl);
84+
const standardControl = standardControls?.find(x => x.ControlId === disableControl);
10085
if (!standardControl) {
10186
console.log(`Control "${disableControl}" not found for Standard "${standard.name}"`);
10287
continue;
@@ -119,7 +104,7 @@ async function onUpdate(event: CloudFormationCustomResourceUpdateEvent) {
119104
c => !standard['controls-to-disable']?.includes(c),
120105
);
121106
for (const enableControl of enableControls || []) {
122-
const standardControl = standardControls.Controls?.find(x => x.ControlId === enableControl);
107+
const standardControl = standardControls?.find(x => x.ControlId === enableControl);
123108
if (!standardControl) {
124109
console.log(`Control "${enableControl}" not found for Standard "${standard.name}"`);
125110
continue;
@@ -140,6 +125,52 @@ async function onUpdate(event: CloudFormationCustomResourceUpdateEvent) {
140125
};
141126
}
142127

128+
async function describeStandards() {
129+
const standards = [];
130+
let token: string | undefined;
131+
do {
132+
const response = await throttlingBackOff(() => hub.describeStandards().promise());
133+
if (response.Standards) {
134+
standards.push(...response.Standards);
135+
}
136+
token = response.NextToken;
137+
} while (token);
138+
139+
return standards;
140+
}
141+
142+
async function getEnabledStandards() {
143+
const enabledStandards = [];
144+
let token: string | undefined;
145+
do {
146+
const response = await throttlingBackOff(() => hub.getEnabledStandards().promise());
147+
if (response.StandardsSubscriptions) {
148+
enabledStandards.push(...response.StandardsSubscriptions);
149+
}
150+
token = response.NextToken;
151+
} while (token);
152+
153+
return enabledStandards;
154+
}
155+
156+
async function describeStandardsControls(subscriptionArn: string | undefined) {
157+
let token: string | undefined;
158+
const standardControls: any[] = [];
159+
if (!subscriptionArn) {
160+
return standardControls;
161+
}
162+
do {
163+
const response = await throttlingBackOff(() =>
164+
hub.describeStandardsControls({ StandardsSubscriptionArn: subscriptionArn, NextToken: token }).promise(),
165+
);
166+
if (response.Controls) {
167+
standardControls.push(...response.Controls);
168+
}
169+
token = response.NextToken;
170+
} while (token);
171+
return standardControls;
172+
}
173+
143174
async function onDelete(_: CloudFormationCustomResourceEvent) {
144175
console.log(`Nothing to do for delete...`);
145176
}

0 commit comments

Comments
 (0)