Skip to content

Commit

Permalink
feat(s3): Allow separate logging bucket for AWS Config
Browse files Browse the repository at this point in the history
For the case where the central logs bucket has Object Lock enabled, AWS
Config does not support logging directly to the central logs bucket. A
workaround for that issue is to create a separate bucket without Object
Lock enabled and replicate everything over to the central logs bucket.
This change adds the option to create that separate bucket with a
replication rule into the central logs bucket and repointing AWS Config
to use that separate bucket instead.
  • Loading branch information
omnibrian committed May 10, 2023
1 parent 3fcd620 commit 48153a8
Show file tree
Hide file tree
Showing 8 changed files with 475 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ interface BucketPrefixes {
elbLogs: string;
costUsage: string;
s3AccessLogs: string;
awsConfig: string;
auditManager: string;
vpcFlowLogs: string;
metadata: string;
Expand Down Expand Up @@ -137,6 +138,7 @@ export class AcceleratorResourceNames {
elbLogs: 'PLACE_HOLDER',
costUsage: 'PLACE_HOLDER',
s3AccessLogs: 'PLACE_HOLDER',
awsConfig: 'PLACE_HOLDER',
auditManager: 'PLACE_HOLDER',
vpcFlowLogs: 'PLACE_HOLDER',
metadata: 'PLACE_HOLDER',
Expand Down Expand Up @@ -256,6 +258,7 @@ export class AcceleratorResourceNames {
this.bucketPrefixes.elbLogs = props.prefixes.bucketName + '-elb-access-logs';
this.bucketPrefixes.costUsage = props.prefixes.bucketName + '-cur';
this.bucketPrefixes.s3AccessLogs = props.prefixes.bucketName + '-s3-access-logs';
this.bucketPrefixes.awsConfig = props.prefixes.bucketName + '-aws-config';
this.bucketPrefixes.auditManager = props.prefixes.bucketName + '-auditmgr';
this.bucketPrefixes.vpcFlowLogs = props.prefixes.bucketName + '-vpc';
this.bucketPrefixes.metadata = props.prefixes.bucketName + '-metadata';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,65 @@ export class LoggingStack extends AcceleratorStack {
// Create VPC Flow logs destination bucket
this.createVpcFlowLogsBucket(s3Key, serverAccessLogsBucket, replicationProps);

if (
cdk.Stack.of(this).account === this.props.accountsConfig.getLogArchiveAccountId() &&
this.props.securityConfig.awsConfig.enableDeliveryChannel &&
this.props.securityConfig.awsConfig.useSeparateBucket
) {
const configBucket = new Bucket(this, 'ConfigBucket', {
encryptionType: BucketEncryptionType.SSE_KMS,
s3BucketName: `${this.acceleratorResourceNames.bucketPrefixes.awsConfig}-${cdk.Stack.of(this).account}-${
cdk.Stack.of(this).region
}`,
kmsKey: this.centralLogsBucket?.getS3Bucket().getKey(),
serverAccessLogsBucket: serverAccessLogsBucket.getS3Bucket(),
replicationProps,
s3LifeCycleRules: this.getS3LifeCycleRules(this.props.globalConfig.logging.configBucket?.lifecycleRules),
});

// To make sure central log bucket created before elb access log bucket, this is required when logging stack executes in home region
if (this.centralLogsBucket) {
configBucket.node.addDependency(this.centralLogsBucket);
}

// AwsSolutions-IAM5: The IAM entity contains wildcard permissions and does not have a cdk_nag rule suppression with evidence for those permission.
NagSuppressions.addResourceSuppressionsByPath(
this,
`/${this.stackName}/ConfigBucket/ConfigBucketReplication/` +
pascalCase(this.centralLogsBucketName) +
'-ReplicationRole/DefaultPolicy/Resource',
[
{
id: 'AwsSolutions-IAM5',
reason: 'Allows only specific policy.',
},
],
);

configBucket.getS3Bucket().addToResourcePolicy(
new cdk.aws_iam.PolicyStatement({
principals: [new cdk.aws_iam.ServicePrincipal('config.amazonaws.com')],
actions: ['s3:PutObject'],
resources: [configBucket.getS3Bucket().arnForObjects('*')],
conditions: {
StringEquals: {
's3:x-amz-acl': 'bucket-owner-full-control',
},
},
}),
);

configBucket.getS3Bucket().addToResourcePolicy(
new cdk.aws_iam.PolicyStatement({
principals: [new cdk.aws_iam.ServicePrincipal('config.amazonaws.com')],
actions: ['s3:GetBucketAcl', 's3:ListBucket'],
resources: [configBucket.getS3Bucket().bucketArn],
}),
);

this.configBucketAddResourcePolicies(configBucket.getS3Bucket());
}

/**
* Create S3 Bucket for ELB Access Logs, this is created in log archive account
* For ELB to write access logs bucket is needed to have SSE-S3 server-side encryption
Expand Down Expand Up @@ -1771,6 +1830,24 @@ export class LoggingStack extends AcceleratorStack {
}
}

private configBucketAddResourcePolicies(configBucket: cdk.aws_s3.IBucket) {
this.logger.info('Adding AWS Config bucket resource policies to S3');
for (const attachment of this.props.globalConfig.logging.configBucket?.s3ResourcePolicyAttachments ?? []) {
const policyDocument = JSON.parse(
this.generatePolicyReplacements(path.join(this.props.configDirPath, attachment.policy), false),
);
// Create a statements list using the PolicyStatement factory
const statements: cdk.aws_iam.PolicyStatement[] = [];
for (const statement of policyDocument.Statement) {
statements.push(cdk.aws_iam.PolicyStatement.fromJson(statement));
}

for (const statement of statements) {
configBucket.addToResourcePolicy(statement);
}
}
}

private elbLogBucketAddResourcePolicies(elbLogBucket: cdk.aws_s3.IBucket) {
this.logger.info(`Adding elb log bucket resource policies to S3`);
for (const attachment of this.props.globalConfig.logging.elbLogBucket?.s3ResourcePolicyAttachments ?? []) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,11 @@ export class SecurityResourcesStack extends AcceleratorStack {
});

this.deliveryChannel = new cdk.aws_config.CfnDeliveryChannel(this, 'ConfigDeliveryChannel', {
s3BucketName: `${this.acceleratorResourceNames.bucketPrefixes.centralLogs}-${this.logArchiveAccountId}-${this.props.centralizedLoggingRegion}`,
s3BucketName: `${
this.props.securityConfig.awsConfig.useSeparateBucket
? this.acceleratorResourceNames.bucketPrefixes.awsConfig
: this.acceleratorResourceNames.bucketPrefixes.centralLogs
}-${this.logArchiveAccountId}-${this.props.centralizedLoggingRegion}`,
configSnapshotDeliveryProperties: {
deliveryFrequency: 'One_Hour',
},
Expand All @@ -553,7 +557,11 @@ export class SecurityResourcesStack extends AcceleratorStack {

if (this.props.securityConfig.awsConfig.overrideExisting) {
const configServiceUpdater = new ConfigServiceRecorder(this, 'ConfigRecorderDeliveryChannel', {
s3BucketName: `${this.acceleratorResourceNames.bucketPrefixes.centralLogs}-${this.logArchiveAccountId}-${this.props.centralizedLoggingRegion}`,
s3BucketName: `${
this.props.securityConfig.awsConfig.useSeparateBucket
? this.acceleratorResourceNames.bucketPrefixes.awsConfig
: this.acceleratorResourceNames.bucketPrefixes.centralLogs
}-${this.logArchiveAccountId}-${this.props.centralizedLoggingRegion}`,
s3BucketKmsKey: this.centralLogS3Key,
logRetentionInDays: this.props.globalConfig.cloudwatchLogRetentionInDays,
configRecorderRoleArn: configRecorderRole.roleArn,
Expand Down
Loading

0 comments on commit 48153a8

Please sign in to comment.