Skip to content

Commit 15e5779

Browse files
feat(core): regionalize cwl to s3 functionality (#346)
* Regionalize Amazon CloudWatch Logging to s3 functionality
1 parent 2f965c3 commit 15e5779

File tree

15 files changed

+334
-126
lines changed

15 files changed

+334
-126
lines changed

reference-artifacts/config.example.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"workloadaccounts-suffix" : 1,
1212
"workloadaccounts-prefix" : "config",
1313
"workloadaccounts-param-filename": "config.json",
14-
"ignored-ous": [],
14+
"ignored-ous": [],
1515
"supported-regions": [
1616
"ap-northeast-1",
1717
"ap-northeast-2",
@@ -64,6 +64,7 @@
6464
"ssm-to-s3": true,
6565
"ssm-to-cwl": true
6666
},
67+
"additional-cwl-regions": {},
6768
"reports": {
6869
"cost-and-usage-report": {
6970
"additional-schema-elements": ["RESOURCES"],

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ Config File Examples: {Not in sample config file}
1616
"exclusions": ["def/*"]
1717
}
1818
],
19+
************************************Additional regions for Amazon CloudWatch Central Logging to S3
20+
"additional-cwl-regions": {
21+
"us-east-1": {
22+
"kinesis-stream-shard-count": 1
23+
}
24+
},
1925
************************************cert REQUEST format
2026
"certificates": [
2127
{

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import * as customResourceRoles from '../deployments/iam';
99
* - Creating required roles for guardDuty custom resources
1010
* - Creating required roles for securityHub custom resources
1111
* - Creating required roles for IamCreateRole custom resource
12+
* - Creating required roles for createSSMDocument custom resource
13+
* - Creating required roles for createLogGroup custom resource
14+
* - Creating required roles for CWLCentralLoggingSubscriptionFilterRole custom resource
1215
*/
1316
export async function deploy({ acceleratorConfig, accountStacks, accounts }: PhaseInput) {
1417
// creates roles for macie custom resources
@@ -35,15 +38,23 @@ export async function deploy({ acceleratorConfig, accountStacks, accounts }: Pha
3538
accounts,
3639
});
3740

38-
// Creates roles for IamCreateRole custom resource
41+
// Creates roles for createSSMDocument custom resource
3942
await customResourceRoles.createSSMDocumentRoles({
4043
accountStacks,
4144
accounts,
4245
config: acceleratorConfig,
4346
});
4447

48+
// Creates roles for createLogGroup custom resource
4549
await customResourceRoles.createLogGroupRole({
4650
accountStacks,
4751
accounts,
4852
});
53+
54+
// Creates roles for createCwlSubscriptionFilter custom resource
55+
await customResourceRoles.createCwlAddSubscriptionFilterRoles({
56+
accountStacks,
57+
accounts,
58+
config: acceleratorConfig,
59+
});
4960
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,14 @@ export async function deploy({ acceleratorConfig, accountStacks, accounts, conte
167167
config: acceleratorConfig,
168168
});
169169

170+
// Creating roles required for CWL Central Logging
171+
await iamDeployment.createCwlCentralLoggingRoles({
172+
acceleratorPrefix: context.acceleratorPrefix,
173+
accountStacks,
174+
config: acceleratorConfig,
175+
logBucket,
176+
});
177+
170178
/**
171179
* Code to create LogGroups required for DNS Logging
172180
*/

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

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -433,13 +433,12 @@ export async function deploy({ acceleratorConfig, accountStacks, accounts, conte
433433
}
434434

435435
// Central Services step 1
436-
const shardCount = acceleratorConfig['global-options']['central-log-services']['kinesis-stream-shard-count'];
437-
const logsAccountStack = accountStacks.getOrCreateAccountStack(logAccountKey);
438436
await cwlCentralLoggingToS3.step1({
439-
accountStack: logsAccountStack,
437+
accountStacks,
440438
accounts,
441439
logBucket,
442-
shardCount,
440+
outputs,
441+
config: acceleratorConfig,
443442
});
444443

445444
await vpcDeployment.step1({
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 { LogDestinationOutput } from '@aws-accelerator/common-outputs/src/log-destination';
4+
5+
export const CfnLogDestinationOutput = createCfnStructuredOutput(LogDestinationOutput);
Lines changed: 64 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,77 @@
11
import * as cdk from '@aws-cdk/core';
2-
import * as iam from '@aws-cdk/aws-iam';
3-
import { createRoleName, createName } from '@aws-accelerator/cdk-accelerator/src/core/accelerator-name-generator';
2+
import { createName } from '@aws-accelerator/cdk-accelerator/src/core/accelerator-name-generator';
43
import * as kinesis from '@aws-cdk/aws-kinesis';
54
import * as s3 from '@aws-cdk/aws-s3';
65
import * as logs from '@aws-cdk/aws-logs';
76
import * as kinesisfirehose from '@aws-cdk/aws-kinesisfirehose';
8-
import { AccountStack } from '../../../common/account-stacks';
7+
import { AccountStacks } from '../../../common/account-stacks';
98
import { Account } from '../../../utils/accounts';
109
import { JsonOutputValue } from '../../../common/json-output';
1110
import { CLOUD_WATCH_CENTRAL_LOGGING_BUCKET_PREFIX } from '@aws-accelerator/common/src/util/constants';
11+
import * as c from '@aws-accelerator/common-config';
12+
import { StackOutput } from '@aws-accelerator/common-outputs/src/stack-output';
13+
import { IamRoleOutputFinder } from '@aws-accelerator/common-outputs/src/iam-role';
14+
import { CfnLogDestinationOutput } from './outputs';
1215

1316
export interface CentralLoggingToS3Step1Props {
14-
accountStack: AccountStack;
17+
accountStacks: AccountStacks;
1518
accounts: Account[];
1619
logBucket: s3.IBucket;
17-
shardCount?: number;
20+
outputs: StackOutput[];
21+
config: c.AcceleratorConfig;
1822
}
1923

2024
/**
2125
* Enable Central Logging to S3 in "log-archive" account Step 1
2226
*/
2327
export async function step1(props: CentralLoggingToS3Step1Props) {
24-
const { accountStack, accounts, logBucket, shardCount } = props;
28+
const { accountStacks, accounts, logBucket, config, outputs } = props;
2529
// Setup for CloudWatch logs storing in logs account
2630
const allAccountIds = accounts.map(account => account.id);
27-
await cwlSettingsInLogArchive({
28-
scope: accountStack,
29-
accountIds: allAccountIds,
30-
bucketArn: logBucket.bucketArn,
31-
encryptionKey: logBucket.encryptionKey?.keyArn!,
32-
shardCount,
31+
const centralLogServices = config['global-options']['central-log-services'];
32+
const cwlRegionsConfig = config['global-options']['additional-cwl-regions'];
33+
if (!cwlRegionsConfig[centralLogServices.region]) {
34+
cwlRegionsConfig[centralLogServices.region] = {
35+
'kinesis-stream-shard-count': centralLogServices['kinesis-stream-shard-count'],
36+
};
37+
}
38+
39+
const cwlLogStreamRoleOutput = IamRoleOutputFinder.tryFindOneByName({
40+
outputs,
41+
accountKey: centralLogServices.account,
42+
roleKey: 'CWLLogsStreamRole',
43+
});
44+
45+
const cwlKinesisStreamRoleOutput = IamRoleOutputFinder.tryFindOneByName({
46+
outputs,
47+
accountKey: centralLogServices.account,
48+
roleKey: 'CWLKinesisStreamRole',
3349
});
50+
51+
if (!cwlLogStreamRoleOutput || !cwlKinesisStreamRoleOutput) {
52+
console.error(`Skipping CWL Central logging setup due to unavailability of roles in output`);
53+
return;
54+
}
55+
56+
// Setting up in default "central-log-services" and "additional-cwl-regions" region
57+
for (const [region, regionConfig] of Object.entries(cwlRegionsConfig)) {
58+
// Setup CWL Central logging in default region
59+
const logAccountStack = accountStacks.tryGetOrCreateAccountStack(centralLogServices.account, region);
60+
if (!logAccountStack) {
61+
console.error(
62+
`Cannot find account stack ${centralLogServices.account}: ${region} while setting up cloudWatch central logging to S3`,
63+
);
64+
continue;
65+
}
66+
await cwlSettingsInLogArchive({
67+
scope: logAccountStack,
68+
accountIds: allAccountIds,
69+
bucketArn: logBucket.bucketArn,
70+
shardCount: regionConfig['kinesis-stream-shard-count'],
71+
logStreamRoleArn: cwlLogStreamRoleOutput.roleArn,
72+
kinesisStreamRoleArn: cwlKinesisStreamRoleOutput.roleArn,
73+
});
74+
}
3475
}
3576

3677
/**
@@ -41,10 +82,11 @@ async function cwlSettingsInLogArchive(props: {
4182
scope: cdk.Construct;
4283
accountIds: string[];
4384
bucketArn: string;
44-
encryptionKey: string;
85+
logStreamRoleArn: string;
86+
kinesisStreamRoleArn: string;
4587
shardCount?: number;
4688
}) {
47-
const { scope, accountIds, bucketArn, encryptionKey, shardCount } = props;
89+
const { scope, accountIds, bucketArn, logStreamRoleArn, kinesisStreamRoleArn, shardCount } = props;
4890

4991
// Create Kinesis Stream for Logs streaming
5092
const logsStream = new kinesis.Stream(scope, 'Logs-Stream', {
@@ -56,27 +98,6 @@ async function cwlSettingsInLogArchive(props: {
5698
shardCount,
5799
});
58100

59-
// Create IAM Role for reading logs from stream and push to destination
60-
const logsRole = new iam.Role(scope, 'CloudWatch-Logs-Stream-Role', {
61-
roleName: createRoleName('CWL-Logs-Stream-Role'),
62-
assumedBy: new iam.ServicePrincipal('logs.amazonaws.com'),
63-
});
64-
65-
// Create IAM Policy for reading logs from stream and push to destination
66-
const logsRolePolicy = new iam.Policy(scope, 'CWL-Logs-Stream-Policy', {
67-
roles: [logsRole],
68-
statements: [
69-
new iam.PolicyStatement({
70-
resources: [logsStream.streamArn],
71-
actions: ['kinesis:PutRecord'],
72-
}),
73-
new iam.PolicyStatement({
74-
resources: [logsRole.roleArn],
75-
actions: ['iam:PassRole'],
76-
}),
77-
],
78-
});
79-
80101
const destinationName = createName({
81102
name: 'LogDestination',
82103
suffixLength: 0,
@@ -100,57 +121,18 @@ async function cwlSettingsInLogArchive(props: {
100121
const logDestination = new logs.CfnDestination(scope, 'Log-Destination', {
101122
destinationName,
102123
targetArn: logsStream.streamArn,
103-
roleArn: logsRole.roleArn,
124+
roleArn: logStreamRoleArn,
104125
destinationPolicy: destinationPolicyStr,
105126
});
106-
logDestination.node.addDependency(logsRolePolicy);
107-
108-
// Creating IAM role for Kinesis Delivery Stream Role
109-
const kinesisStreamRole = new iam.Role(scope, 'CWL-Kinesis-Stream-Role', {
110-
roleName: createRoleName('Kinesis-Stream-Role'),
111-
assumedBy: new iam.ServicePrincipal('firehose.amazonaws.com'),
112-
});
113127

114-
const kinesisStreamPolicy = new iam.Policy(scope, 'CWL-Kinesis-Stream-Policy', {
115-
roles: [kinesisStreamRole],
116-
statements: [
117-
new iam.PolicyStatement({
118-
resources: [encryptionKey],
119-
actions: ['kms:DescribeKey', 'kms:GenerateDataKey*', 'kms:Decrypt', 'kms:Encrypt', 'kms:ReEncrypt*'],
120-
}),
121-
new iam.PolicyStatement({
122-
resources: [bucketArn, `${bucketArn}/*`],
123-
actions: [
124-
's3:PutObject',
125-
's3:PutObjectAcl',
126-
's3:GetEncryptionConfiguration',
127-
's3:AbortMultipartUpload',
128-
's3:GetBucketLocation',
129-
's3:GetObject',
130-
's3:ListBucket',
131-
's3:ListBucketMultipartUploads',
132-
's3:PutObject',
133-
],
134-
}),
135-
new iam.PolicyStatement({
136-
resources: ['*'],
137-
actions: ['kinesis:DescribeStream', 'kinesis:GetShardIterator', 'kinesis:GetRecords', 'kinesis:ListShards'],
138-
}),
139-
new iam.PolicyStatement({
140-
resources: ['arn:aws:logs:*:*:*'],
141-
actions: ['logs:PutLogEvents'],
142-
}),
143-
],
144-
});
145-
146-
const kinesisDeliveryStream = new kinesisfirehose.CfnDeliveryStream(scope, 'Kinesis-Firehouse-Stream', {
128+
new kinesisfirehose.CfnDeliveryStream(scope, 'Kinesis-Firehouse-Stream', {
147129
deliveryStreamName: createName({
148130
name: 'Kinesis-Delivery-Stream',
149131
}),
150132
deliveryStreamType: 'KinesisStreamAsSource',
151133
kinesisStreamSourceConfiguration: {
152134
kinesisStreamArn: logsStream.streamArn,
153-
roleArn: kinesisStreamRole.roleArn,
135+
roleArn: kinesisStreamRoleArn,
154136
},
155137
extendedS3DestinationConfiguration: {
156138
bucketArn,
@@ -159,18 +141,15 @@ async function cwlSettingsInLogArchive(props: {
159141
sizeInMBs: 50,
160142
},
161143
compressionFormat: 'UNCOMPRESSED',
162-
roleArn: kinesisStreamRole.roleArn,
144+
roleArn: kinesisStreamRoleArn,
163145
prefix: CLOUD_WATCH_CENTRAL_LOGGING_BUCKET_PREFIX,
164146
},
165147
});
166-
kinesisDeliveryStream.node.addDependency(kinesisStreamPolicy);
167-
kinesisDeliveryStream.node.addDependency(logsRolePolicy);
168148

169149
// Store LogDestination ARN in output so that subsequent phases can access the output
170-
new JsonOutputValue(scope, `CloudWatchCentralLoggingOutput`, {
171-
type: 'CloudWatchCentralLogging',
172-
value: {
173-
logDestination: logDestination.attrArn,
174-
},
150+
new CfnLogDestinationOutput(scope, `CloudWatchCentralLoggingOutput`, {
151+
destinationArn: logDestination.attrArn,
152+
destinationName: logDestination.destinationName,
153+
destinationKey: 'CwlCentralLogDestination',
175154
});
176155
}

0 commit comments

Comments
 (0)