Skip to content

Commit 3c11049

Browse files
Fix: destination policy length scale issue (#899)
* fix: change log destination policy in the logs account to be based on ASEA roles and source is from within the org * fix: reference org id from log stack account app to avoid cross app reference * feat: add check for home region log account stack and throw error if not found * fix: create the iam policy document leveraging the cdk, multiple conditions made easier * fix: change entry for principal org id condition to array * fix: remove unsupported condition key from aws logs destination policy * fix: extend env vars of subscription filter lambda to be provided the roleArn for the filter * feat: create separate role for the filter subscription on cloud watch logs destionation * feat: add policy iam constrcut to enforce policy change to org id being condition source of log destination policy * Update index.ts fix es-lint error * fixed duplicate iam import Co-authored-by: hickeydh-aws <88673813+hickeydh-aws@users.noreply.github.com> Co-authored-by: hickeydh <hickeydh@amazon.com>
1 parent 18e901b commit 3c11049

File tree

9 files changed

+113
-31
lines changed

9 files changed

+113
-31
lines changed

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,13 @@ export async function deploy({ acceleratorConfig, accountStacks, accounts }: Pha
7777
config: acceleratorConfig,
7878
});
7979

80+
// Creates roles for cloud watch logs group subscriptions
81+
await globalRoles.createCwlSubscriptionFilterRoles({
82+
accountStacks,
83+
accounts,
84+
config: acceleratorConfig,
85+
});
86+
8087
// Creates role for SnsSubscriberLambda function
8188
await globalRoles.createSnsSubscriberLambdaRole({
8289
accountStacks,

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -575,7 +575,6 @@ export async function deploy({ acceleratorConfig, accountStacks, accounts, conte
575575
// Central Services step 1
576576
await cwlCentralLoggingToS3.step1({
577577
accountStacks,
578-
accounts,
579578
logBucket,
580579
outputs,
581580
config: acceleratorConfig,

src/deployments/cdk/src/deployments/central-services/central-logging-s3/step-1.ts

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,17 @@ import * as s3 from '@aws-cdk/aws-s3';
2020
import * as logs from '@aws-cdk/aws-logs';
2121
import * as kinesisfirehose from '@aws-cdk/aws-kinesisfirehose';
2222
import { AccountStacks } from '../../../common/account-stacks';
23-
import { Account } from '../../../utils/accounts';
24-
import { JsonOutputValue } from '../../../common/json-output';
2523
import { CLOUD_WATCH_CENTRAL_LOGGING_BUCKET_PREFIX } from '@aws-accelerator/common/src/util/constants';
2624
import * as c from '@aws-accelerator/common-config';
2725
import { StackOutput } from '@aws-accelerator/common-outputs/src/stack-output';
2826
import { IamRoleOutputFinder } from '@aws-accelerator/common-outputs/src/iam-role';
2927
import { CfnLogDestinationOutput } from './outputs';
28+
import { Organizations } from '@aws-accelerator/custom-resource-organization';
3029

3130
import path from 'path';
3231

3332
export interface CentralLoggingToS3Step1Props {
3433
accountStacks: AccountStacks;
35-
accounts: Account[];
3634
logBucket: s3.IBucket;
3735
outputs: StackOutput[];
3836
config: c.AcceleratorConfig;
@@ -42,9 +40,8 @@ export interface CentralLoggingToS3Step1Props {
4240
* Enable Central Logging to S3 in "log-archive" account Step 1
4341
*/
4442
export async function step1(props: CentralLoggingToS3Step1Props) {
45-
const { accountStacks, accounts, logBucket, config, outputs } = props;
46-
// Setup for CloudWatch logs storing in logs account
47-
const allAccountIds = accounts.map(account => account.id);
43+
const { accountStacks, logBucket, config, outputs } = props;
44+
// Setup for CloudWatch logs storing in logs account for org
4845
const centralLogServices = config['global-options']['central-log-services'];
4946
const cwlRegionsConfig = config['global-options']['additional-cwl-regions'];
5047
if (!cwlRegionsConfig[centralLogServices.region]) {
@@ -69,6 +66,16 @@ export async function step1(props: CentralLoggingToS3Step1Props) {
6966
console.error(`Skipping CWL Central logging setup due to unavailability of roles in output`);
7067
return;
7168
}
69+
const logAccountStack = accountStacks.tryGetOrCreateAccountStack(
70+
centralLogServices.account,
71+
centralLogServices.region,
72+
);
73+
if (!logAccountStack) {
74+
throw new Error(
75+
`Cannot find mandatory ${centralLogServices.account} account in home region ${centralLogServices.region}.`,
76+
);
77+
}
78+
const organizations = new Organizations(logAccountStack, 'Organizations');
7279

7380
// Setting up in default "central-log-services" and "additional-cwl-regions" region
7481
for (const [region, regionConfig] of Object.entries(cwlRegionsConfig)) {
@@ -82,11 +89,11 @@ export async function step1(props: CentralLoggingToS3Step1Props) {
8289
}
8390
await cwlSettingsInLogArchive({
8491
scope: logAccountStack,
85-
accountIds: allAccountIds,
8692
bucketArn: logBucket.bucketArn,
8793
shardCount: regionConfig['kinesis-stream-shard-count'],
8894
logStreamRoleArn: cwlLogStreamRoleOutput.roleArn,
8995
kinesisStreamRoleArn: cwlKinesisStreamRoleOutput.roleArn,
96+
orgId: organizations.organizationId,
9097
dynamicS3LogPartitioning: centralLogServices['dynamic-s3-log-partitioning'],
9198
region,
9299
});
@@ -99,17 +106,17 @@ export async function step1(props: CentralLoggingToS3Step1Props) {
99106
*/
100107
async function cwlSettingsInLogArchive(props: {
101108
scope: cdk.Construct;
102-
accountIds: string[];
103109
bucketArn: string;
104110
logStreamRoleArn: string;
105111
kinesisStreamRoleArn: string;
112+
orgId: string;
106113
shardCount?: number;
107114
dynamicS3LogPartitioning?: c.S3LogPartition[];
108115
region: string;
109116
}) {
110117
const {
111118
scope,
112-
accountIds,
119+
orgId,
113120
bucketArn,
114121
logStreamRoleArn,
115122
kinesisStreamRoleArn,
@@ -133,20 +140,26 @@ async function cwlSettingsInLogArchive(props: {
133140
suffixLength: 0,
134141
});
135142

136-
const destinatinPolicy = {
137-
Version: '2012-10-17',
138-
Statement: [
139-
{
140-
Effect: 'Allow',
141-
Principal: {
142-
AWS: accountIds,
143+
const destinationPolicy = new iam.PolicyDocument({
144+
statements: [
145+
new iam.PolicyStatement({
146+
effect: iam.Effect.ALLOW,
147+
principals: [new iam.AnyPrincipal()],
148+
actions: ['logs:PutSubscriptionFilter'],
149+
resources: [`arn:aws:logs:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:destination:${destinationName}`],
150+
conditions: {
151+
StringEquals: {
152+
'aws:PrincipalOrgID': [orgId],
153+
},
143154
},
144-
Action: 'logs:PutSubscriptionFilter',
145-
Resource: `arn:aws:logs:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:destination:${destinationName}`,
146-
},
155+
}),
147156
],
148-
};
149-
const destinationPolicyStr = JSON.stringify(destinatinPolicy);
157+
});
158+
const enforcedPolicy = new iam.Policy(scope, 'Log-Destination-Policy', {
159+
force: true,
160+
document: destinationPolicy,
161+
});
162+
const destinationPolicyStr = JSON.stringify(enforcedPolicy.document.toJSON());
150163
// Create AWS Logs Destination
151164
const logDestination = new logs.CfnDestination(scope, 'Log-Destination', {
152165
destinationName,

src/deployments/cdk/src/deployments/central-services/central-logging-s3/step-2.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,10 @@ export async function step2(props: CentralLoggingToS3Step2Props) {
4747
const subscriptionRoleOutput = IamRoleOutputFinder.tryFindOneByName({
4848
accountKey,
4949
outputs,
50-
roleKey: 'CWLAddSubscriptionFilter',
50+
roleKey: 'CWLSubscriptionFilter',
5151
});
5252
if (!subscriptionRoleOutput) {
53-
console.error(`Can't find "CWLAddSubscriptionFilter" Role in account ${accountKey} outputs`);
53+
console.error(`Can't find "CWLSubscriptionFilter" Role in account ${accountKey} outputs`);
5454
continue;
5555
}
5656
for (const region of cwlRegions) {
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
5+
* with the License. A copy of the License is located at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES
10+
* OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions
11+
* and limitations under the License.
12+
*/
13+
14+
import * as c from '@aws-accelerator/common-config/src';
15+
import * as iam from '@aws-cdk/aws-iam';
16+
import { AccountStacks } from '../../common/account-stacks';
17+
import { createRoleName } from '@aws-accelerator/cdk-accelerator/src/core/accelerator-name-generator';
18+
import { CfnIamRoleOutput } from './outputs';
19+
import { Account } from '../../utils/accounts';
20+
21+
export interface CwlSubscriptionFilterRoleProps {
22+
accountStacks: AccountStacks;
23+
config: c.AcceleratorConfig;
24+
accounts: Account[];
25+
}
26+
27+
export async function createCwlSubscriptionFilterRoles(props: CwlSubscriptionFilterRoleProps): Promise<void> {
28+
const { accountStacks, config, accounts } = props;
29+
const centralLoggingServices = config['global-options']['central-log-services'];
30+
for (const account of accounts) {
31+
const accountStack = accountStacks.tryGetOrCreateAccountStack(account.key, centralLoggingServices.region);
32+
if (!accountStack) {
33+
console.error(
34+
`Not able to create stack for "${centralLoggingServices.account}" while creating role for CWL Central logging`,
35+
);
36+
continue;
37+
}
38+
// Create IAM Role for reading logs from stream and push to destination
39+
const role = new iam.Role(accountStack, 'CWLSubscriptionFilterRole', {
40+
roleName: createRoleName('CWL-Subscription-Filter'),
41+
assumedBy: new iam.ServicePrincipal('logs.amazonaws.com'),
42+
});
43+
44+
role.addToPrincipalPolicy(
45+
new iam.PolicyStatement({
46+
actions: ['logs:PutLogEvents'],
47+
resources: ['*'],
48+
}),
49+
);
50+
51+
new CfnIamRoleOutput(accountStack, `CWLSubscriptionFilterRoleOutput`, {
52+
roleName: role.roleName,
53+
roleArn: role.roleArn,
54+
roleKey: 'CWLSubscriptionFilter',
55+
});
56+
}
57+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export * from './ssm-document-roles';
2222
export * from './log-group-role';
2323
export * from './cwl-central-logging-roles';
2424
export * from './cwl-add-subscription-filter-role';
25+
export * from './cwl-subscription-filter-role';
2526
export * from './tgw-create-peering-roles';
2627
export * from './tgw-accept-peering-roles';
2728
export * from './logs-metric-filter-role';

src/lib/custom-resources/logs-add-subscription-filter/runtime-event-trigger/src/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export const handler = async (input: any): Promise<string> => {
2323
console.log(JSON.stringify(input, null, 2));
2424

2525
const logGroupName = input.detail.requestParameters.logGroupName as string;
26+
const roleArn = process.env.ROLE_ARN || '';
2627
const logDestinationArn = process.env.LOG_DESTINATION;
2728
if (!logDestinationArn) {
2829
console.warn(`Log Destination is not parent in env for this account`);
@@ -40,7 +41,7 @@ export const handler = async (input: any): Promise<string> => {
4041
if (isExcluded(exclusions, logGroupName)) {
4142
return `No Need of Subscription Filter for "${logGroupName}"`;
4243
}
43-
await addSubscriptionFilter(logGroupName, logDestinationArn);
44+
await addSubscriptionFilter(logGroupName, logDestinationArn, roleArn);
4445
const logRetention = process.env.LOG_RETENTION;
4546
if (logRetention) {
4647
// Update Log Retention Policy
@@ -49,7 +50,7 @@ export const handler = async (input: any): Promise<string> => {
4950
return 'SUCCESS';
5051
};
5152

52-
async function addSubscriptionFilter(logGroupName: string, destinationArn: string) {
53+
async function addSubscriptionFilter(logGroupName: string, destinationArn: string, roleArn: string) {
5354
// Adding subscription filter
5455
await throttlingBackOff(() =>
5556
logs
@@ -58,6 +59,7 @@ async function addSubscriptionFilter(logGroupName: string, destinationArn: strin
5859
logGroupName,
5960
filterName: `${CloudWatchRulePrefix}${logGroupName}`,
6061
filterPattern: '',
62+
roleArn,
6163
})
6264
.promise(),
6365
);

src/lib/custom-resources/logs-add-subscription-filter/runtime/src/index.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export interface HandlerProperties {
2727
logDestinationArn: string;
2828
globalExclusions?: string[];
2929
logRetention: number;
30+
roleArn: string;
3031
}
3132

3233
export const handler = errorHandler(onEvent);
@@ -90,7 +91,7 @@ const isExcluded = (exclusions: string[], logGroupName: string): boolean => {
9091

9192
async function centralLoggingSubscription(event: CloudFormationCustomResourceEvent): Promise<void> {
9293
const properties = (event.ResourceProperties as unknown) as HandlerProperties;
93-
const { logDestinationArn, logRetention } = properties;
94+
const { logDestinationArn, logRetention, roleArn } = properties;
9495
const globalExclusions = properties.globalExclusions || [];
9596
const logGroups = await getLogGroups();
9697
const filterLogGroups = logGroups.filter(lg => !isExcluded(globalExclusions, lg.logGroupName!));
@@ -108,14 +109,14 @@ async function centralLoggingSubscription(event: CloudFormationCustomResourceEve
108109
await putLogRetentionPolicy(logGroup.logGroupName!, logRetention);
109110
// Add Subscription filter to logGroup
110111
console.log(`Adding subscription filter for ${logGroup.logGroupName}`);
111-
await addSubscriptionFilter(logGroup.logGroupName!, logDestinationArn);
112+
await addSubscriptionFilter(logGroup.logGroupName!, logDestinationArn, roleArn);
112113
}),
113114
);
114115
}
115116

116117
async function centralLoggingSubscriptionUpdate(event: CloudFormationCustomResourceEvent): Promise<void> {
117118
const properties = (event.ResourceProperties as unknown) as HandlerProperties;
118-
const { logDestinationArn, logRetention } = properties;
119+
const { logDestinationArn, logRetention, roleArn } = properties;
119120
const globalExclusions = properties.globalExclusions || [];
120121
const logGroups = await getLogGroups();
121122
const filterLogGroups = logGroups.filter(lg => !isExcluded(globalExclusions, lg.logGroupName!));
@@ -142,7 +143,7 @@ async function centralLoggingSubscriptionUpdate(event: CloudFormationCustomResou
142143
await putLogRetentionPolicy(logGroup.logGroupName!, logRetention);
143144
// Add Subscription filter to logGroup
144145
console.log(`Adding subscription filter for ${logGroup.logGroupName}`);
145-
await addSubscriptionFilter(logGroup.logGroupName!, logDestinationArn);
146+
await addSubscriptionFilter(logGroup.logGroupName!, logDestinationArn, roleArn);
146147
}),
147148
);
148149
}
@@ -167,7 +168,7 @@ async function removeSubscriptionFilter(logGroupName: string, filterName: string
167168
}
168169
}
169170

170-
async function addSubscriptionFilter(logGroupName: string, destinationArn: string) {
171+
async function addSubscriptionFilter(logGroupName: string, destinationArn: string, roleArn: string) {
171172
try {
172173
// Adding subscription filter
173174
await throttlingBackOff(() =>
@@ -177,6 +178,7 @@ async function addSubscriptionFilter(logGroupName: string, destinationArn: strin
177178
logGroupName,
178179
filterName: `${CloudWatchRulePrefix}${logGroupName}`,
179180
filterPattern: '',
181+
roleArn,
180182
})
181183
.promise(),
182184
);

src/lib/custom-resources/logs-add-subscription-filter/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export class CentralLoggingSubscriptionFilter extends cdk.Construct {
5656
EXCLUSIONS: JSON.stringify(props.globalExclusions),
5757
LOG_DESTINATION: props.logDestinationArn,
5858
LOG_RETENTION: props.logRetention.toString(),
59+
ROLE_ARN: props.roleArn,
5960
};
6061
const addSubscriptionLambda = this.ensureLambdaFunction(
6162
this.cloudWatchEnventLambdaPath,

0 commit comments

Comments
 (0)