Skip to content

Commit cefa453

Browse files
authored
feat(aws-s3): create default bucket policy when required (under feature flag) (#20765)
Created a new feature flag `@aws-cdk/aws-s3:createDefaultLoggingPolicy` There are certain types of S3 Buckets that AWS will automatically create a bucket policy for you if you do not create one. For example, if you create an S3 Bucket to be used as the destination for VPC Flow Logs and you do not create a Bucket Policy, AWS will automatically create a bucket policy for you. The full list of resources can be found [here](https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/AWS-logs-and-resource-policy.html#AWS-logs-infrastructure-S3) - [X] Vpc Flow Logs ~- [ ] AWS Network Firewall logs~ (No L2 support yet) ~- [ ] AWS Global Accelerator flow logs~ ([not currently possible](aws-cloudformation/cloudformation-coverage-roadmap#922)] ~- [ ] EC2 Spot Instance data feed~(no cloudformation support yet) ~- [ ] CloudFront access logs & streaming access logs~ (CloudFront uses bucket ACL _not_ bucket policy) - [X] Network Load Balancer access logs (already done) - [x] Amazon Managed Streaming for Apache Kafka broker logs If we allow AWS to create these policies automatically, it prevents CDK from every managing that policy in the future. Since we know what the policy should be we should instead create the logging bucket with the required policy. fixes #18816 ---- ### All Submissions: * [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) ### Adding new Unconventional Dependencies: * [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies) ### New Features * [x] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)? * [x] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)? *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 651e3b5 commit cefa453

File tree

39 files changed

+6701
-591
lines changed

39 files changed

+6701
-591
lines changed

packages/@aws-cdk/aws-ec2/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1356,6 +1356,20 @@ new ec2.FlowLog(this, 'FlowLogWithKeyPrefix', {
13561356
});
13571357
```
13581358

1359+
When the S3 destination is configured, AWS will automatically create an S3 bucket policy
1360+
that allows the service to write logs to the bucket. This makes it impossible to later update
1361+
that bucket policy. To have CDK create the bucket policy so that future updates can be made,
1362+
the `@aws-cdk/aws-s3:createDefaultLoggingPolicy` [feature flag](https://docs.aws.amazon.com/cdk/v2/guide/featureflags.html) can be used. This can be set
1363+
in the `cdk.json` file.
1364+
1365+
```json
1366+
{
1367+
"context": {
1368+
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true
1369+
}
1370+
}
1371+
```
1372+
13591373
## User Data
13601374

13611375
User data enables you to run a script when your instances start up. In order to configure these scripts you can add commands directly to the script

packages/@aws-cdk/aws-ec2/lib/vpc-flow-logs.ts

Lines changed: 99 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
import * as iam from '@aws-cdk/aws-iam';
22
import * as logs from '@aws-cdk/aws-logs';
33
import * as s3 from '@aws-cdk/aws-s3';
4-
import { IResource, PhysicalName, RemovalPolicy, Resource } from '@aws-cdk/core';
4+
import { IResource, PhysicalName, RemovalPolicy, Resource, FeatureFlags, Stack } from '@aws-cdk/core';
5+
import { S3_CREATE_DEFAULT_LOGGING_POLICY } from '@aws-cdk/cx-api';
56
import { Construct } from 'constructs';
67
import { CfnFlowLog } from './ec2.generated';
78
import { ISubnet, IVpc } from './vpc';
89

910
/**
1011
* A FlowLog
11-
*
12-
*
1312
*/
1413
export interface IFlowLog extends IResource {
1514
/**
@@ -22,8 +21,6 @@ export interface IFlowLog extends IResource {
2221

2322
/**
2423
* The type of VPC traffic to log
25-
*
26-
*
2724
*/
2825
export enum FlowLogTrafficType {
2926
/**
@@ -44,7 +41,6 @@ export enum FlowLogTrafficType {
4441

4542
/**
4643
* The available destination types for Flow Logs
47-
*
4844
*/
4945
export enum FlowLogDestinationType {
5046
/**
@@ -60,8 +56,6 @@ export enum FlowLogDestinationType {
6056

6157
/**
6258
* The type of resource to create the flow log for
63-
*
64-
*
6559
*/
6660
export abstract class FlowLogResourceType {
6761
/**
@@ -106,9 +100,29 @@ export abstract class FlowLogResourceType {
106100
}
107101

108102
/**
109-
* The destination type for the flow log
110-
*
103+
* Options for writing logs to a S3 destination
104+
*/
105+
export interface S3DestinationOptions {
106+
/**
107+
* Use Hive-compatible prefixes for flow logs
108+
* stored in Amazon S3
109+
*
110+
* @default false
111+
*/
112+
readonly hiveCompatiblePartitions?: boolean;
113+
}
114+
115+
/**
116+
* Options for writing logs to a destination
111117
*
118+
* TODO: there are other destination options, currently they are
119+
* only for s3 destinations (not sure if that will change)
120+
*/
121+
export interface DestinationOptions extends S3DestinationOptions { }
122+
123+
124+
/**
125+
* The destination type for the flow log
112126
*/
113127
export abstract class FlowLogDestination {
114128
/**
@@ -124,12 +138,18 @@ export abstract class FlowLogDestination {
124138

125139
/**
126140
* Use S3 as the destination
141+
*
142+
* @param bucket optional s3 bucket to publish logs to. If one is not provided
143+
* a default bucket will be created
144+
* @param keyPrefix optional prefix within the bucket to write logs to
145+
* @param options additional s3 destination options
127146
*/
128-
public static toS3(bucket?: s3.IBucket, keyPrefix?: string): FlowLogDestination {
147+
public static toS3(bucket?: s3.IBucket, keyPrefix?: string, options?: S3DestinationOptions): FlowLogDestination {
129148
return new S3Destination({
130149
logDestinationType: FlowLogDestinationType.S3,
131150
s3Bucket: bucket,
132151
keyPrefix,
152+
destinationOptions: options,
133153
});
134154
}
135155

@@ -141,8 +161,6 @@ export abstract class FlowLogDestination {
141161

142162
/**
143163
* Flow Log Destination configuration
144-
*
145-
*
146164
*/
147165
export interface FlowLogDestinationConfig {
148166
/**
@@ -179,6 +197,13 @@ export interface FlowLogDestinationConfig {
179197
* @default - undefined
180198
*/
181199
readonly keyPrefix?: string;
200+
201+
/**
202+
* Options for writing flow logs to a supported destination
203+
*
204+
* @default - undefined
205+
*/
206+
readonly destinationOptions?: DestinationOptions;
182207
}
183208

184209
/**
@@ -196,13 +221,73 @@ class S3Destination extends FlowLogDestination {
196221
encryption: s3.BucketEncryption.UNENCRYPTED,
197222
removalPolicy: RemovalPolicy.RETAIN,
198223
});
224+
199225
} else {
200226
s3Bucket = this.props.s3Bucket;
201227
}
228+
229+
// https://docs.aws.amazon.com/vpc/latest/userguide/flow-logs-s3.html#flow-logs-s3-permissions
230+
if (FeatureFlags.of(scope).isEnabled(S3_CREATE_DEFAULT_LOGGING_POLICY)) {
231+
const stack = Stack.of(scope);
232+
let keyPrefix = this.props.keyPrefix ?? '';
233+
if (keyPrefix && !keyPrefix.endsWith('/')) {
234+
keyPrefix = keyPrefix + '/';
235+
}
236+
const prefix = this.props.destinationOptions?.hiveCompatiblePartitions
237+
? s3Bucket.arnForObjects(`${keyPrefix}AWSLogs/aws-account-id=${stack.account}/*`)
238+
: s3Bucket.arnForObjects(`${keyPrefix}AWSLogs/${stack.account}/*`);
239+
240+
s3Bucket.addToResourcePolicy(new iam.PolicyStatement({
241+
effect: iam.Effect.ALLOW,
242+
principals: [
243+
new iam.ServicePrincipal('delivery.logs.amazonaws.com'),
244+
],
245+
resources: [
246+
prefix,
247+
],
248+
actions: ['s3:PutObject'],
249+
conditions: {
250+
StringEquals: {
251+
's3:x-amz-acl': 'bucket-owner-full-control',
252+
'aws:SourceAccount': stack.account,
253+
},
254+
ArnLike: {
255+
'aws:SourceArn': stack.formatArn({
256+
service: 'logs',
257+
resource: '*',
258+
}),
259+
},
260+
},
261+
}));
262+
263+
s3Bucket.addToResourcePolicy(new iam.PolicyStatement({
264+
effect: iam.Effect.ALLOW,
265+
principals: [
266+
new iam.ServicePrincipal('delivery.logs.amazonaws.com'),
267+
],
268+
resources: [s3Bucket.bucketArn],
269+
actions: [
270+
's3:GetBucketAcl',
271+
's3:ListBucket',
272+
],
273+
conditions: {
274+
StringEquals: {
275+
'aws:SourceAccount': stack.account,
276+
},
277+
ArnLike: {
278+
'aws:SourceArn': stack.formatArn({
279+
service: 'logs',
280+
resource: '*',
281+
}),
282+
},
283+
},
284+
}));
285+
}
202286
return {
203287
logDestinationType: FlowLogDestinationType.S3,
204288
s3Bucket,
205289
keyPrefix: this.props.keyPrefix,
290+
destinationOptions: this.props.destinationOptions,
206291
};
207292
}
208293
}
@@ -263,8 +348,6 @@ class CloudWatchLogsDestination extends FlowLogDestination {
263348

264349
/**
265350
* Options to add a flow log to a VPC
266-
*
267-
*
268351
*/
269352
export interface FlowLogOptions {
270353
/**
@@ -285,8 +368,6 @@ export interface FlowLogOptions {
285368

286369
/**
287370
* Properties of a VPC Flow Log
288-
*
289-
*
290371
*/
291372
export interface FlowLogProps extends FlowLogOptions {
292373
/**
@@ -307,8 +388,6 @@ export interface FlowLogProps extends FlowLogOptions {
307388

308389
/**
309390
* The base class for a Flow Log
310-
*
311-
*
312391
*/
313392
abstract class FlowLogBase extends Resource implements IFlowLog {
314393
/**
@@ -322,8 +401,6 @@ abstract class FlowLogBase extends Resource implements IFlowLog {
322401
/**
323402
* A VPC flow log.
324403
* @resource AWS::EC2::FlowLog
325-
*
326-
*
327404
*/
328405
export class FlowLog extends FlowLogBase {
329406
/**
@@ -383,6 +460,7 @@ export class FlowLog extends FlowLogBase {
383460
}
384461

385462
const flowLog = new CfnFlowLog(this, 'FlowLog', {
463+
destinationOptions: destinationConfig.destinationOptions,
386464
deliverLogsPermissionArn: this.iamRole ? this.iamRole.roleArn : undefined,
387465
logDestinationType: destinationConfig.logDestinationType,
388466
logGroupName: this.logGroup ? this.logGroup.logGroupName : undefined,

packages/@aws-cdk/aws-ec2/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
"@aws-cdk/assertions": "0.0.0",
8484
"@aws-cdk/cdk-build-tools": "0.0.0",
8585
"@aws-cdk/integ-runner": "0.0.0",
86+
"@aws-cdk/integ-tests": "0.0.0",
8687
"@aws-cdk/cfn2ts": "0.0.0",
8788
"@aws-cdk/cloud-assembly-schema": "0.0.0",
8889
"@aws-cdk/cx-api": "0.0.0",

packages/@aws-cdk/aws-ec2/test/integ.vpc-flow-logs.ts

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,35 @@
1-
/// !cdk-integ *
21
import { PolicyStatement, Effect, ServicePrincipal } from '@aws-cdk/aws-iam';
32
import * as s3 from '@aws-cdk/aws-s3';
43
import { App, RemovalPolicy, Stack, StackProps } from '@aws-cdk/core';
5-
import { FlowLog, FlowLogDestination, FlowLogResourceType, Vpc } from '../lib';
4+
import { IntegTest, ExpectedResult, AssertionsProvider } from '@aws-cdk/integ-tests';
5+
import { FlowLog, FlowLogDestination, FlowLogResourceType, Vpc, Instance, InstanceType, InstanceClass, InstanceSize, MachineImage, AmazonLinuxGeneration } from '../lib';
66

77
const app = new App();
88

9+
class FeatureFlagStack extends Stack {
10+
public readonly bucketArn: string;
11+
public readonly bucket: s3.IBucket;
12+
constructor(scope: App, id: string, props?: StackProps) {
13+
super(scope, id, props);
14+
15+
const vpc = new Vpc(this, 'VPC');
16+
17+
const flowLog = vpc.addFlowLog('FlowLogsS3', {
18+
destination: FlowLogDestination.toS3(),
19+
});
20+
this.bucket = flowLog.bucket!;
21+
this.bucketArn = this.exportValue(flowLog.bucket!.bucketArn);
22+
23+
new Instance(this, 'FlowLogsInstance', {
24+
vpc,
25+
instanceType: InstanceType.of(InstanceClass.T3, InstanceSize.SMALL),
26+
machineImage: MachineImage.latestAmazonLinux({
27+
generation: AmazonLinuxGeneration.AMAZON_LINUX_2,
28+
}),
29+
});
30+
}
31+
}
32+
933
class TestStack extends Stack {
1034
constructor(scope: App, id: string, props?: StackProps) {
1135
super(scope, id, props);
@@ -66,6 +90,27 @@ class TestStack extends Stack {
6690
}
6791
}
6892

69-
new TestStack(app, 'FlowLogsTestStack');
93+
const featureFlagTest = new FeatureFlagStack(app, 'FlowLogsFeatureFlag');
94+
95+
const integ = new IntegTest(app, 'FlowLogs', {
96+
testCases: [
97+
new TestStack(app, 'FlowLogsTestStack'),
98+
featureFlagTest,
99+
],
100+
});
101+
102+
103+
const objects = integ.assertions.awsApiCall('S3', 'listObjectsV2', {
104+
Bucket: featureFlagTest.bucket.bucketName,
105+
MaxKeys: 1,
106+
Prefix: `AWSLogs/${featureFlagTest.account}/vpcflowlogs`,
107+
});
108+
const assertionProvider = objects.node.tryFindChild('SdkProvider') as AssertionsProvider;
109+
assertionProvider.addPolicyStatementFromSdkCall('s3', 'ListBucket', [featureFlagTest.bucketArn]);
110+
assertionProvider.addPolicyStatementFromSdkCall('s3', 'GetObject', [`${featureFlagTest.bucketArn}/*`]);
111+
112+
objects.expect(ExpectedResult.objectLike({
113+
KeyCount: 1,
114+
}));
70115

71116
app.synth();

0 commit comments

Comments
 (0)