Skip to content

Commit 49fff99

Browse files
feat(core): Deploy sns to enable cloud watch events and notifications (#360)
1 parent 46a9914 commit 49fff99

File tree

16 files changed

+278
-9
lines changed

16 files changed

+278
-9
lines changed

reference-artifacts/master-config-sample-snippets/sample_snippets.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,16 @@ Config File Examples: {Not in sample config file}
2222
"kinesis-stream-shard-count": 1
2323
}
2424
},
25+
****************SNS Topics ignore regions and adding subscription emails for topic subscriptions*********
26+
****************By default we create High,Medium,Low,Ignore SNS topics with out subscribers**************
27+
"central-log-services": {
28+
"sns-excl-regions": ["sa-east-1"],
29+
"sns-subscription-emails": {
30+
"High": ["notify+high@example.com"],
31+
"Low": ["notify+low@example.com"],
32+
"Medium": ["notify+medium@example.com"]
33+
}
34+
}
2535
************************************cert REQUEST format
2636
"certificates": [
2737
{

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

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { PhaseInput } from './shared';
2-
import * as customResourceRoles from '../deployments/iam';
2+
import * as globalRoles from '../deployments/iam';
33

44
/**
55
* This is the main entry point to deploy phase -1.
@@ -15,44 +15,51 @@ import * as customResourceRoles from '../deployments/iam';
1515
*/
1616
export async function deploy({ acceleratorConfig, accountStacks, accounts }: PhaseInput) {
1717
// creates roles for macie custom resources
18-
await customResourceRoles.createMacieRoles({
18+
await globalRoles.createMacieRoles({
1919
accountStacks,
2020
config: acceleratorConfig,
2121
});
2222

2323
// creates roles for guardDuty custom resources
24-
await customResourceRoles.createGuardDutyRoles({
24+
await globalRoles.createGuardDutyRoles({
2525
accountStacks,
2626
config: acceleratorConfig,
2727
});
2828

2929
// creates roles for securityHub custom resources
30-
await customResourceRoles.createSecurityHubRoles({
30+
await globalRoles.createSecurityHubRoles({
3131
accountStacks,
3232
accounts,
3333
});
3434

3535
// Creates roles for IamCreateRole custom resource
36-
await customResourceRoles.createIamRole({
36+
await globalRoles.createIamRole({
3737
accountStacks,
3838
accounts,
3939
});
4040

4141
// Creates roles for createSSMDocument custom resource
42-
await customResourceRoles.createSSMDocumentRoles({
42+
await globalRoles.createSSMDocumentRoles({
4343
accountStacks,
4444
accounts,
4545
config: acceleratorConfig,
4646
});
4747

4848
// Creates roles for createLogGroup custom resource
49-
await customResourceRoles.createLogGroupRole({
49+
await globalRoles.createLogGroupRole({
5050
accountStacks,
5151
accounts,
5252
});
5353

5454
// Creates roles for createCwlSubscriptionFilter custom resource
55-
await customResourceRoles.createCwlAddSubscriptionFilterRoles({
55+
await globalRoles.createCwlAddSubscriptionFilterRoles({
56+
accountStacks,
57+
accounts,
58+
config: acceleratorConfig,
59+
});
60+
61+
// Creates role for SnsSubscriberLambda function
62+
await globalRoles.createSnsSubscriberLambdaRole({
5663
accountStacks,
5764
accounts,
5865
config: acceleratorConfig,

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import * as tgwDeployment from '../deployments/transit-gateway';
2424
import * as macie from '../deployments/macie';
2525
import * as centralServices from '../deployments/central-services';
2626
import * as guardDutyDeployment from '../deployments/guardduty';
27+
import * as snsDeployment from '../deployments/sns';
2728

2829
/**
2930
* This is the main entry point to deploy phase 2.
@@ -288,6 +289,15 @@ export async function deploy({ acceleratorConfig, accountStacks, accounts, conte
288289
accounts,
289290
outputs,
290291
});
292+
293+
/**
294+
* Creating required SNS Topics in Log accont if baseline is Organizations
295+
*/
296+
await snsDeployment.step1({
297+
accountStacks,
298+
config: acceleratorConfig,
299+
outputs,
300+
});
291301
}
292302

293303
const logBucket = accountBuckets[acceleratorConfig['global-options']['central-log-services'].account];

src/deployments/cdk/src/deployments/iam/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@ export * from './ssm-document-roles';
99
export * from './log-group-role';
1010
export * from './cwl-central-logging-roles';
1111
export * from './cwl-add-subscription-filter-role';
12+
export * from './sns-subscriber-lambda-role';
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import * as c from '@aws-accelerator/common-config/src';
2+
import * as iam from '@aws-cdk/aws-iam';
3+
import { AccountStacks } from '../../common/account-stacks';
4+
import { createRoleName } from '@aws-accelerator/cdk-accelerator/src/core/accelerator-name-generator';
5+
import { CfnIamRoleOutput } from './outputs';
6+
import { Account } from '../../utils/accounts';
7+
8+
export interface CreateSnsSubscriberLambdaRoleProps {
9+
accountStacks: AccountStacks;
10+
config: c.AcceleratorConfig;
11+
accounts: Account[];
12+
}
13+
14+
export async function createSnsSubscriberLambdaRole(props: CreateSnsSubscriberLambdaRoleProps): Promise<void> {
15+
const { accountStacks, config } = props;
16+
const centralLoggingServices = config['global-options']['central-log-services'];
17+
const accountStack = accountStacks.tryGetOrCreateAccountStack(
18+
centralLoggingServices.account,
19+
centralLoggingServices.region,
20+
);
21+
if (!accountStack) {
22+
console.error(
23+
`Not able to create stack for "${centralLoggingServices.account}" while creating role for CWL Central logging`,
24+
);
25+
return;
26+
}
27+
28+
// Create IAM Role for reading logs from stream and push to destination
29+
const role = new iam.Role(accountStack, 'SnsSubscriberLambdaRole', {
30+
roleName: createRoleName('SnsSubscriberLambda'),
31+
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
32+
});
33+
34+
role.addToPrincipalPolicy(
35+
new iam.PolicyStatement({
36+
actions: ['sns:Publish', 'logs:CreateLogGroup', 'logs:CreateLogStream', 'logs:PutLogEvents'],
37+
resources: ['*'],
38+
}),
39+
);
40+
41+
new CfnIamRoleOutput(accountStack, `SnsSubscriberLambdaOutput`, {
42+
roleName: role.roleName,
43+
roleArn: role.roleArn,
44+
roleKey: 'SnsSubscriberLambda',
45+
});
46+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './outputs';
2+
export * from './step-1';
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import * as t from 'io-ts';
2+
import { createCfnStructuredOutput } from '../../common/structured-output';
3+
import { SnsTopicOutput } from '@aws-accelerator/common-outputs/src/sns-topic';
4+
5+
export const CfnSnsTopicOutput = createCfnStructuredOutput(SnsTopicOutput);
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import * as c from '@aws-accelerator/common-config';
2+
import { AccountStacks } from '../../common/account-stacks';
3+
import * as sns from '@aws-cdk/aws-sns';
4+
import { createSnsTopicName } from '@aws-accelerator/cdk-accelerator/src/core/accelerator-name-generator';
5+
import { SNS_NOTIFICATION_TYPES } from '@aws-accelerator/common/src/util/constants';
6+
import * as path from 'path';
7+
import * as lambda from '@aws-cdk/aws-lambda';
8+
import * as cdk from '@aws-cdk/core';
9+
import { IamRoleOutputFinder } from '@aws-accelerator/common-outputs/src/iam-role';
10+
import { StackOutput } from '@aws-accelerator/common-outputs/src/stack-output';
11+
import * as iam from '@aws-cdk/aws-iam';
12+
13+
export interface SnsStep1Props {
14+
accountStacks: AccountStacks;
15+
config: c.AcceleratorConfig;
16+
outputs: StackOutput[];
17+
}
18+
19+
/**
20+
*
21+
* Create SNS Topics High, Medium, Low, Ignore
22+
* in Central-Log-Services Account
23+
*/
24+
export async function step1(props: SnsStep1Props) {
25+
const { accountStacks, config, outputs } = props;
26+
const globalOptions = config['global-options'];
27+
const centralLogServices = globalOptions['central-log-services'];
28+
const supportedRegions = globalOptions['supported-regions'];
29+
const excludeRegions = centralLogServices['sns-excl-regions'];
30+
const regions = supportedRegions.filter(r => !excludeRegions?.includes(r));
31+
if (!regions.includes(centralLogServices.region)) {
32+
regions.push(centralLogServices.region);
33+
}
34+
const subscribeEmails = centralLogServices['sns-subscription-emails'];
35+
const snsSubscriberLambdaRoleOutput = IamRoleOutputFinder.tryFindOneByName({
36+
outputs,
37+
accountKey: centralLogServices.account,
38+
roleKey: 'SnsSubscriberLambda',
39+
});
40+
if (!snsSubscriberLambdaRoleOutput) {
41+
console.error(`Role required for SNS Subscription Lambda is not created in ${centralLogServices.account}`);
42+
return;
43+
}
44+
for (const region of regions) {
45+
const accountStack = accountStacks.tryGetOrCreateAccountStack(centralLogServices.account, region);
46+
if (!accountStack) {
47+
console.error(`Cannot find account stack ${centralLogServices.account}: ${region}, while deploying SNS`);
48+
continue;
49+
}
50+
const lambdaPath = require.resolve('@aws-accelerator/deployments-runtime');
51+
const lambdaDir = path.dirname(lambdaPath);
52+
const lambdaCode = lambda.Code.fromAsset(lambdaDir);
53+
const role = iam.Role.fromRoleArn(accountStack, `SnsSubscriberLambdaRole`, snsSubscriberLambdaRoleOutput.roleArn);
54+
let snsSubscriberFunc: lambda.Function | undefined;
55+
if (region !== centralLogServices.region) {
56+
snsSubscriberFunc = new lambda.Function(accountStack, `SnsSubscriberLambda`, {
57+
runtime: lambda.Runtime.NODEJS_12_X,
58+
handler: 'index.createSnsPublishToCentralRegion',
59+
code: lambdaCode,
60+
role,
61+
environment: {
62+
CENTRAL_LOG_SERVICES_REGION: centralLogServices.region,
63+
},
64+
timeout: cdk.Duration.minutes(15),
65+
});
66+
67+
snsSubscriberFunc.addPermission(`InvokePermission-SnsSubscriberLambda`, {
68+
action: 'lambda:InvokeFunction',
69+
principal: new iam.ServicePrincipal('sns.amazonaws.com'),
70+
});
71+
}
72+
73+
const ignoreActionFunc = new lambda.Function(accountStack, `IgnoreActionLambda`, {
74+
runtime: lambda.Runtime.NODEJS_12_X,
75+
handler: 'index.createIgnoreAction',
76+
code: lambdaCode,
77+
role,
78+
timeout: cdk.Duration.minutes(15),
79+
});
80+
81+
ignoreActionFunc.addPermission(`InvokePermission-IgnoreActionLambda`, {
82+
action: 'lambda:InvokeFunction',
83+
principal: new iam.ServicePrincipal('sns.amazonaws.com'),
84+
});
85+
86+
for (const notificationType of SNS_NOTIFICATION_TYPES) {
87+
const topicName = createSnsTopicName(notificationType);
88+
const topic = new sns.Topic(accountStack, `SnsNotificationTopic${notificationType}`, {
89+
displayName: topicName,
90+
topicName,
91+
});
92+
93+
// Allowing Publish from CloudWatch Service form any account
94+
topic.grantPublish({
95+
grantPrincipal: new iam.ServicePrincipal('cloudwatch.amazonaws.com'),
96+
});
97+
98+
// Allowing Publish from Lambda Service form any account
99+
topic.grantPublish({
100+
grantPrincipal: new iam.ServicePrincipal('lambda.amazonaws.com'),
101+
});
102+
103+
if (region === centralLogServices.region && subscribeEmails && subscribeEmails[notificationType]) {
104+
subscribeEmails[notificationType].forEach((email, index) => {
105+
new sns.CfnSubscription(accountStack, `SNSTopicSubscriptionFor${notificationType}-${index + 1}`, {
106+
topicArn: topic.topicArn,
107+
protocol: sns.SubscriptionProtocol.EMAIL,
108+
endpoint: email,
109+
});
110+
});
111+
} else if (region === centralLogServices.region && notificationType.toLowerCase() === 'ignore') {
112+
new sns.CfnSubscription(accountStack, `SNSTopicSubscriptionFor${notificationType}`, {
113+
topicArn: topic.topicArn,
114+
protocol: sns.SubscriptionProtocol.LAMBDA,
115+
endpoint: ignoreActionFunc.functionArn,
116+
});
117+
} else if (region !== centralLogServices.region && snsSubscriberFunc) {
118+
new sns.CfnSubscription(accountStack, `SNSTopicSubscriptionFor${notificationType}`, {
119+
topicArn: topic.topicArn,
120+
protocol: sns.SubscriptionProtocol.LAMBDA,
121+
endpoint: snsSubscriberFunc.functionArn,
122+
});
123+
}
124+
}
125+
}
126+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// tslint:disable-next-line: no-any
2+
export const handler = async (input: any): Promise<void> => {
3+
console.log('Ignoring Action input ....');
4+
console.log(JSON.stringify(input, null, 2));
5+
};
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
1+
export { handler as createSnsPublishToCentralRegion } from './sns-publish-central-region';
2+
export { handler as createIgnoreAction } from './ignore-action';
13
import * as ouValidationEvents from './ou-validation-events';
24
export { ouValidationEvents };

0 commit comments

Comments
 (0)