11import * as AWS from 'aws-sdk' ;
22AWS . config . logger = console ;
3- import { CloudFormationCustomResourceEvent } from 'aws-lambda' ;
3+ import {
4+ CloudFormationCustomResourceDeleteEvent ,
5+ CloudFormationCustomResourceEvent ,
6+ CloudFormationCustomResourceUpdateEvent ,
7+ } from 'aws-lambda' ;
48import { throttlingBackOff } from '@aws-accelerator/custom-resource-cfn-utils' ;
59import { errorHandler } from '@aws-accelerator/custom-resource-runtime-cfn-response' ;
10+ import { StandardsSubscriptionRequests } from 'aws-sdk/clients/securityhub' ;
611
712const hub = new AWS . SecurityHub ( ) ;
813
914export const handler = errorHandler ( onEvent ) ;
1015
16+ interface SecurityHubStandard {
17+ name : string ;
18+ 'controls-to-disable' : string [ ] | undefined ;
19+ }
20+
1121async 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