Skip to content

Commit 696adb8

Browse files
rycerratBrian969
andauthored
(fix): Switch Log archive bucket policy to Org policy (#1051)
* Adding code for destination policy update * Changing logic to use subscriptionFilterRoleArn * Updating custom resource runtime * Testing some code for update custom resource * Trying to force custom resource update * Updating uuid to force update * Fixing custom resource again * Removing uuid from other function * Fixing uuid() * Fixing props object * ADding some logging * Moving logging * More logging * Fixing subscriptionfilterrolearn * Removing additional log statements * Reducing Store Outputs SM max concurrency from 50 to 20. Also adding limit increase requests for both CloudWatch Logs and Lambda Concurrency * Reducing Store Outputs SM max concurrency from 50 to 20. Also adding limit increase requests for both CloudWatch Logs and Lambda Concurrency * Updating maxconcurrency in one more sm to 20 * Adding extra logging stmts * Removing additional logging statements * Moving SM concurrent executions down to 10 from 20 to Mirror Ryan's limits * Create force-github-actions.txt * Fixing prettier issues * Adding if statement around subscriptionFilterRoleArn. If it doesn't exist, don't add Subscription Filter --------- Co-authored-by: Brian969 <56414362+Brian969@users.noreply.github.com>
1 parent bed0a62 commit 696adb8

File tree

8 files changed

+77
-23
lines changed

8 files changed

+77
-23
lines changed

src/core/runtime/src/load-limits-step.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,16 @@ const LIMITS: { [limitKey: string]: LimitCode } = {
6464
quotaCode: 'L-29A0C5DF',
6565
enabled: false,
6666
},
67+
[Limit.CloudWatchCreateLogStream]: {
68+
serviceCode: 'logs',
69+
quotaCode: 'L-76507CEF',
70+
enabled: true,
71+
},
72+
[Limit.LambdaConcurrentExecutions]: {
73+
serviceCode: 'lambda',
74+
quotaCode: 'L-B99A9384',
75+
enabled: true,
76+
},
6777
};
6878

6979
const dynamodb = new DynamoDB();

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,12 +85,21 @@ export interface IamPolicyArtifactsOutput {
8585
* - Transit Gateway Peering
8686
* - Create LogGroup required for DNS Logging
8787
*/
88-
export async function deploy({ acceleratorConfig, accountStacks, accounts, context, limiter, outputs }: PhaseInput) {
88+
export async function deploy({
89+
acceleratorConfig,
90+
accountStacks,
91+
accounts,
92+
context,
93+
limiter,
94+
outputs,
95+
organizations,
96+
}: PhaseInput) {
8997
const assignedVpcCidrPools = await loadAssignedVpcCidrPool(context.vpcCidrPoolAssignedTable);
9098
const assignedSubnetCidrPools = await loadAssignedSubnetCidrPool(context.subnetCidrPoolAssignedTable);
9199
const masterAccountKey = acceleratorConfig.getMandatoryAccountKey('master');
92100
const iamConfigs = acceleratorConfig.getIamConfigs();
93101
const masterAccountId = getAccountId(accounts, masterAccountKey);
102+
const rootOrgId = organizations[0].rootOrgId!;
94103
if (!masterAccountId) {
95104
throw new Error(`Cannot find mandatory primary account ${masterAccountKey}`);
96105
}
@@ -576,10 +585,10 @@ export async function deploy({ acceleratorConfig, accountStacks, accounts, conte
576585
// Central Services step 1
577586
await cwlCentralLoggingToS3.step1({
578587
accountStacks,
579-
accounts,
580588
logBucket,
581589
outputs,
582590
config: acceleratorConfig,
591+
rootOrgId,
583592
});
584593

585594
await vpcDeployment.step1({

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

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,18 @@ import path from 'path';
3535

3636
export interface CentralLoggingToS3Step1Props {
3737
accountStacks: AccountStacks;
38-
accounts: Account[];
3938
logBucket: s3.IBucket;
4039
outputs: StackOutput[];
4140
config: c.AcceleratorConfig;
41+
rootOrgId: string;
4242
}
4343

4444
/**
4545
* Enable Central Logging to S3 in "log-archive" account Step 1
4646
*/
4747
export async function step1(props: CentralLoggingToS3Step1Props) {
48-
const { accountStacks, accounts, logBucket, config, outputs } = props;
48+
const { accountStacks, logBucket, config, outputs, rootOrgId } = props;
4949
// Setup for CloudWatch logs storing in logs account
50-
const allAccountIds = accounts.map(account => account.id);
5150
const centralLogServices = config['global-options']['central-log-services'];
5251
const cwlRegionsConfig = config['global-options']['additional-cwl-regions'];
5352
const homeRegion = config['global-options']['central-log-services'].region;
@@ -112,7 +111,6 @@ export async function step1(props: CentralLoggingToS3Step1Props) {
112111

113112
await cwlSettingsInLogArchive({
114113
scope: logAccountStack,
115-
accountIds: allAccountIds,
116114
bucketArn: logBucket.bucketArn,
117115
shardCount: regionConfig['kinesis-stream-shard-count'],
118116
logStreamRoleArn: cwlLogStreamRoleOutput.roleArn,
@@ -121,6 +119,7 @@ export async function step1(props: CentralLoggingToS3Step1Props) {
121119
region,
122120
encryptionKey,
123121
homeRegionEncryptionKey,
122+
rootOrgId,
124123
});
125124
}
126125
}
@@ -131,7 +130,6 @@ export async function step1(props: CentralLoggingToS3Step1Props) {
131130
*/
132131
async function cwlSettingsInLogArchive(props: {
133132
scope: cdk.Construct;
134-
accountIds: string[];
135133
bucketArn: string;
136134
logStreamRoleArn: string;
137135
kinesisStreamRoleArn: string;
@@ -140,10 +138,10 @@ async function cwlSettingsInLogArchive(props: {
140138
shardCount?: number;
141139
dynamicS3LogPartitioning?: c.S3LogPartition[];
142140
region: string;
141+
rootOrgId: string;
143142
}) {
144143
const {
145144
scope,
146-
accountIds,
147145
bucketArn,
148146
logStreamRoleArn,
149147
kinesisStreamRoleArn,
@@ -152,6 +150,7 @@ async function cwlSettingsInLogArchive(props: {
152150
region,
153151
encryptionKey,
154152
homeRegionEncryptionKey,
153+
rootOrgId,
155154
} = props;
156155

157156
// Create Kinesis Stream for Logs streaming
@@ -166,26 +165,29 @@ async function cwlSettingsInLogArchive(props: {
166165
});
167166

168167
const destinationName = createName({
169-
name: 'LogDestination',
168+
name: 'LogDestinationOrg',
170169
suffixLength: 0,
171170
});
172171

173-
const destinatinPolicy = {
172+
const destinationPolicy = {
174173
Version: '2012-10-17',
175174
Statement: [
176175
{
177176
Effect: 'Allow',
178-
Principal: {
179-
AWS: accountIds,
180-
},
177+
Principal: '*',
181178
Action: 'logs:PutSubscriptionFilter',
182179
Resource: `arn:aws:logs:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:destination:${destinationName}`,
180+
Condition: {
181+
StringEquals: {
182+
'aws:PrincipalOrgID': [rootOrgId],
183+
},
184+
},
183185
},
184186
],
185187
};
186-
const destinationPolicyStr = JSON.stringify(destinatinPolicy);
188+
const destinationPolicyStr = JSON.stringify(destinationPolicy);
187189
// Create AWS Logs Destination
188-
const logDestination = new logs.CfnDestination(scope, 'Log-Destination', {
190+
const logDestination = new logs.CfnDestination(scope, 'Log-Destination-Org', {
189191
destinationName,
190192
targetArn: logsStream.streamArn,
191193
roleArn: logStreamRoleArn,
@@ -269,7 +271,7 @@ async function cwlSettingsInLogArchive(props: {
269271
});
270272

271273
// Store LogDestination ARN in output so that subsequent phases can access the output
272-
new CfnLogDestinationOutput(scope, `CloudWatchCentralLoggingOutput`, {
274+
new CfnLogDestinationOutput(scope, `CloudWatchCentralLoggingOrgOutput`, {
273275
destinationArn: logDestination.attrArn,
274276
destinationName: logDestination.destinationName,
275277
destinationKey: 'CwlCentralLogDestination',

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { CentralLoggingSubscriptionFilter } from '@aws-accelerator/custom-resour
1818
import { createName } from '@aws-accelerator/cdk-accelerator/src/core/accelerator-name-generator';
1919
import { LogDestinationOutputFinder } from '@aws-accelerator/common-outputs/src/log-destination';
2020
import { IamRoleOutputFinder } from '@aws-accelerator/common-outputs/src/iam-role';
21+
import { v4 as uuidv4 } from 'uuid';
2122

2223
export interface CentralLoggingToS3Step2Props {
2324
accountStacks: AccountStacks;
@@ -86,6 +87,7 @@ export async function step2(props: CentralLoggingToS3Step2Props) {
8687
ruleName,
8788
logRetention,
8889
roleArn: subscriptionRoleOutput.roleArn,
90+
uuid: uuidv4(),
8991
});
9092
}
9193
}

src/lib/common-outputs/src/limits.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ export enum Limit {
1818
CloudFormationStackCount = 'AWS CloudFormation/Stack count',
1919
CloudFormationStackSetPerAdmin = 'AWS CloudFormation/Stack sets per administrator account',
2020
OrganizationsMaximumAccounts = 'AWS Organizations/Maximum accounts',
21+
CloudWatchCreateLogStream = 'AWS CloudWatch Logs/CreateLogStream throttle limit in transactions per second',
22+
LambdaConcurrentExecutions = 'AWS Lambda/Concurrent Executions',
2123
}
2224

2325
export interface LimitOutput {

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const handler = async (input: any): Promise<string> => {
2424

2525
const logGroupName = input.detail.requestParameters.logGroupName as string;
2626
const logDestinationArn = process.env.LOG_DESTINATION;
27+
const subscriptionFilterRoleArn = process.env.SUBSCRIPTION_FILTER_ROLE_ARN;
2728
if (!logDestinationArn) {
2829
console.warn(`Log Destination is not parent in env for this account`);
2930
const newLocal = `Log Destination is not parent in env for this account`;
@@ -40,7 +41,11 @@ 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+
45+
if (subscriptionFilterRoleArn) {
46+
await addSubscriptionFilter(logGroupName, logDestinationArn, subscriptionFilterRoleArn);
47+
}
48+
4449
const logRetention = process.env.LOG_RETENTION;
4550
if (logRetention) {
4651
// Update Log Retention Policy
@@ -49,7 +54,7 @@ export const handler = async (input: any): Promise<string> => {
4954
return 'SUCCESS';
5055
};
5156

52-
async function addSubscriptionFilter(logGroupName: string, destinationArn: string) {
57+
async function addSubscriptionFilter(logGroupName: string, destinationArn: string, subscriptionFilterRoleArn: string) {
5358
// Adding subscription filter
5459
await throttlingBackOff(() =>
5560
logs
@@ -58,6 +63,7 @@ async function addSubscriptionFilter(logGroupName: string, destinationArn: strin
5863
logGroupName,
5964
filterName: `${CloudWatchRulePrefix}${logGroupName}`,
6065
filterPattern: '',
66+
roleArn: subscriptionFilterRoleArn,
6167
})
6268
.promise(),
6369
);

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+
subscriptionFilterRoleArn?: 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, subscriptionFilterRoleArn } = 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, subscriptionFilterRoleArn!);
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, subscriptionFilterRoleArn } = 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, subscriptionFilterRoleArn!);
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, subscriptionFilterRoleArn: 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: subscriptionFilterRoleArn,
180182
})
181183
.promise(),
182184
);

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ export interface CentralLoggingSubscriptionFilterProps {
2525
ruleName: string;
2626
logRetention: number;
2727
roleArn: string;
28+
uuid: string;
29+
subscriptionFilterRoleArn?: string;
2830
}
2931

3032
/**
@@ -41,6 +43,24 @@ export class CentralLoggingSubscriptionFilter extends cdk.Construct {
4143
constructor(scope: cdk.Construct, id: string, props: CentralLoggingSubscriptionFilterProps) {
4244
super(scope, id);
4345

46+
// Since this is deployed organization wide, this role is required
47+
// https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CreateSubscriptionFilter-IAMrole.html
48+
const subscriptionFilterRole = new iam.Role(this, 'SubscriptionFilterRole', {
49+
assumedBy: new iam.ServicePrincipal(cdk.Fn.sub('logs.${AWS::Region}.amazonaws.com')),
50+
description: 'Role used by Subscription Filter to allow access to CloudWatch Destination',
51+
inlinePolicies: {
52+
accessLogEvents: new iam.PolicyDocument({
53+
statements: [
54+
new iam.PolicyStatement({
55+
resources: ['*'],
56+
actions: ['logs:PutLogEvents'],
57+
}),
58+
],
59+
}),
60+
},
61+
});
62+
63+
props.subscriptionFilterRoleArn = subscriptionFilterRole.roleArn;
4464
this.role = iam.Role.fromRoleArn(this, `${resourceType}Role`, props.roleArn);
4565
// Custom Resource to add subscriptin filter to existing logGroups
4666
this.resource = new cdk.CustomResource(this, 'CustomResource', {
@@ -56,6 +76,7 @@ export class CentralLoggingSubscriptionFilter extends cdk.Construct {
5676
EXCLUSIONS: JSON.stringify(props.globalExclusions),
5777
LOG_DESTINATION: props.logDestinationArn,
5878
LOG_RETENTION: props.logRetention.toString(),
79+
SUBSCRIPTION_FILTER_ROLE_ARN: subscriptionFilterRole.roleArn,
5980
};
6081
const addSubscriptionLambda = this.ensureLambdaFunction(
6182
this.cloudWatchEnventLambdaPath,

0 commit comments

Comments
 (0)