Skip to content

Commit e76f581

Browse files
fix(core): Updating Security Hub Standards based on Configuration file (#526)
* fix(core): enables disabling security hub frameworks and re-enabling individual controls within the frameworks * fix(core): Disable/Enable SecurityHub Controls based on Config Co-authored-by: Brian969 <56414362+Brian969@users.noreply.github.com>
1 parent 71c48fb commit e76f581

File tree

3 files changed

+180
-19
lines changed
  • src
    • deployments/cdk/src/apps
    • lib/custom-resources
      • cdk-security-hub-disable-controls/runtime/src
      • cdk-security-hub-enable/runtime/src

3 files changed

+180
-19
lines changed

src/deployments/cdk/src/apps/phase-2.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,8 @@ export async function deploy({ acceleratorConfig, accountStacks, accounts, conte
219219
}
220220
}
221221

222-
// Deploy Security Hub Step-1
222+
// Deploy Security Hub in Security Account
223+
// Enables Security Hub, Standards and send invites to member accounts
223224
await securityHub.step1({
224225
accountStacks,
225226
accounts,

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

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
import * as AWS from 'aws-sdk';
22
AWS.config.logger = console;
3-
import { CloudFormationCustomResourceEvent } from 'aws-lambda';
3+
import { CloudFormationCustomResourceEvent, CloudFormationCustomResourceUpdateEvent } from 'aws-lambda';
44
import { throttlingBackOff } from '@aws-accelerator/custom-resource-cfn-utils';
55
import { errorHandler } from '@aws-accelerator/custom-resource-runtime-cfn-response';
66

77
const hub = new AWS.SecurityHub();
88

99
export const handler = errorHandler(onEvent);
1010

11+
interface SecurityHubStandard {
12+
name: string;
13+
'controls-to-disable': string[] | undefined;
14+
}
15+
1116
async function onEvent(event: CloudFormationCustomResourceEvent) {
1217
console.log(`Disable Security Hub Standards specific controls...`);
1318
console.log(JSON.stringify(event, null, 2));
@@ -62,10 +67,77 @@ async function onCreate(event: CloudFormationCustomResourceEvent) {
6267
);
6368
}
6469
}
70+
71+
return {
72+
physicalResourceId: `SecurityHubEnableControls`,
73+
};
6574
}
6675

67-
async function onUpdate(event: CloudFormationCustomResourceEvent) {
68-
return onCreate(event);
76+
async function onUpdate(event: CloudFormationCustomResourceUpdateEvent) {
77+
const standards = event.ResourceProperties.standards as SecurityHubStandard[];
78+
const oldStandards = event.OldResourceProperties.standards as SecurityHubStandard[];
79+
const standardNames = standards.map(st => st.name);
80+
81+
const standardsResponse = await throttlingBackOff(() => hub.describeStandards().promise());
82+
const enabledStandardsResponse = await throttlingBackOff(() => hub.getEnabledStandards().promise());
83+
// Getting standards and disabling specific Controls for each standard
84+
for (const standard of standards) {
85+
const standardArn = standardsResponse.Standards?.find(x => x.Name === standard.name)?.StandardsArn;
86+
const standardSubscriptionArn = enabledStandardsResponse.StandardsSubscriptions?.find(
87+
s => s.StandardsArn === standardArn,
88+
)?.StandardsSubscriptionArn;
89+
90+
const standardControls = await throttlingBackOff(() =>
91+
hub
92+
.describeStandardsControls({
93+
StandardsSubscriptionArn: standardSubscriptionArn!,
94+
MaxResults: 100,
95+
})
96+
.promise(),
97+
);
98+
for (const disableControl of standard['controls-to-disable'] || []) {
99+
const standardControl = standardControls.Controls?.find(x => x.ControlId === disableControl);
100+
if (!standardControl) {
101+
console.log(`Control "${disableControl}" not found for Standard "${standard.name}"`);
102+
continue;
103+
}
104+
105+
console.log(`Disabling Control "${disableControl}" for Standard "${standard.name}"`);
106+
await throttlingBackOff(() =>
107+
hub
108+
.updateStandardsControl({
109+
StandardsControlArn: standardControl.StandardsControlArn!,
110+
ControlStatus: 'DISABLED',
111+
DisabledReason: 'Control disabled by Accelerator',
112+
})
113+
.promise(),
114+
);
115+
}
116+
const oldStandard = oldStandards.find(st => st.name === standard.name);
117+
if (oldStandard) {
118+
const enableControls = oldStandard['controls-to-disable']?.filter(
119+
c => !standard['controls-to-disable']?.includes(c),
120+
);
121+
for (const enableControl of enableControls || []) {
122+
const standardControl = standardControls.Controls?.find(x => x.ControlId === enableControl);
123+
if (!standardControl) {
124+
console.log(`Control "${enableControl}" not found for Standard "${standard.name}"`);
125+
continue;
126+
}
127+
await throttlingBackOff(() =>
128+
hub
129+
.updateStandardsControl({
130+
StandardsControlArn: standardControl.StandardsControlArn!,
131+
ControlStatus: 'ENABLED',
132+
})
133+
.promise(),
134+
);
135+
}
136+
}
137+
}
138+
return {
139+
physicalResourceId: `SecurityHubEnableControls`,
140+
};
69141
}
70142

71143
async function onDelete(_: CloudFormationCustomResourceEvent) {

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

Lines changed: 103 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,23 @@
11
import * as AWS from 'aws-sdk';
22
AWS.config.logger = console;
3-
import { CloudFormationCustomResourceEvent } from 'aws-lambda';
3+
import {
4+
CloudFormationCustomResourceDeleteEvent,
5+
CloudFormationCustomResourceEvent,
6+
CloudFormationCustomResourceUpdateEvent,
7+
} from 'aws-lambda';
48
import { throttlingBackOff } from '@aws-accelerator/custom-resource-cfn-utils';
59
import { errorHandler } from '@aws-accelerator/custom-resource-runtime-cfn-response';
10+
import { StandardsSubscriptionRequests } from 'aws-sdk/clients/securityhub';
611

712
const hub = new AWS.SecurityHub();
813

914
export const handler = errorHandler(onEvent);
1015

16+
interface SecurityHubStandard {
17+
name: string;
18+
'controls-to-disable': string[] | undefined;
19+
}
20+
1121
async function onEvent(event: CloudFormationCustomResourceEvent) {
1222
console.log(`Enabling Security Hub and Security Hub Standards...`);
1323
console.log(JSON.stringify(event, null, 2));
@@ -34,30 +44,108 @@ async function onCreate(event: CloudFormationCustomResourceEvent) {
3444
}
3545
}
3646

37-
const standards = event.ResourceProperties.standards;
47+
const standards = event.ResourceProperties.standards as SecurityHubStandard[];
3848
const standardsResponse = await throttlingBackOff(() => hub.describeStandards().promise());
3949

40-
// Enable standards based on input
50+
const standardRequests: StandardsSubscriptionRequests = [];
4151
for (const standard of standards) {
42-
const standardArn = standardsResponse.Standards?.find(x => x.Name === standard.name)?.StandardsArn;
52+
standardRequests.push({
53+
StandardsArn: standardsResponse.Standards?.find(x => x.Name === standard.name)?.StandardsArn!,
54+
});
55+
}
56+
57+
await throttlingBackOff(() =>
58+
hub
59+
.batchEnableStandards({
60+
StandardsSubscriptionRequests: standardRequests,
61+
})
62+
.promise(),
63+
);
64+
65+
return {
66+
physicalResourceId: `SecurityHubEnable`,
67+
};
68+
}
69+
70+
async function onUpdate(event: CloudFormationCustomResourceUpdateEvent) {
71+
try {
72+
await throttlingBackOff(() => hub.enableSecurityHub().promise());
73+
} catch (error) {
74+
if (error.code === 'ResourceConflictException') {
75+
console.log('Account is already subscribed to Security Hub');
76+
} else {
77+
throw new Error(error);
78+
}
79+
}
80+
81+
const standardNames = (event.ResourceProperties.standards as SecurityHubStandard[]).map(st => st.name);
82+
const standardsResponse = await throttlingBackOff(() => hub.describeStandards().promise());
83+
84+
const oldStandardNames = (event.OldResourceProperties.standards as SecurityHubStandard[]).map(st => st.name);
85+
86+
const removedStandards = oldStandardNames.filter(st => !standardNames.includes(st));
87+
88+
const standardRequests: StandardsSubscriptionRequests = [];
89+
for (const standard of standardNames) {
90+
standardRequests.push({
91+
StandardsArn: standardsResponse.Standards?.find(x => x.Name === standard)?.StandardsArn!,
92+
});
93+
}
94+
95+
await throttlingBackOff(() =>
96+
hub
97+
.batchEnableStandards({
98+
StandardsSubscriptionRequests: standardRequests,
99+
})
100+
.promise(),
101+
);
102+
103+
// Disable standards based on change
104+
if (removedStandards.length > 0) {
105+
const getEnabledStandards = await throttlingBackOff(() => hub.getEnabledStandards().promise());
106+
const enabledStandardArns = standardsResponse.Standards?.filter(st => removedStandards.includes(st.Name!)).map(
107+
x => x.StandardsArn,
108+
);
109+
const enabledStandardSubscriptionsArns = getEnabledStandards.StandardsSubscriptions?.filter(st =>
110+
enabledStandardArns?.includes(st.StandardsArn),
111+
).map(x => x.StandardsSubscriptionArn);
43112
await throttlingBackOff(() =>
44113
hub
45-
.batchEnableStandards({
46-
StandardsSubscriptionRequests: [
47-
{
48-
StandardsArn: standardArn!,
49-
},
50-
],
114+
.batchDisableStandards({
115+
StandardsSubscriptionArns: enabledStandardSubscriptionsArns!,
51116
})
52117
.promise(),
53118
);
54119
}
55-
}
56120

57-
async function onUpdate(event: CloudFormationCustomResourceEvent) {
58-
return onCreate(event);
121+
return {
122+
physicalResourceId: `SecurityHubEnable`,
123+
};
59124
}
60125

61-
async function onDelete(_: CloudFormationCustomResourceEvent) {
62-
console.log(`Nothing to do for delete...`);
126+
async function onDelete(event: CloudFormationCustomResourceDeleteEvent) {
127+
if (event.PhysicalResourceId !== 'SecurityHubEnable') {
128+
return;
129+
}
130+
const standardNames = (event.ResourceProperties.standards as SecurityHubStandard[]).map(st => st.name);
131+
132+
const allStandardsResponse = await throttlingBackOff(() => hub.describeStandards().promise());
133+
// Disable standards based on change
134+
if (standardNames.length > 0) {
135+
const getEnabledStandards = await throttlingBackOff(() => hub.getEnabledStandards().promise());
136+
const enabledStandardArns = allStandardsResponse.Standards?.filter(st => standardNames.includes(st.Name!)).map(
137+
x => x.StandardsArn,
138+
);
139+
const enabledStandardSubscriptionsArns = getEnabledStandards.StandardsSubscriptions?.filter(st =>
140+
enabledStandardArns?.includes(st.StandardsArn),
141+
).map(x => x.StandardsSubscriptionArn);
142+
143+
await throttlingBackOff(() =>
144+
hub
145+
.batchDisableStandards({
146+
StandardsSubscriptionArns: enabledStandardSubscriptionsArns!,
147+
})
148+
.promise(),
149+
);
150+
}
63151
}

0 commit comments

Comments
 (0)