Skip to content

Commit b919753

Browse files
fix(core): S3 'consistency' Issues (aws-samples#564)
* Adding enable versioning Custom Resource * Adding replication to S3 bucket * Prettier * Adding lifecycle rule while enabling s3 version * Fixing tests
1 parent 0620e72 commit b919753

File tree

33 files changed

+1080
-130
lines changed

33 files changed

+1080
-130
lines changed

src/deployments/cdk/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@
9999
"@aws-accelerator/custom-resource-ssm-create-document": "workspace:^0.0.1",
100100
"@aws-accelerator/custom-resource-disassociate-hosted-zones": "workspace:^0.0.1",
101101
"@aws-accelerator/custom-resource-ec2-modify-transit-gateway-vpc-attachment": "workspace:^0.0.1",
102+
"@aws-accelerator/custom-resource-s3-put-bucket-versioning": "workspace:^0.0.1",
102103
"@aws-cdk/aws-accessanalyzer": "1.75.0",
103104
"@aws-cdk/aws-autoscaling": "1.75.0",
104105
"@aws-cdk/aws-budgets": "1.75.0",

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import * as globalRoles from '../deployments/iam';
1717
* - Creating required roles for createLogsMetricFilter custom resource
1818
* - Creating required roles for SnsSubscriberLambda custom resource
1919
* - Creating required role for SsmIncreaseThroughput custom resource
20+
* - Creating required role for S3PutBucketReplication custom resource
2021
*/
2122
export async function deploy({ acceleratorConfig, accountStacks, accounts }: PhaseInput) {
2223
// creates roles for macie custom resources
@@ -111,4 +112,10 @@ export async function deploy({ acceleratorConfig, accountStacks, accounts }: Pha
111112
accountStacks,
112113
accounts,
113114
});
115+
116+
// Creates required role for S3PutBucketReplication custom resource
117+
await globalRoles.createS3PutReplicationRole({
118+
accountStacks,
119+
accounts,
120+
});
114121
}

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

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import * as ssm from '@aws-cdk/aws-ssm';
21
import { getAccountId } from '../utils/accounts';
32
import { VpcOutputFinder } from '@aws-accelerator/common-outputs/src/vpc';
43
import { getStackJsonOutput } from '@aws-accelerator/common-outputs/src/stack-output';
@@ -15,6 +14,7 @@ import { ArtifactOutputFinder } from '../deployments/artifacts/outputs';
1514
import { ImageIdOutputFinder } from '@aws-accelerator/common-outputs/src/ami-output';
1615
import * as cloudWatchDeployment from '../deployments/cloud-watch';
1716
import * as ssmDeployment from '../deployments/ssm';
17+
import * as defaults from '../deployments/defaults';
1818

1919
/**
2020
* This is the main entry point to deploy phase 5
@@ -29,6 +29,21 @@ import * as ssmDeployment from '../deployments/ssm';
2929
*/
3030

3131
export async function deploy({ acceleratorConfig, accountStacks, accounts, context, outputs }: PhaseInput) {
32+
// Find the account buckets in the outputs
33+
const accountBuckets = defaults.AccountBucketOutput.getAccountBuckets({
34+
accounts,
35+
accountStacks,
36+
config: acceleratorConfig,
37+
outputs,
38+
});
39+
40+
// Find the central bucket in the outputs
41+
const centralBucket = defaults.CentralBucketOutput.getBucket({
42+
accountStacks,
43+
config: acceleratorConfig,
44+
outputs,
45+
});
46+
3247
const accountNames = acceleratorConfig
3348
.getMandatoryAccountConfigs()
3449
.map(([_, accountConfig]) => accountConfig['account-name']);
@@ -205,4 +220,13 @@ export async function deploy({ acceleratorConfig, accountStacks, accounts, conte
205220
config: acceleratorConfig,
206221
outputs,
207222
});
223+
224+
await defaults.step3({
225+
accountBuckets,
226+
accountStacks,
227+
accounts,
228+
config: acceleratorConfig,
229+
centralBucket,
230+
outputs,
231+
});
208232
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './outputs';
22
export * from './step-1';
33
export * from './step-2';
4+
export * from './step-3';

src/deployments/cdk/src/deployments/defaults/shared.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,16 @@ export function createDefaultS3Bucket(props: {
4040
accountStack: AccountStack;
4141
encryptionKey: kms.Key;
4242
logRetention: number;
43+
versioned?: boolean;
4344
}): Bucket {
44-
const { accountStack, encryptionKey, logRetention } = props;
45+
const { accountStack, encryptionKey, logRetention, versioned } = props;
4546

4647
// Generate fixed bucket name so we can do initialize cross-account bucket replication
4748
const bucket = new Bucket(accountStack, 'DefaultBucket', {
4849
encryptionKey,
4950
expirationInDays: logRetention,
5051
removalPolicy: cdk.RemovalPolicy.RETAIN,
52+
versioned,
5153
});
5254

5355
// Let the bucket name be generated by CloudFormation

src/deployments/cdk/src/deployments/defaults/step-1.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,6 @@ function createCentralBucketCopy(props: DefaultsStep1Props) {
9393

9494
const bucket = new s3.Bucket(masterAccountStack, 'CentralBucketCopy', {
9595
encryptionKey,
96-
versioned: true,
9796
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
9897
removalPolicy: cdk.RemovalPolicy.RETAIN,
9998
});
@@ -177,6 +176,7 @@ function createCentralLogBucket(props: DefaultsStep1Props) {
177176
accountStack: logAccountStack,
178177
encryptionKey: logKey.encryptionKey,
179178
logRetention: defaultLogRetention!,
179+
versioned: true,
180180
});
181181

182182
// Allow replication from all Accelerator accounts

src/deployments/cdk/src/deployments/defaults/step-2.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,6 @@ function createDefaultS3Buckets(props: DefaultsStep2Props) {
8282
}),
8383
);
8484

85-
bucket.replicateTo({
86-
destinationBucket: centralLogBucket,
87-
destinationAccountId: logAccountId,
88-
// Only replicate files under ACCOUNT_ID/
89-
prefix: `${cdk.Aws.ACCOUNT_ID}/`,
90-
});
9185
buckets[accountKey] = bucket;
9286

9387
new CfnAccountBucketOutput(accountStack, 'DefaultBucketOutput', {
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import * as cdk from '@aws-cdk/core';
2+
3+
import { AccountStacks } from '../../common/account-stacks';
4+
import { Account, getAccountId } from '../../utils/accounts';
5+
import { AccountBuckets, RegionalBucket } from './outputs';
6+
import { AcceleratorConfig } from '@aws-accelerator/common-config/src';
7+
import { S3PutBucketVersioning } from '@aws-accelerator/custom-resource-s3-put-bucket-versioning';
8+
import { BucketReplication } from '@aws-accelerator/cdk-constructs/src/s3';
9+
import { IamRoleOutputFinder } from '@aws-accelerator/common-outputs/src/iam-role';
10+
import { StackOutput } from '@aws-accelerator/common-outputs/src/stack-output';
11+
12+
export interface DefaultsStep3Props {
13+
accountStacks: AccountStacks;
14+
accounts: Account[];
15+
config: AcceleratorConfig;
16+
accountBuckets: AccountBuckets;
17+
centralBucket: RegionalBucket;
18+
outputs: StackOutput[];
19+
}
20+
21+
export async function step3(props: DefaultsStep3Props) {
22+
const { config, accountBuckets, accountStacks, centralBucket, accounts, outputs } = props;
23+
const logAccountKey = config['global-options']['central-log-services'].account;
24+
const masterAccountKey = config['global-options']['aws-org-master'].account;
25+
const centralLogBucket = accountBuckets[logAccountKey];
26+
const defaultLogRetention = config['global-options']['default-s3-retention'];
27+
for (const [accountKey, accountBucket] of Object.entries(accountBuckets)) {
28+
if (accountKey === logAccountKey) {
29+
continue;
30+
}
31+
console.log(`Enabling Versioning for accountBucket "${accountKey}"`);
32+
const accountStack = accountStacks.tryGetOrCreateAccountStack(accountKey);
33+
if (!accountStack) {
34+
console.warn(`Cannot find account stack ${accountKey}`);
35+
continue;
36+
}
37+
38+
const accountConfig = config.getAccountByKey(accountKey);
39+
const logRetention = accountConfig?.['s3-retention'] ?? defaultLogRetention;
40+
41+
const s3PutReplicationRole = IamRoleOutputFinder.tryFindOneByName({
42+
outputs,
43+
accountKey,
44+
roleKey: 'S3PutReplicationRole',
45+
});
46+
47+
if (!s3PutReplicationRole) {
48+
console.warn(`S3PutBucketReplication role is not created for account "${accountKey}"`);
49+
continue;
50+
}
51+
52+
const versioning = new S3PutBucketVersioning(accountStack, `AccountBucket-PutBucketVersioning`, {
53+
bucketName: accountBucket.bucketName,
54+
logRetention,
55+
roleArn: s3PutReplicationRole.roleArn,
56+
});
57+
58+
// Get IBucket object for enabling replication on it
59+
const bucket = new BucketReplication(accountStack, 'DefaultBucketReplication', {
60+
bucket: accountBucket,
61+
s3PutReplicationRole: s3PutReplicationRole.roleArn,
62+
});
63+
bucket.node.addDependency(versioning);
64+
65+
bucket.replicateTo({
66+
destinationBucket: centralLogBucket,
67+
destinationAccountId: getAccountId(accounts, logAccountKey)!,
68+
id: 'LogArchiveAccountBucket',
69+
// Only replicate files under ACCOUNT_ID/
70+
prefix: `${cdk.Aws.ACCOUNT_ID}/`,
71+
});
72+
}
73+
74+
console.log(`Enabling Versioning for centralBucket "${masterAccountKey}"`);
75+
const accountStack = accountStacks.tryGetOrCreateAccountStack(masterAccountKey);
76+
if (!accountStack) {
77+
console.warn(`Cannot find account stack ${masterAccountKey}`);
78+
return;
79+
}
80+
81+
const s3PutReplicationRoleMaster = IamRoleOutputFinder.tryFindOneByName({
82+
outputs,
83+
accountKey: masterAccountKey,
84+
roleKey: 'S3PutReplicationRole',
85+
});
86+
87+
if (!s3PutReplicationRoleMaster) {
88+
console.warn(`S3PutBucketReplication role is not created for account "${masterAccountKey}"`);
89+
return;
90+
}
91+
92+
const centralBucketVersioning = new S3PutBucketVersioning(accountStack, `CentralBucket-PutBucketVersioning`, {
93+
bucketName: centralBucket.bucketName,
94+
roleArn: s3PutReplicationRoleMaster.roleArn,
95+
});
96+
97+
// Get IBucket object for enabling replication on it
98+
const centralBucketObj = new BucketReplication(accountStack, 'CentralBucketReplication', {
99+
bucket: centralBucket,
100+
s3PutReplicationRole: s3PutReplicationRoleMaster.roleArn,
101+
});
102+
centralBucketObj.node.addDependency(centralBucketVersioning);
103+
104+
centralBucketObj.replicateTo({
105+
destinationBucket: centralLogBucket,
106+
destinationAccountId: getAccountId(accounts, logAccountKey)!,
107+
id: 'LogArchiveAccountBucket',
108+
// Only replicate files under ACCOUNT_ID/
109+
prefix: `${cdk.Aws.ACCOUNT_ID}/`,
110+
});
111+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ export * from './cleanup-role';
1717
export * from './central-endpoints-deployment-roles';
1818
export * from './ssm-throughput-roles';
1919
export * from './ec2';
20+
export * from './s3-put-replication-role';
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import * as iam from '@aws-cdk/aws-iam';
2+
import { AccountStacks, AccountStack } from '../../common/account-stacks';
3+
import { createIamRoleOutput } from './outputs';
4+
import { Account } from '@aws-accelerator/common-outputs/src/accounts';
5+
6+
export interface S3PutReplicationRoleProps {
7+
accountStacks: AccountStacks;
8+
accounts: Account[];
9+
}
10+
11+
export async function createS3PutReplicationRole(props: S3PutReplicationRoleProps): Promise<void> {
12+
const { accountStacks, accounts } = props;
13+
14+
for (const account of accounts) {
15+
const accountStack = accountStacks.getOrCreateAccountStack(account.key);
16+
const iamRole = await createRole(accountStack);
17+
createIamRoleOutput(accountStack, iamRole, 'S3PutReplicationRole');
18+
}
19+
}
20+
21+
async function createRole(stack: AccountStack) {
22+
const role = new iam.Role(stack, 'Custom::S3PutReplicationRole', {
23+
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
24+
});
25+
26+
role.addToPrincipalPolicy(
27+
new iam.PolicyStatement({
28+
actions: [
29+
'iam:PassRole',
30+
'logs:CreateLogStream',
31+
'logs:CreateLogGroup',
32+
'logs:PutLogEvents',
33+
's3:PutLifecycleConfiguration',
34+
's3:PutReplicationConfiguration',
35+
's3:PutBucketVersioning',
36+
],
37+
resources: ['*'],
38+
}),
39+
);
40+
return role;
41+
}

0 commit comments

Comments
 (0)