Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(aws-cloudfront-s3): update construct to use origin access controls; add support for CMK-encrypted buckets #1038

Merged
merged 50 commits into from
Jan 9, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
1f4eda2
feat: upgrade aws-cloudfront-s3 to use oac instead of oai
hayesry Dec 6, 2023
d39a81d
feat: added testing and documentation notes
hayesry Dec 7, 2023
01a834e
merge: conflict resolution between main and feature branch
hayesry Dec 7, 2023
372a244
merge: alignment with #1037
hayesry Dec 8, 2023
a02f795
fix: version number for aws-cloudfront-s3 custom resource
hayesry Dec 8, 2023
a1e9872
fix: edits to custom resource package.json file
hayesry Dec 8, 2023
2b7fc52
fix: issue with installing deps for custom resource
hayesry Dec 8, 2023
f2a7053
feat: create new S3OacOrigin, consolidate custom resources
hayesry Dec 19, 2023
2fbce66
merge: branch 'main' into 'feature/hayesry/cloudfront-s3-oac-update-s…
hayesry Dec 19, 2023
7e82a23
chore: change bucket variable naming in 'aws-cloudfront-s3/lib/index.ts'
hayesry Dec 20, 2023
721d2ad
chore: consolidate custom resources, additional cleanup
hayesry Dec 21, 2023
c231611
chore: update resources/package.json file
hayesry Dec 21, 2023
b7b583d
chore: add resources dependency to aws-cloudfront-s3 package.json file
hayesry Dec 21, 2023
dac63df
chore: edit source/package.json to revert workspaces specification
hayesry Dec 21, 2023
16519f9
chore: add aws-cdk-lib to resources/package.json
hayesry Dec 21, 2023
e08a746
chore: remove carat from aws-cdk-lib dependency
hayesry Dec 21, 2023
1bbe291
Update package.json
hayesry Dec 21, 2023
ba5549f
chore: pr updates
hayesry Dec 26, 2023
2b35b85
chore: pr updates
hayesry Dec 26, 2023
eeee5bb
chore: update integ tests
hayesry Dec 26, 2023
91cb42d
chore: update package.json
hayesry Dec 26, 2023
aa6a554
fix: oac naming and resources/package.json
hayesry Dec 27, 2023
572b05b
chore: add cfn suppression statements to custom resource providers wh…
hayesry Dec 27, 2023
2b78680
chore: cfn-nag statements for bucket deployments
hayesry Dec 27, 2023
32bb658
chore: remove bucketDeployment samples
hayesry Dec 27, 2023
2b4a67f
chore: update imports and integ tests
hayesry Dec 28, 2023
aafa8dd
chore: update integ tests
hayesry Dec 28, 2023
9f41fe6
chore: updated naming for `defaults.CloudFrontDistributionForS3` func…
hayesry Dec 28, 2023
721725b
chore: edited comments for s3-oac-origin.ts
hayesry Dec 28, 2023
ad63fe5
chore: minor organizational improvements to import statements
hayesry Dec 28, 2023
e054453
chore: update custom resource description; remove CWLogs policy state…
hayesry Dec 29, 2023
e0a323a
chore: naming and spelling
hayesry Dec 29, 2023
d503152
feat: add checking for duplicate key policies by sid
hayesry Dec 29, 2023
5f73d6a
feat: re-add support for legacy HttpOrigin configs
hayesry Jan 4, 2024
f2ececd
chore: eslint
hayesry Jan 4, 2024
744841c
chore: more eslint
hayesry Jan 4, 2024
0b77299
chore: cfn-nag
hayesry Jan 4, 2024
fde2da5
feat: add printWarning for customers using HttpOrigin
hayesry Jan 4, 2024
49b8369
fix: remove warning prefix
hayesry Jan 4, 2024
9b231fc
chore: add comment on default key policy name specification
hayesry Jan 4, 2024
e9b0063
chore: additional testing, readme updates
hayesry Jan 5, 2024
379fdd1
Merge branch 'main' into feature/hayesry/cloudfront-s3-oac-update-sta…
hayesry Jan 5, 2024
65e8c32
feat: refactored props for createCloudFrontDistributionForS3()
hayesry Jan 8, 2024
e512955
Merge branch 'feature/hayesry/cloudfront-s3-oac-update-staging' of ht…
hayesry Jan 8, 2024
0b48016
chore: print warnings language updates
hayesry Jan 8, 2024
545e005
chore: switch file naming for shared functions in resources/
hayesry Jan 8, 2024
aa61318
chore: update imports in resources/
hayesry Jan 8, 2024
7f24545
feat: overwrite existing key policy, update unit tests
hayesry Jan 8, 2024
7acf926
fix: pass id from the top level through to createCloudFrontDistributi…
hayesry Jan 8, 2024
702ad2e
chore: update integration tests
hayesry Jan 9, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion source/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
},
"workspaces": {
"packages": [
"./patterns/@aws-solutions-constructs/*"
"./patterns/@aws-solutions-constructs/**/*"
],
"nohoist": [
"**/deepmerge",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
lib/*.js
lib/**/*.js
test/*.js
*.d.ts
coverage
test/lambda/index.js
node_modules
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@
|![Java Logo](https://docs.aws.amazon.com/cdk/api/latest/img/java32.png) Java|`software.amazon.awsconstructs.services.cloudfronts3`|

## Overview
This AWS Solutions Construct implements an AWS CloudFront fronting an AWS S3 Bucket.
This AWS Solutions Construct provisions an Amazon CloudFront Distribution that serves objects from an AWS S3 Bucket via an Origin Access Control (OAC).

> **Note:** This AWS Solutions Construct was updated in December 2023 to replace the use of Origin Access Identities (OAIs) with Origin Access Controls (OACs) for accessing objects from the Bucket. Due to the
> current configuration of one or more underlying AWS CDK constructs, this Construct will still provison an OAI, but it will be "orphaned" and the OAC will be used for accessing the Bucket. The
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this still true? With the new S3OacOrigin construct, my anticipation would be that upgrading old versions will leave behind an OAI, but new stacks will not get one.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, removed the notice altogether in e0a323a since we now just provision an OAC and no OAI

> unused OAI does not introduce any cost or security risk, and it can be safely deleted through the AWS CloudFront Management Console if desired.

Here is a minimal deployable pattern definition:

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
**/*.js
node_modules
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/**
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This module needs to move to resources/lib/kms-key-policy-updater-custom-resource.ts - we've already got a structure for where to put custom resources.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this into the /resources folder in commit f2a7053

* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
* with the License. A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES
* OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions
* and limitations under the License.
*/

import { KMSClient, GetKeyPolicyCommand, DescribeKeyCommand, PutKeyPolicyCommand, KeyManagerType } from "@aws-sdk/client-kms";

const kmsClient = new KMSClient();

export const handler = async (e: any, context: any) => {

let status = 'SUCCESS';
let responseData = {};

if (e.RequestType === 'Create' || e.RequestType === 'Update') {

try {
const kmsKeyId = e.ResourceProperties.KmsKeyId;
const cloudFrontDistributionId = e.ResourceProperties.CloudFrontDistributionId;
const accountId = e.ResourceProperties.AccountId;
const region = process.env.AWS_REGION;

const describeKeyCommandResponse = await kmsClient.send(new DescribeKeyCommand({
KeyId: kmsKeyId
}));

if (describeKeyCommandResponse.KeyMetadata?.KeyManager === KeyManagerType.AWS) {
return {
Status: 'SUCCESS',
Reason: 'An AWS managed key was provided, no action needed from the custom resource, exiting now.',
PhysicalResourceId: e.PhysicalResourceId ?? context.logStreamName,
StackId: e.StackId,
RequestId: e.RequestId,
LogicalResourceId: e.LogicalResourceId,
Data: 'An AWS managed key was provided, no action needed from the custom resource, exiting now.',
};
}

const getKeyPolicyCommandResponse = await kmsClient.send(new GetKeyPolicyCommand({
KeyId: kmsKeyId,
PolicyName: 'default'
}));

if (!getKeyPolicyCommandResponse.Policy) {
return {
Status: 'FAILED',
Reason: 'An error occurred while retrieving the key policy',
PhysicalResourceId: e.PhysicalResourceId ?? context.logStreamName,
StackId: e.StackId,
RequestId: e.RequestId,
LogicalResourceId: e.LogicalResourceId,
Data: 'An error occurred while retrieving the key policy',
};
}

const keyPolicy = JSON.parse(getKeyPolicyCommandResponse?.Policy);

// Update the existing key policy to allow the cloudfront distribution to use the key
keyPolicy.Statement.push({
Sid: 'Grant-CloudFront-Distribution-Key-Usage',
Effect: 'Allow',
Principal: {
Service: 'cloudfront.amazonaws.com',
},
Action: [
'kms:Decrypt',
'kms:Encrypt',
'kms:GenerateDataKey*',
'kms:ReEncrypt*'
],
Resource: `arn:aws:kms:${region}:${accountId}:key/${kmsKeyId}`,
Condition: {
StringEquals: {
'AWS:SourceArn': `arn:aws:cloudfront::${accountId}:distribution/${cloudFrontDistributionId}`
}
}
});

await kmsClient.send(new PutKeyPolicyCommand({
KeyId: kmsKeyId,
Policy: JSON.stringify(keyPolicy),
PolicyName: 'default'
}));
} catch (err) {
status = 'FAILED';
responseData = {
Error: JSON.stringify(err)
};
}
}

return {
Status: status,
Reason: JSON.stringify(responseData),
PhysicalResourceId: e.PhysicalResourceId ?? context.logStreamName,
StackId: e.StackId,
RequestId: e.RequestId,
LogicalResourceId: e.LogicalResourceId,
Data: responseData,
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"name": "kms-key-policy-updater",
"version": "0.0.0",
"description": "Custom resource for updating CMK key policies",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"author": {
"name": "Amazon Web Services",
"url": "https://aws.amazon.com",
"organization": true
},
"license": "Apache-2.0",
"scripts": {
"build": "tsc -b .",
"lint": "eslint -c ../eslintrc.yml --ext=.js,.ts . && tslint --project .",
"lint-fix": "eslint -c ../eslintrc.yml --ext=.js,.ts --fix .",
"test": "jest --coverage",
"clean": "tsc -b --clean",
"watch": "tsc -b -w"
},
"dependencies": {
"@aws-sdk/client-kms": "^3.468.0",
"aws-sdk-client-mock": "^3.0.0"
},
"devDependencies": {
"@types/jest": "^27.4.0",
"@types/node": "^10.3.0"
},
"jest": {
"moduleFileExtensions": [
"js"
],
"coverageReporters": [
"text",
[
"lcov",
{
"projectRoot": "../../../../"
}
]
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance
* with the License. A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES
* OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions
* and limitations under the License.
*/

import { handler } from "..";
import { mockClient } from "aws-sdk-client-mock";
import { KMSClient, DescribeKeyCommand, KeyManagerType, GetKeyPolicyCommand, PutKeyPolicyCommand } from "@aws-sdk/client-kms";

const kmsMock = mockClient(KMSClient);

beforeEach(() => {
kmsMock.reset();
});

it('Should exit if an AWS managed key is provided; return a success code but give the exact reason', async () => {
// Mocks
kmsMock.on(DescribeKeyCommand).resolves({
KeyMetadata: {
KeyId: 'sample-key-id',
KeyManager: KeyManagerType.AWS
}
});
// Arrange
const e = {
RequestType: 'Create',
ResourceProperties: {
CloudFrontDistributionId: 'sample-cf-distro-id',
AccountId: '111122223333'
}
};
const context = {
// ...
};
// Act
const res = await handler(e, context);
// Assert
expect(res.Status).toBe('SUCCESS');
expect(res.Data).toBe('An AWS managed key was provided, no action needed from the custom resource, exiting now.');
});

it('Should return an error if the key policy is returned as undefined', async () => {
// Mocks
kmsMock.on(DescribeKeyCommand).resolves({
KeyMetadata: {
KeyId: 'sample-key-id',
KeyManager: KeyManagerType.CUSTOMER
}
});
kmsMock.on(GetKeyPolicyCommand).resolves({
Policy: undefined
});
// Arrange
const e = {
RequestType: 'Update',
ResourceProperties: {
CloudFrontDistributionId: 'sample-cf-distro-id',
AccountId: '111122223333'
}
};
const context = {
// ...
};
// Act
const res = await handler(e, context);
// Assert
expect(res.Status).toBe('FAILED');
});

it('Should update the key policy if the proper params are given', async () => {
// Mocks
kmsMock.on(DescribeKeyCommand).resolves({
KeyMetadata: {
KeyId: 'sample-key-id',
KeyManager: KeyManagerType.CUSTOMER
}
});
kmsMock.on(GetKeyPolicyCommand).resolves({
Policy: `{\n
\"Version\" : \"2012-10-17\",\n
\"Id\" : \"key-default-1\",\n
\"Statement\" : [ {\n
\"Sid\" : \"Enable IAM User Permissions\",\n
\"Effect\" : \"Allow\",\n
\"Principal\" : {\n
\"AWS\" : \"arn:aws:iam::111122223333:root\"\n
},\n
\"Action\" : \"kms:*\",\n
\"Resource\" : \"*\"\n
} ]\n
}`
});
// Arrange
const e = {
RequestType: 'Update',
ResourceProperties: {
CloudFrontDistributionId: 'sample-cf-distro-id',
AccountId: '111122223333'
}
};
const context = {
// ...
};
// Act
const res = await handler(e, context);
// Assert
expect(res.Status).toBe('SUCCESS');
});

it('Should fail if an error occurs with putting the new key policy, all other inputs valid', async () => {
// Mocks
kmsMock.on(DescribeKeyCommand).resolves({
KeyMetadata: {
KeyId: 'sample-key-id',
KeyManager: KeyManagerType.CUSTOMER
}
});
kmsMock.on(GetKeyPolicyCommand).resolves({
Policy: `{\n
\"Version\" : \"2012-10-17\",\n
\"Id\" : \"key-default-1\",\n
\"Statement\" : [ {\n
\"Sid\" : \"Enable IAM User Permissions\",\n
\"Effect\" : \"Allow\",\n
\"Principal\" : {\n
\"AWS\" : \"arn:aws:iam::111122223333:root\"\n
},\n
\"Action\" : \"kms:*\",\n
\"Resource\" : \"*\"\n
} ]\n
}`
});
kmsMock.on(PutKeyPolicyCommand).rejects();
const e = {
RequestType: 'Update',
ResourceProperties: {
CloudFrontDistributionId: 'sample-cf-distro-id',
AccountId: '111122223333'
}
};
const context = {
// ...
};
// Act
const res = await handler(e, context);
// Assert
expect(res.Status).toBe('FAILED');
});
Loading