Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.1.1] - 2024-04-10

### Changed

- Changed order of CloudFormation parameters to emphasize the Security Control playbook
- Changed default for all playbooks other than SC to 'no'
- Updated descriptions of playbook parameters
- Updated architecture diagram

## [2.1.0] - 2024-03-28

### Added
Expand Down
Binary file modified docs/architecture_diagram.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "automated_security_response_on_aws"
version = "2.1.0"
version = "2.1.1"

[tool.setuptools]
package-dir = {"" = "source"}
Expand Down
2 changes: 1 addition & 1 deletion solution-manifest.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
id: SO0111
name: security-hub-automated-response-and-remediation
version: 2.1.0
version: 2.1.1
cloudformation_templates:
- template: aws-sharr-deploy.template
main_template: true
Expand Down
42 changes: 29 additions & 13 deletions source/lib/__snapshots__/member-stack.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -103,15 +103,31 @@ exports[`member stack snapshot matches 1`] = `
},
{
"Label": {
"default": "Playbooks",
"default": "Consolidated control finding Playbook",
},
"Parameters": [
"LoadSCMemberStack",
],
},
{
"Label": {
"default": "Security Standard Playbooks",
},
"Parameters": [
"LoadAFSBPMemberStack",
"LoadCIS120MemberStack",
"LoadCIS140MemberStack",
"LoadNIST80053MemberStack",
"LoadPCI321MemberStack",
"LoadSCMemberStack",
],
},
{
"Label": {
"default": "Configuration",
},
"Parameters": [
"CreateS3BucketForRedshiftAuditLogging",
"SecHubAdminAccount",
],
},
],
Expand All @@ -137,44 +153,44 @@ exports[`member stack snapshot matches 1`] = `
"yes",
"no",
],
"Default": "yes",
"Description": "Load Playbook member stack for AFSBP?",
"Default": "no",
"Description": "Install the member components for automated remediation of AFSBP controls?",
"Type": "String",
},
"LoadCIS120MemberStack": {
"AllowedValues": [
"yes",
"no",
],
"Default": "yes",
"Description": "Load Playbook member stack for CIS120?",
"Default": "no",
"Description": "Install the member components for automated remediation of CIS120 controls?",
"Type": "String",
},
"LoadCIS140MemberStack": {
"AllowedValues": [
"yes",
"no",
],
"Default": "yes",
"Description": "Load Playbook member stack for CIS140?",
"Default": "no",
"Description": "Install the member components for automated remediation of CIS140 controls?",
"Type": "String",
},
"LoadNIST80053MemberStack": {
"AllowedValues": [
"yes",
"no",
],
"Default": "yes",
"Description": "Load Playbook member stack for NIST80053?",
"Default": "no",
"Description": "Install the member components for automated remediation of NIST80053 controls?",
"Type": "String",
},
"LoadPCI321MemberStack": {
"AllowedValues": [
"yes",
"no",
],
"Default": "yes",
"Description": "Load Playbook member stack for PCI321?",
"Default": "no",
"Description": "Install the member components for automated remediation of PCI321 controls?",
"Type": "String",
},
"LoadSCMemberStack": {
Expand All @@ -183,7 +199,7 @@ exports[`member stack snapshot matches 1`] = `
"no",
],
"Default": "yes",
"Description": "Load Playbook member stack for SC?",
"Description": "If the consolidated control findings feature is turned on in Security Hub, only enable the Security Control (SC) playbook. If the feature is not turned on, enable the playbooks for the security standards that are enabled in Security Hub. Enabling additional playbooks can result in reaching the quota for EventBridge Rules.",
"Type": "String",
},
"LogGroupName": {
Expand Down
2 changes: 2 additions & 0 deletions source/lib/admin-account-param.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { CfnParameter } from 'aws-cdk-lib';
import { Construct } from 'constructs';

export default class AdminAccountParam extends Construct {
public readonly paramId: string;
public readonly value: string;

constructor(scope: Construct, id: string) {
Expand All @@ -16,6 +17,7 @@ export default class AdminAccountParam extends Construct {
allowedPattern: accountIdRegex.source,
});
param.overrideLogicalId(`SecHubAdminAccount`);
this.paramId = param.logicalId;

this.value = param.valueAsString;
}
Expand Down
55 changes: 55 additions & 0 deletions source/lib/admin-playbook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { CfnCondition, CfnParameter, CfnResource, Fn, NestedStack, Stack } from 'aws-cdk-lib';
import { Construct } from 'constructs';

export interface AdminPlaybookProps {
name: string;
stackDependencies?: CfnResource[];
defaultState?: 'yes' | 'no';
description?: string;
}

export class AdminPlaybook {
parameterName = '';
playbookStack: Stack;

constructor(scope: Construct, props: AdminPlaybookProps) {
const templateFile = `${props.name}Stack.template`;
const illegalChars = /[\\._]/g;
const playbookName = props.name.replace(illegalChars, '');
this.parameterName = `Load${playbookName}AdminStack`;

//---------------------------------------------------------------------
// Playbook Template Nested Stack
//
const stackOption = new CfnParameter(scope, `LoadAdminStack${playbookName}`, {
type: 'String',
description:
props.description ?? `Install the admin components for automated remediation of ${props.name} controls?`,
default: props.defaultState ?? 'no',
allowedValues: ['yes', 'no'],
});
stackOption.overrideLogicalId(this.parameterName);

this.playbookStack = new NestedStack(scope, `PlaybookAdminStack${playbookName}`);
const cfnStack = this.playbookStack.nestedStackResource as CfnResource;
cfnStack.addPropertyOverride(
'TemplateURL',
'https://' +
Fn.findInMap('SourceCode', 'General', 'S3Bucket') +
'-reference.s3.amazonaws.com/' +
Fn.findInMap('SourceCode', 'General', 'KeyPrefix') +
'/playbooks/' +
templateFile,
);
cfnStack.cfnOptions.condition = new CfnCondition(scope, `load${playbookName}Cond`, {
expression: Fn.conditionEquals(stackOption, 'yes'),
});
props.stackDependencies?.forEach((dependency) => {
cfnStack.node.addDependency(dependency);
});
cfnStack.overrideLogicalId(`PlaybookAdminStack${props.name}`);
}
}
50 changes: 50 additions & 0 deletions source/lib/member-playbook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { CfnCondition, CfnParameter, CfnResource, Fn, Stack } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import { SerializedNestedStackFactory } from './cdk-helper/nested-stack';

export interface MemberPlaybookProps {
readonly name: string;
readonly nestedStackFactory: SerializedNestedStackFactory;
readonly parameters?: { [_: string]: string };
readonly stackDependencies?: CfnResource[];
readonly defaultState?: 'yes' | 'no';
readonly description?: string;
}

export class MemberPlaybook {
parameterName = '';
playbookStack: Stack;

constructor(scope: Construct, props: MemberPlaybookProps) {
const templateFile = `${props.name}MemberStack.template`;
const illegalChars = /[\\._]/g;
const playbookName = props.name.replace(illegalChars, '');
this.parameterName = `Load${playbookName}MemberStack`;

//---------------------------------------------------------------------
// Playbook Template Nested Stack
//
const stackOption = new CfnParameter(scope, `LoadMemberStack${playbookName}`, {
type: 'String',
description:
props.description ?? `Install the member components for automated remediation of ${props.name} controls?`,
default: props.defaultState ?? 'no',
allowedValues: ['yes', 'no'],
});
stackOption.overrideLogicalId(this.parameterName);

this.playbookStack = props.nestedStackFactory.addNestedStack(`PlaybookMemberStack${playbookName}`, {
templateRelativePath: `playbooks/${templateFile}`,
parameters: props.parameters,
condition: new CfnCondition(scope, `load${playbookName}Cond`, {
expression: Fn.conditionEquals(stackOption, 'yes'),
}),
});

const cfnStack = this.playbookStack.nestedStackResource as CfnResource;
cfnStack.overrideLogicalId(`PlaybookMemberStack${props.name}`);
}
}
1 change: 0 additions & 1 deletion source/lib/member-stack.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,6 @@ describe('member stack', function () {

const expectedTemplateParameterProperties = {
AllowedValues: ['yes', 'no'],
Default: 'yes',
Type: 'String',
};

Expand Down
59 changes: 34 additions & 25 deletions source/lib/member-stack.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import { readdirSync } from 'fs';
import { StackProps, Stack, App, CfnParameter, CfnCondition, Fn, CfnResource } from 'aws-cdk-lib';
import { StackProps, Stack, App, CfnResource } from 'aws-cdk-lib';
import { Runtime } from 'aws-cdk-lib/aws-lambda';
import AdminAccountParam from './admin-account-param';
import { RedshiftAuditLogging } from './member/redshift-audit-logging';
Expand All @@ -11,6 +11,7 @@ import { MemberBucketEncryption } from './member/bucket-encryption';
import { MemberVersion } from './member/version';
import { SerializedNestedStackFactory } from './cdk-helper/nested-stack';
import { WaitProvider } from './wait-provider';
import { MemberPlaybook } from './member-playbook';

export interface SolutionProps extends StackProps {
solutionId: string;
Expand All @@ -27,7 +28,7 @@ export class MemberStack extends Stack {

const adminAccountParam = new AdminAccountParam(this, 'AdminAccountParameter');

new RedshiftAuditLogging(this, 'RedshiftAuditLogging', { solutionId: props.solutionId });
const redShiftLogging = new RedshiftAuditLogging(this, 'RedshiftAuditLogging', { solutionId: props.solutionId });

new MemberRemediationKey(this, 'MemberKey', { solutionId: props.solutionId });

Expand Down Expand Up @@ -60,40 +61,40 @@ export class MemberStack extends Stack {
this.nestedStacks.push(nestedStackNoRoles as Stack);

const playbookDirectory = `${__dirname}/../playbooks`;
const ignore = ['.DS_Store', 'common', '.pytest_cache', 'NEWPLAYBOOK', '.coverage'];
const illegalChars = /[\\._]/g;
const ignore = ['.DS_Store', 'common', '.pytest_cache', 'NEWPLAYBOOK', '.coverage', 'SC'];
const listOfPlaybooks: string[] = [];
const items = readdirSync(playbookDirectory);
items.forEach((file) => {
if (!ignore.includes(file)) {
const templateFile = `${file}MemberStack.template`;

const parmname = file.replace(illegalChars, '');
const memberStackOption = new CfnParameter(this, `LoadMemberStack${parmname}`, {
type: 'String',
description: `Load Playbook member stack for ${file}?`,
default: 'yes',
allowedValues: ['yes', 'no'],
});
memberStackOption.overrideLogicalId(`Load${parmname}MemberStack`);
listOfPlaybooks.push(memberStackOption.logicalId);

const nestedStack = nestedStackFactory.addNestedStack(`PlaybookMemberStack${file}`, {
templateRelativePath: `playbooks/${templateFile}`,
const playbook = new MemberPlaybook(this, {
name: file,
defaultState: 'no',
nestedStackFactory,
parameters: {
SecHubAdminAccount: adminAccountParam.value,
WaitProviderServiceToken: waitProvider.serviceToken,
},
condition: new CfnCondition(this, `load${file}Cond`, {
expression: Fn.conditionEquals(memberStackOption, 'yes'),
}),
});
const cfnResource = nestedStack.nestedStackResource as CfnResource;
cfnResource.overrideLogicalId(`PlaybookMemberStack${file}`);
this.nestedStacks.push(nestedStack as Stack);

listOfPlaybooks.push(playbook.parameterName);
this.nestedStacks.push(playbook.playbookStack);
}
});

const scPlaybook = new MemberPlaybook(this, {
name: 'SC',
defaultState: 'yes',
description:
'If the consolidated control findings feature is turned on in Security Hub, only enable the Security Control (SC) playbook. If the feature is not turned on, enable the playbooks for the security standards that are enabled in Security Hub. Enabling additional playbooks can result in reaching the quota for EventBridge Rules.',
nestedStackFactory,
parameters: {
SecHubAdminAccount: adminAccountParam.value,
WaitProviderServiceToken: waitProvider.serviceToken,
},
});

this.nestedStacks.push(scPlaybook.playbookStack);

/********************
** Metadata
********************/
Expand All @@ -105,9 +106,17 @@ export class MemberStack extends Stack {
Parameters: [memberLogGroup.paramId],
},
{
Label: { default: 'Playbooks' },
Label: { default: 'Consolidated control finding Playbook' },
Parameters: [scPlaybook.parameterName],
},
{
Label: { default: 'Security Standard Playbooks' },
Parameters: listOfPlaybooks,
},
{
Label: { default: 'Configuration' },
Parameters: [redShiftLogging.paramId, adminAccountParam.paramId],
},
],
ParameterLabels: {
[memberLogGroup.paramId]: {
Expand Down
3 changes: 3 additions & 0 deletions source/lib/member/redshift-audit-logging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ export interface RedshiftAuditLoggingProps {
}

export class RedshiftAuditLogging extends Construct {
public readonly paramId: string;

constructor(scope: Construct, id: string, props: RedshiftAuditLoggingProps) {
super(scope, id);

Expand All @@ -25,6 +27,7 @@ export class RedshiftAuditLogging extends Construct {
allowedValues: [ChoiceParam.Yes, ChoiceParam.No],
description: 'Create S3 Bucket For Redshift Cluster Audit Logging.',
});
this.paramId = templateParam.logicalId;

const condition = new CfnCondition(scope, 'EnableS3BucketForRedShift4', {
expression: Fn.conditionEquals(templateParam.valueAsString, ChoiceParam.Yes),
Expand Down
Loading