From 003f70a4651b35fcbd091523219595b73ab888b9 Mon Sep 17 00:00:00 2001 From: Henry Goffin Date: Wed, 29 Mar 2023 02:55:53 +0000 Subject: [PATCH] implement cloudfront.OriginAccessControl construct --- .../aws-cloudfront-origins/lib/s3-origin.ts | 101 ++++- .../test/s3-origin.test.ts | 119 ++++++ .../aws-cloudfront/lib/distribution.ts | 13 + packages/@aws-cdk/aws-cloudfront/lib/index.ts | 1 + .../lib/origin-access-control.ts | 226 ++++++++++ .../@aws-cdk/aws-cloudfront/lib/origin.ts | 13 + .../aws-cloudfront/lib/web-distribution.ts | 32 +- ...efaultTestDeployAssertEF946D96.assets.json | 19 + ...aultTestDeployAssertEF946D96.template.json | 36 ++ .../cdk.out | 1 + .../integ-cloudfront-s3-oac-l2.assets.json | 19 + .../integ-cloudfront-s3-oac-l2.template.json | 218 ++++++++++ .../integ.json | 12 + .../manifest.json | 141 +++++++ .../tree.json | 388 ++++++++++++++++++ .../test/integ.cloudfront-s3-oac-l2.ts | 49 +++ ...efaultTestDeployAssertFF6C26DC.assets.json | 19 + ...aultTestDeployAssertFF6C26DC.template.json | 36 ++ .../cdk.out | 1 + .../integ-cloudfront-s3-oac.assets.json | 19 + .../integ-cloudfront-s3-oac.template.json | 105 +++++ .../integ.json | 12 + .../manifest.json | 123 ++++++ .../tree.json | 219 ++++++++++ .../test/integ.cloudfront-s3-oac.ts | 40 ++ .../@aws-cdk/aws-cloudfront/test/oac.test.ts | 166 ++++++++ .../test/web-distribution.test.ts | 25 +- 27 files changed, 2144 insertions(+), 9 deletions(-) create mode 100644 packages/@aws-cdk/aws-cloudfront/lib/origin-access-control.ts create mode 100644 packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac-l2.js.snapshot/S3OriginAccessControlL2DefaultTestDeployAssertEF946D96.assets.json create mode 100644 packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac-l2.js.snapshot/S3OriginAccessControlL2DefaultTestDeployAssertEF946D96.template.json create mode 100644 packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac-l2.js.snapshot/cdk.out create mode 100644 packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac-l2.js.snapshot/integ-cloudfront-s3-oac-l2.assets.json create mode 100644 packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac-l2.js.snapshot/integ-cloudfront-s3-oac-l2.template.json create mode 100644 packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac-l2.js.snapshot/integ.json create mode 100644 packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac-l2.js.snapshot/manifest.json create mode 100644 packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac-l2.js.snapshot/tree.json create mode 100644 packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac-l2.ts create mode 100644 packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac.js.snapshot/S3OriginAccessControlDefaultTestDeployAssertFF6C26DC.assets.json create mode 100644 packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac.js.snapshot/S3OriginAccessControlDefaultTestDeployAssertFF6C26DC.template.json create mode 100644 packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac.js.snapshot/cdk.out create mode 100644 packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac.js.snapshot/integ-cloudfront-s3-oac.assets.json create mode 100644 packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac.js.snapshot/integ-cloudfront-s3-oac.template.json create mode 100644 packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac.js.snapshot/integ.json create mode 100644 packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac.js.snapshot/manifest.json create mode 100644 packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac.js.snapshot/tree.json create mode 100644 packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac.ts create mode 100644 packages/@aws-cdk/aws-cloudfront/test/oac.test.ts diff --git a/packages/@aws-cdk/aws-cloudfront-origins/lib/s3-origin.ts b/packages/@aws-cdk/aws-cloudfront-origins/lib/s3-origin.ts index d2003c9285571..6bbb72901f120 100644 --- a/packages/@aws-cdk/aws-cloudfront-origins/lib/s3-origin.ts +++ b/packages/@aws-cdk/aws-cloudfront-origins/lib/s3-origin.ts @@ -2,6 +2,7 @@ import * as cloudfront from '@aws-cdk/aws-cloudfront'; import * as iam from '@aws-cdk/aws-iam'; import * as s3 from '@aws-cdk/aws-s3'; import * as cdk from '@aws-cdk/core'; +import { RemovalPolicy } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { HttpOrigin } from './http-origin'; @@ -9,10 +10,26 @@ import { HttpOrigin } from './http-origin'; * Properties to use to customize an S3 Origin. */ export interface S3OriginProps extends cloudfront.OriginProps { + /** + * When set to `true`, S3Origin will automatically configure IAM policies on the target S3 bucket + * and any attached SSE-KMS key policy to permit read-only access by the CloudFront distribution. + * In addition, if the `originAccessControl` property is not specified, a default Origin Access + * Control will be attached to the CloudFront distribution. + * + * This property is incompatible with the legacy `originAccessIdentity` property. + * + * @default false + */ + readonly autoGrantOACPermissions?: boolean; + /** * An optional Origin Access Identity of the origin identity cloudfront will use when calling your s3 bucket. + * This is a legacy feature and in most cases, you should prefer to use Origin Access Control properties such + * as `originAccessControl` and `autoGrantOACPermissions` instead. + * + * @see https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html * - * @default - An Origin Access Identity will be created. + * @default - An Origin Access Identity will be created, unless `originAccessControl` or `applyOACBucketPolicy` is specified */ readonly originAccessIdentity?: cloudfront.IOriginAccessIdentity; } @@ -28,6 +45,9 @@ export class S3Origin implements cloudfront.IOrigin { private readonly origin: cloudfront.IOrigin; constructor(bucket: s3.IBucket, props: S3OriginProps = {}) { + if (bucket.isWebsite && props.originAccessControl) { + throw new Error('Origin Access Control does not apply to S3 buckets configured for website hosting'); + } this.origin = bucket.isWebsite ? new HttpOrigin(bucket.bucketWebsiteDomainName, { protocolPolicy: cloudfront.OriginProtocolPolicy.HTTP_ONLY, // S3 only supports HTTP for website buckets @@ -41,22 +61,86 @@ export class S3Origin implements cloudfront.IOrigin { } } +/** + * An origin-internal IOriginAccessControl wrapper which allows the creation of + * the OriginAccessControl construct to be deferred until bind() is called on the + * origin, which provides the necessary scope for the construct. + */ +class LateBindOAC implements cloudfront.IOriginAccessControl { + public bound?: cloudfront.IOriginAccessControl; + get env() { return this.bound!.env; } + get node() { return this.bound!.node; } + get stack() { return this.bound!.stack; } + get originAccessControlId() { return this.bound!.originAccessControlId; } + applyRemovalPolicy(policy: RemovalPolicy) { return this.bound!.applyRemovalPolicy(policy); } +} + /** * An Origin specific to a S3 bucket (not configured for website hosting). * * Contains additional logic around bucket permissions and origin access identities. */ class S3BucketOrigin extends cloudfront.OriginBase { - private originAccessIdentity!: cloudfront.IOriginAccessIdentity; - constructor(private readonly bucket: s3.IBucket, { originAccessIdentity, ...props }: S3OriginProps) { - super(bucket.bucketRegionalDomainName, props); - if (originAccessIdentity) { - this.originAccessIdentity = originAccessIdentity; + private static preprocessProps(props: S3OriginProps): S3OriginProps { + if (props.autoGrantOACPermissions && !props.originAccessControl) { + return { originAccessControl: new LateBindOAC(), ...props }; + } + return props; + } + + private originAccessIdentity?: cloudfront.IOriginAccessIdentity; + private readonly autoGrantOACPermissions: boolean; + private lateBindAutoCreateOAC?: LateBindOAC; + + constructor(private readonly bucket: s3.IBucket, props: S3OriginProps) { + super(bucket.bucketRegionalDomainName, S3BucketOrigin.preprocessProps(props)); + + this.originAccessIdentity = props.originAccessIdentity; + this.autoGrantOACPermissions = !!props.autoGrantOACPermissions; + + // preprocessProps may have created a LateBindOAC that we need to process in bind() + if (!props.originAccessControl && this.originAccessControl instanceof LateBindOAC) { + this.lateBindAutoCreateOAC = this.originAccessControl as LateBindOAC; + } + + if (this.originAccessControl && this.originAccessIdentity) { + throw new Error('Origin Access Control cannot be combined with Origin Access Identity'); } } public bind(scope: Construct, options: cloudfront.OriginBindOptions): cloudfront.OriginBindConfig { + if (this.originAccessControl) { + if (this.lateBindAutoCreateOAC) { + this.lateBindAutoCreateOAC.bound = cloudfront.OriginAccessControl.fromS3Defaults(scope); + } + if (this.autoGrantOACPermissions) { + //if (cdk.Stack.of(this.bucket) !== cdk.Stack.of(scope)) { + // throw new Error('autoGrantOACPermissions cannot be used on buckets from other stacks'); + //} + const dist = scope.node.scope as cloudfront.Distribution; + const lazyDistArn = cdk.Lazy.string({ produce: () => dist.distributionArn }); + const added = this.bucket.addToResourcePolicy(new iam.PolicyStatement({ + principals: [new iam.ServicePrincipal('cloudfront.amazonaws.com')], + actions: ['s3:GetObject'], + resources: [this.bucket.arnForObjects('*')], + conditions: { StringEquals: { 'aws:SourceArn': lazyDistArn } }, + })); + if (!added.statementAdded) { + throw new Error('autoGrantOACPermissions cannot be used on imported buckets or buckets with non-CDK policies'); + } + if (this.bucket.encryptionKey) { + this.bucket.encryptionKey.addToResourcePolicy(new iam.PolicyStatement({ + principals: [new iam.ServicePrincipal('cloudfront.amazonaws.com')], + actions: ['kms:Decrypt'], + resources: ['*'], + conditions: { StringEquals: { 'aws:SourceArn': lazyDistArn } }, + }), false /*do not allow failures, throw error instead*/); + } + } + return super.bind(scope, options); + } + if (!this.originAccessIdentity) { // Using a bucket from another stack creates a cyclic reference with // the bucket taking a dependency on the generated S3CanonicalUserId for the grant principal, @@ -84,6 +168,9 @@ class S3BucketOrigin extends cloudfront.OriginBase { } protected renderS3OriginConfig(): cloudfront.CfnDistribution.S3OriginConfigProperty | undefined { - return { originAccessIdentity: `origin-access-identity/cloudfront/${this.originAccessIdentity.originAccessIdentityId}` }; + if (this.originAccessControl) { + return { }; + } + return { originAccessIdentity: `origin-access-identity/cloudfront/${this.originAccessIdentity!.originAccessIdentityId}` }; } } diff --git a/packages/@aws-cdk/aws-cloudfront-origins/test/s3-origin.test.ts b/packages/@aws-cdk/aws-cloudfront-origins/test/s3-origin.test.ts index 7889659db5420..9e135d39f25d6 100644 --- a/packages/@aws-cdk/aws-cloudfront-origins/test/s3-origin.test.ts +++ b/packages/@aws-cdk/aws-cloudfront-origins/test/s3-origin.test.ts @@ -153,6 +153,125 @@ describe('With bucket', () => { }); }); + test('can use OriginAccessCotrol', () => { + const bucket = new s3.Bucket(stack, 'Bucket'); + + const oac = cloudfront.OriginAccessControl.fromS3Defaults(stack); + const origin = new S3Origin(bucket, { originAccessControl: oac }); + new cloudfront.Distribution(stack, 'Dist', { defaultBehavior: { origin } }); + + const oacSingletonRef = 'OriginAccessControlACB7EFE0CA7DB170D0C7D8E8DC4943CFAFE70B28'; + + const tmpl = Template.fromStack(stack); + tmpl.hasResourceProperties('AWS::CloudFront::OriginAccessControl', { + OriginAccessControlConfig: { + OriginAccessControlOriginType: 's3', + SigningBehavior: 'always', + SigningProtocol: 'sigv4', + }, + }); + tmpl.hasResourceProperties('AWS::CloudFront::Distribution', { + DistributionConfig: { + Origins: [{ + OriginAccessControlId: Match.exact({ Ref: oacSingletonRef }), + S3OriginConfig: Match.exact({}), + }], + }, + }); + }); + + test('can use OriginAccessCotrol with automatic permissions', () => { + const bucket = new s3.Bucket(stack, 'Bucket'); + const origin = new S3Origin(bucket, { autoGrantOACPermissions: true }); + new cloudfront.Distribution(stack, 'Dist', { defaultBehavior: { origin } }); + + const oacSingletonRef = 'OriginAccessControlACB7EFE0CA7DB170D0C7D8E8DC4943CFAFE70B28'; + + const tmpl = Template.fromStack(stack); + tmpl.hasResourceProperties('AWS::CloudFront::OriginAccessControl', { + OriginAccessControlConfig: { + OriginAccessControlOriginType: 's3', + SigningBehavior: 'always', + SigningProtocol: 'sigv4', + }, + }); + tmpl.hasResourceProperties('AWS::CloudFront::Distribution', { + DistributionConfig: { + Origins: [{ + DomainName: Match.exact({ 'Fn::GetAtt': ['Bucket83908E77', 'RegionalDomainName'] }), + OriginAccessControlId: Match.exact({ Ref: oacSingletonRef }), + S3OriginConfig: Match.exact({}), + }], + }, + }); + tmpl.hasResourceProperties('AWS::S3::BucketPolicy', { + PolicyDocument: { + Statement: [{ + Action: 's3:GetObject', + Effect: 'Allow', + Principal: Match.exact({ Service: 'cloudfront.amazonaws.com' }), + Resource: Match.exact({ 'Fn::Join': ['', [Match.anyValue(), '/*']] }), + Condition: Match.exact({ StringEquals: { 'aws:SourceArn': Match.anyValue() } }), + }], + }, + }); + }); + + false && test('can use OriginAccessCotrol with KMS and automatic permissions', () => { + + // XXX circular dependency! Distribution requires Bucket, Bucket requires Key, + // Key requires Distribution (because key policy can't be set after creation). + // We can crack this by forcing the Distribution to create with enabled=false, + // then use Lambda to adjust Key policy and optionally enable the Distribution. + // Distribution doesn't allow the explicit specification of a distribution ID. + + const bucket = new s3.Bucket(stack, 'Bucket', { encryption: s3.BucketEncryption.KMS }); + const origin = new S3Origin(bucket, { autoGrantOACPermissions: true }); + new cloudfront.Distribution(stack, 'Dist', { defaultBehavior: { origin } }); + + const oacSingletonRef = 'OriginAccessControlACB7EFE0CA7DB170D0C7D8E8DC4943CFAFE70B28'; + + const tmpl = Template.fromStack(stack); + tmpl.hasResourceProperties('AWS::CloudFront::OriginAccessControl', { + OriginAccessControlConfig: { + OriginAccessControlOriginType: 's3', + SigningBehavior: 'always', + SigningProtocol: 'sigv4', + }, + }); + tmpl.hasResourceProperties('AWS::CloudFront::Distribution', { + DistributionConfig: { + Origins: [{ + DomainName: Match.exact({ 'Fn::GetAtt': ['Bucket83908E77', 'RegionalDomainName'] }), + OriginAccessControlId: Match.exact({ Ref: oacSingletonRef }), + S3OriginConfig: Match.exact({}), + }], + }, + }); + tmpl.hasResourceProperties('AWS::S3::BucketPolicy', { + PolicyDocument: { + Statement: [{ + Action: 's3:GetObject', + Effect: 'Allow', + Principal: Match.exact({ Service: 'cloudfront.amazonaws.com' }), + Resource: Match.exact({ 'Fn::Join': ['', [Match.anyValue(), '/*']] }), + Condition: Match.exact({ StringEquals: { 'aws:SourceArn': Match.anyValue() } }), + }], + }, + }); + tmpl.hasResourceProperties('AWS::KMS::KeyPolicy', { + PolicyDocument: { + Statement: [{ + Action: 'kms:Decrypt', + Effect: 'Allow', + Principal: Match.exact({ Service: 'cloudfront.amazonaws.com' }), + Resource: '*', + Condition: Match.exact({ StringEquals: { 'aws:SourceArn': Match.anyValue() } }), + }], + }, + }); + }); + test('Can set a custom originId', () => { const bucket = new s3.Bucket(stack, 'Bucket'); const bucket2 = new s3.Bucket(stack, 'Bucket2'); diff --git a/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts b/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts index 790173451dbaf..e971744729f73 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts @@ -42,6 +42,15 @@ export interface IDistribution extends IResource { */ readonly distributionId: string; + /** + * The ARN which uniquely identifies this distribution. When Origin Access Control + * is configured, this is the `aws:SourceArn` condition value used when evaluating + * IAM policy checks for origin resources. + * + * @attribute + */ + readonly distributionArn: string; + /** * Adds an IAM policy statement associated with this distribution to an IAM * principal's policy. @@ -268,12 +277,14 @@ export class Distribution extends Resource implements IDistribution { public readonly domainName: string; public readonly distributionDomainName: string; public readonly distributionId: string; + public readonly distributionArn: string; constructor() { super(scope, id); this.domainName = attrs.domainName; this.distributionDomainName = attrs.domainName; this.distributionId = attrs.distributionId; + this.distributionArn = formatDistributionArn(this); } public grant(grantee: iam.IGrantable, ...actions: string[]): iam.Grant { @@ -288,6 +299,7 @@ export class Distribution extends Resource implements IDistribution { public readonly domainName: string; public readonly distributionDomainName: string; public readonly distributionId: string; + public readonly distributionArn: string; private readonly defaultBehavior: CacheBehavior; private readonly additionalBehaviors: CacheBehavior[] = []; @@ -353,6 +365,7 @@ export class Distribution extends Resource implements IDistribution { this.domainName = distribution.attrDomainName; this.distributionDomainName = distribution.attrDomainName; this.distributionId = distribution.ref; + this.distributionArn = formatDistributionArn(this); } /** diff --git a/packages/@aws-cdk/aws-cloudfront/lib/index.ts b/packages/@aws-cdk/aws-cloudfront/lib/index.ts index e5abe99c0bfbd..99ca65ec76a9c 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/index.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/index.ts @@ -4,6 +4,7 @@ export * from './function'; export * from './geo-restriction'; export * from './key-group'; export * from './origin'; +export * from './origin-access-control'; export * from './origin-access-identity'; export * from './origin-request-policy'; export * from './public-key'; diff --git a/packages/@aws-cdk/aws-cloudfront/lib/origin-access-control.ts b/packages/@aws-cdk/aws-cloudfront/lib/origin-access-control.ts new file mode 100644 index 0000000000000..e8e2842d1bf34 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/lib/origin-access-control.ts @@ -0,0 +1,226 @@ +import * as cdk from '@aws-cdk/core'; +import { md5hash } from '@aws-cdk/core/lib/helpers-internal'; +import { Construct } from 'constructs'; +import { CfnOriginAccessControl } from './cloudfront.generated'; + +/** + * AWS service for which CloudFront Origin Access Control should sign requests + */ +export enum OriginAccessControlOriginType { + /** + * AWS Elemental MediaStore + */ + MEDIASTORE = 'mediastore', + /** + * Amazon S3 + */ + S3 = 's3', +} + +/** + * Configuration of when CloudFront Origin Access Control will sign requests + * + * @see https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html#oac-advanced-settings + */ +export enum OriginAccessControlSigningBehavior { + /** + * Always sign requests, replacing any existing `Authorization` header + */ + ALWAYS = 'always', + /** + * Never sign requests, effectively disabling Origin Access Control + */ + NEVER = 'never', + /** + * Sign requests only if an `Authorization` header is not already set + * + * *WARNING: You must add the `Authorization` header to the [cache key](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/controlling-the-cache-key.html) policy for all associated origins.* + */ + NO_OVERRIDE = 'no-override', +} + +/** + * Signature protocol used by CloudFront Origin Access Control to sign requests + */ +export enum OriginAccessControlSigningProtocol { + /** + * SIGV4 is the only supported signature type at this time + */ + SIGV4 = 'sigv4', +} + +/** + * Properties of CloudFront Origin Access Control + */ +export interface OriginAccessControlProps { + /** + * A name which uniquely identifies this Origin Access Control within your account + * + * @default - A unique name will be generated from the construct ID + */ + readonly originAccessControlName?: string; + + /** + * An optional description of this Origin Access Control + * + * @default - No description + */ + readonly description?: string; + + /** + * AWS service for which CloudFront Origin Access Control should sign requests + */ + readonly originType: OriginAccessControlOriginType; + + /** + * Configuration of when CloudFront Origin Access Control will sign requests + * + * @default OriginAccessControlSigningBehavior.ALWAYS + */ + readonly signingBehavior?: OriginAccessControlSigningBehavior; + + /** + * Signature protocol used by CloudFront Origin Access Control to sign requests + * + * @default OriginAccessControlSigningProtocol.SIGV4 + */ + readonly signingProtocol?: OriginAccessControlSigningProtocol; +} + +/** + * Interface for CloudFront Origin Access Control + */ +export interface IOriginAccessControl extends cdk.IResource { + /** + * The Origin Access Control ID (Physical ID in CloudFormation) + * + * @attribute + */ + readonly originAccessControlId: string; +} + +/** + * An Origin Access Control is a set of parameters used to control how CloudFront + * distributions authenticate requests to the origin. This functionality replaces + * the older Origin Access Identity authentication feature. + * + * @see https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-origin.html + * + * @resource AWS::CloudFront::OriginAccessControl + */ +export class OriginAccessControl extends cdk.Resource implements IOriginAccessControl { + /** + * Imports an existing Origin Access Control by ID + */ + public static fromOriginAccessControlId( + scope: Construct, + id: string, + originAccessControlId: string): IOriginAccessControl { + + class Import extends cdk.Resource implements IOriginAccessControl { + public readonly originAccessControlId: string; + constructor(s: Construct, i: string, oacId: string) { + super(s, i, { physicalName: oacId }); + this.originAccessControlId = oacId; + } + } + + return new Import(scope, id, originAccessControlId); + } + + /** + * Creates an Origin Access Control which signs Amazon S3 requests. If no + * logical construct ID is specified, this will create and manage a singleton + * Origin Access Control within the stack which has suitable default values + * for accessing S3 buckets. + */ + public static fromS3Defaults( + scope: Construct, + id?: string, + behaviorOverride?: OriginAccessControlSigningBehavior): IOriginAccessControl { + + const props = { + originType: OriginAccessControlOriginType.S3, + signingBehavior: behaviorOverride, + }; + if (id === undefined) { + return OriginAccessControl.fromSingleton(cdk.Stack.of(scope), props); + } else { + return new OriginAccessControl(scope, id, props); + } + } + + /** + * Creates an Origin Access Control which signs MediaStore requests. If no + * logical construct ID is specified, this will create and manage a singleton + * Origin Access Control within the stack which has suitable default values + * for accessing MediaStore origins. + */ + public static fromMediaStoreDefaults( + scope: Construct, + id?: string, + behaviorOverride?: OriginAccessControlSigningBehavior): IOriginAccessControl { + + const props = { + originType: OriginAccessControlOriginType.MEDIASTORE, + signingBehavior: behaviorOverride, + }; + if (id === undefined) { + return OriginAccessControl.fromSingleton(cdk.Stack.of(scope), props); + } else { + return new OriginAccessControl(scope, id, props); + } + } + + /** + * Creates or returns an existing Origin Access Control in the target stack + * which matches the given properties. The CDK construct id and CloudFormation + * physical id are both derived from a hash of the properties. + */ + public static fromSingleton(stack: cdk.Stack, props: OriginAccessControlProps) { + props = OriginAccessControl.assignDefaultValues(props); + const hash = md5hash(`OACStackSingleton-${props.originType}-${props.signingProtocol}-${props.signingBehavior}`); + const id = `OriginAccessControl${hash.toUpperCase()}`; + const existing = stack.node.tryFindChild(id); + if (existing) { + return existing as OriginAccessControl; + } + return new OriginAccessControl(stack, id, props); + } + + private static assignDefaultValues(props: OriginAccessControlProps): OriginAccessControlProps { + const ret = { ...props }; + ret.signingBehavior = ret.signingBehavior ?? OriginAccessControlSigningBehavior.ALWAYS; + ret.signingProtocol = ret.signingProtocol ?? OriginAccessControlSigningProtocol.SIGV4; + return ret; + } + + /** + * The Origin Access Control Id + * + * @attribute + */ + public readonly originAccessControlId: string; + + /** + * CDK L1 resource + */ + private readonly resource: CfnOriginAccessControl; + + constructor(scope: Construct, id: string, props: OriginAccessControlProps) { + super(scope, id, { physicalName: props.originAccessControlName }); + + props = OriginAccessControl.assignDefaultValues(props); + + this.resource = new CfnOriginAccessControl(this, 'Resource', { + originAccessControlConfig: { + name: props.originAccessControlName ?? cdk.Names.uniqueResourceName(this, { maxLength: 64 }), + description: props.description, + originAccessControlOriginType: props.originType, + signingBehavior: props.signingBehavior!, + signingProtocol: props.signingProtocol!, + }, + }); + this.originAccessControlId = this.getResourceNameAttribute(this.resource.ref); + } +} diff --git a/packages/@aws-cdk/aws-cloudfront/lib/origin.ts b/packages/@aws-cdk/aws-cloudfront/lib/origin.ts index 9f7d8c7f852ea..ca807e919d81d 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/origin.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/origin.ts @@ -1,6 +1,7 @@ import { Duration, Token } from '@aws-cdk/core'; import { Construct } from 'constructs'; import { CfnDistribution } from './cloudfront.generated'; +import { IOriginAccessControl } from './origin-access-control'; /** * The failover configuration used for Origin Groups, @@ -73,6 +74,15 @@ export interface OriginOptions { */ readonly customHeaders?: Record; + /** + * An Origin Access Control that specifies how CloudFront requests to the origin should be signed. + * + * @see https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html + * + * @default - Origin Access Control is not configured + */ + readonly originAccessControl?: IOriginAccessControl; + /** * When you enable Origin Shield in the AWS Region that has the lowest latency to your origin, you can get better network performance * @@ -135,6 +145,7 @@ export abstract class OriginBase implements IOrigin { private readonly originShieldRegion?: string; private readonly originShieldEnabled: boolean; private readonly originId?: string; + protected readonly originAccessControl?: IOriginAccessControl; protected constructor(domainName: string, props: OriginProps = {}) { validateIntInRangeOrUndefined('connectionTimeout', 1, 10, props.connectionTimeout?.toSeconds()); @@ -149,6 +160,7 @@ export abstract class OriginBase implements IOrigin { this.originShieldRegion = props.originShieldRegion; this.originId = props.originId; this.originShieldEnabled = props.originShieldEnabled ?? true; + this.originAccessControl = props.originAccessControl; } /** @@ -170,6 +182,7 @@ export abstract class OriginBase implements IOrigin { connectionAttempts: this.connectionAttempts, connectionTimeout: this.connectionTimeout?.toSeconds(), originCustomHeaders: this.renderCustomHeaders(), + originAccessControlId: this.originAccessControl?.originAccessControlId, s3OriginConfig, customOriginConfig, originShield: this.renderOriginShield(this.originShieldEnabled, this.originShieldRegion), diff --git a/packages/@aws-cdk/aws-cloudfront/lib/web-distribution.ts b/packages/@aws-cdk/aws-cloudfront/lib/web-distribution.ts index ca08aa477f018..3ac76aaf0821a 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/web-distribution.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/web-distribution.ts @@ -9,6 +9,7 @@ import { HttpVersion, IDistribution, LambdaEdgeEventType, OriginProtocolPolicy, import { FunctionAssociation } from './function'; import { GeoRestriction } from './geo-restriction'; import { IKeyGroup } from './key-group'; +import { IOriginAccessControl } from './origin-access-control'; import { IOriginAccessIdentity } from './origin-access-identity'; import { formatDistributionArn } from './private/utils'; @@ -304,10 +305,24 @@ export interface S3OriginConfig { */ readonly s3BucketSource: s3.IBucket; + /** + * The optional Origin Access Control that CloudFront will use to sign origin requests for your S3 bucket. + * This replaces the older Origin Access Identity authentication mechanism. You cannot specify both this + * property and also `originAccessIdentity`. + * + * @see https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html + * + * @default - No Origin Access Control, falling back to Origin Access Identity configuration + */ + readonly originAccessControl?: IOriginAccessControl; + /** * The optional Origin Access Identity of the origin identity cloudfront will use when calling your s3 bucket. + * You should prefer `originAccessControl` for all new use cases, this exists for backwards compatibility. + * + * @see https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html * - * @default No Origin Access Identity which requires the S3 bucket to be public accessible + * @default - No Origin Access Identity which requires the S3 bucket to be public accessible */ readonly originAccessIdentity?: IOriginAccessIdentity; @@ -752,12 +767,14 @@ export class CloudFrontWebDistribution extends cdk.Resource implements IDistribu public readonly domainName: string; public readonly distributionDomainName: string; public readonly distributionId: string; + public readonly distributionArn: string; constructor() { super(scope, id); this.domainName = attrs.domainName; this.distributionDomainName = attrs.domainName; this.distributionId = attrs.distributionId; + this.distributionArn = formatDistributionArn(this); } public grant(grantee: iam.IGrantable, ...actions: string[]): iam.Grant { @@ -796,6 +813,11 @@ export class CloudFrontWebDistribution extends cdk.Resource implements IDistribu */ public readonly distributionId: string; + /** + * The distribution ARN for this distribution. + */ + public readonly distributionArn: string; + /** * Maps our methods to the string arrays they are */ @@ -989,6 +1011,7 @@ export class CloudFrontWebDistribution extends cdk.Resource implements IDistribu this.domainName = distribution.attrDomainName; this.distributionDomainName = distribution.attrDomainName; this.distributionId = distribution.ref; + this.distributionArn = formatDistributionArn(this); } /** @@ -1120,6 +1143,12 @@ export class CloudFrontWebDistribution extends cdk.Resource implements IDistribu if (originConfig.s3OriginSource) { // first case for backwards compatibility if (originConfig.s3OriginSource.originAccessIdentity) { + if (originConfig.s3OriginSource.originAccessControl) { + throw new Error( + 'There cannot be both originAccessControl and originAccessIdentity on the same s3OriginSource', + ); + } + // grant CloudFront OriginAccessIdentity read access to S3 bucket // Used rather than `grantRead` because `grantRead` will grant overly-permissive policies. // Only GetObject is needed to retrieve objects for the distribution. @@ -1158,6 +1187,7 @@ export class CloudFrontWebDistribution extends cdk.Resource implements IDistribu originCustomHeaders: originHeaders.length > 0 ? originHeaders : undefined, s3OriginConfig, + originAccessControlId: originConfig.s3OriginSource?.originAccessControl?.originAccessControlId, originShield: this.toOriginShieldProperty(originConfig), customOriginConfig: originConfig.customOriginSource ? { diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac-l2.js.snapshot/S3OriginAccessControlL2DefaultTestDeployAssertEF946D96.assets.json b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac-l2.js.snapshot/S3OriginAccessControlL2DefaultTestDeployAssertEF946D96.assets.json new file mode 100644 index 0000000000000..a431cea19013e --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac-l2.js.snapshot/S3OriginAccessControlL2DefaultTestDeployAssertEF946D96.assets.json @@ -0,0 +1,19 @@ +{ + "version": "31.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "S3OriginAccessControlL2DefaultTestDeployAssertEF946D96.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac-l2.js.snapshot/S3OriginAccessControlL2DefaultTestDeployAssertEF946D96.template.json b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac-l2.js.snapshot/S3OriginAccessControlL2DefaultTestDeployAssertEF946D96.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac-l2.js.snapshot/S3OriginAccessControlL2DefaultTestDeployAssertEF946D96.template.json @@ -0,0 +1,36 @@ +{ + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac-l2.js.snapshot/cdk.out b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac-l2.js.snapshot/cdk.out new file mode 100644 index 0000000000000..7925065efbcc4 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac-l2.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"31.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac-l2.js.snapshot/integ-cloudfront-s3-oac-l2.assets.json b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac-l2.js.snapshot/integ-cloudfront-s3-oac-l2.assets.json new file mode 100644 index 0000000000000..a0524cadc48aa --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac-l2.js.snapshot/integ-cloudfront-s3-oac-l2.assets.json @@ -0,0 +1,19 @@ +{ + "version": "31.0.0", + "files": { + "20078edfd7667744ee6ac099a0ef64f0fbb29b904e809322a43939a7c92deca0": { + "source": { + "path": "integ-cloudfront-s3-oac-l2.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "20078edfd7667744ee6ac099a0ef64f0fbb29b904e809322a43939a7c92deca0.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac-l2.js.snapshot/integ-cloudfront-s3-oac-l2.template.json b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac-l2.js.snapshot/integ-cloudfront-s3-oac-l2.template.json new file mode 100644 index 0000000000000..d697a79fca14b --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac-l2.js.snapshot/integ-cloudfront-s3-oac-l2.template.json @@ -0,0 +1,218 @@ +{ + "Resources": { + "Bucket83908E77": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "OACL2C7CC1987": { + "Type": "AWS::CloudFront::OriginAccessControl", + "Properties": { + "OriginAccessControlConfig": { + "Name": "integcloudfronts3oacl2OACL20729B516", + "OriginAccessControlOriginType": "s3", + "SigningBehavior": "always", + "SigningProtocol": "sigv4" + } + } + }, + "DistributionCFDistribution882A7313": { + "Type": "AWS::CloudFront::Distribution", + "Properties": { + "DistributionConfig": { + "DefaultCacheBehavior": { + "AllowedMethods": [ + "GET", + "HEAD" + ], + "CachedMethods": [ + "GET", + "HEAD" + ], + "Compress": true, + "ForwardedValues": { + "Cookies": { + "Forward": "none" + }, + "QueryString": false + }, + "TargetOriginId": "origin1", + "ViewerProtocolPolicy": "redirect-to-https" + }, + "DefaultRootObject": "index.html", + "Enabled": true, + "HttpVersion": "http2", + "IPV6Enabled": true, + "Origins": [ + { + "ConnectionAttempts": 3, + "ConnectionTimeout": 10, + "DomainName": { + "Fn::GetAtt": [ + "Bucket83908E77", + "RegionalDomainName" + ] + }, + "Id": "origin1", + "OriginAccessControlId": { + "Ref": "OACL2C7CC1987" + }, + "S3OriginConfig": {} + } + ], + "PriceClass": "PriceClass_100", + "ViewerCertificate": { + "CloudFrontDefaultCertificate": true + } + } + } + }, + "OriginAccessControlACB7EFE0CA7DB170D0C7D8E8DC4943CFAFE70B28": { + "Type": "AWS::CloudFront::OriginAccessControl", + "Properties": { + "OriginAccessControlConfig": { + "Name": "integcloudfronts3oacl2OriginEFE0CA7DB170D0C7D8E8DC4943CF506089EA", + "OriginAccessControlOriginType": "s3", + "SigningBehavior": "always", + "SigningProtocol": "sigv4" + } + } + }, + "Distribution2CFDistribution297A8304": { + "Type": "AWS::CloudFront::Distribution", + "Properties": { + "DistributionConfig": { + "DefaultCacheBehavior": { + "AllowedMethods": [ + "GET", + "HEAD" + ], + "CachedMethods": [ + "GET", + "HEAD" + ], + "Compress": true, + "ForwardedValues": { + "Cookies": { + "Forward": "none" + }, + "QueryString": false + }, + "TargetOriginId": "origin1", + "ViewerProtocolPolicy": "redirect-to-https" + }, + "DefaultRootObject": "index.html", + "Enabled": true, + "HttpVersion": "http2", + "IPV6Enabled": true, + "Origins": [ + { + "ConnectionAttempts": 3, + "ConnectionTimeout": 10, + "DomainName": { + "Fn::GetAtt": [ + "Bucket83908E77", + "RegionalDomainName" + ] + }, + "Id": "origin1", + "OriginAccessControlId": { + "Ref": "OriginAccessControlACB7EFE0CA7DB170D0C7D8E8DC4943CFAFE70B28" + }, + "S3OriginConfig": {} + } + ], + "PriceClass": "PriceClass_100", + "ViewerCertificate": { + "CloudFrontDefaultCertificate": true + } + } + } + }, + "Distribution3CFDistribution15BC87C4": { + "Type": "AWS::CloudFront::Distribution", + "Properties": { + "DistributionConfig": { + "DefaultCacheBehavior": { + "AllowedMethods": [ + "GET", + "HEAD" + ], + "CachedMethods": [ + "GET", + "HEAD" + ], + "Compress": true, + "ForwardedValues": { + "Cookies": { + "Forward": "none" + }, + "QueryString": false + }, + "TargetOriginId": "origin1", + "ViewerProtocolPolicy": "redirect-to-https" + }, + "DefaultRootObject": "index.html", + "Enabled": true, + "HttpVersion": "http2", + "IPV6Enabled": true, + "Origins": [ + { + "ConnectionAttempts": 3, + "ConnectionTimeout": 10, + "DomainName": { + "Fn::GetAtt": [ + "Bucket83908E77", + "RegionalDomainName" + ] + }, + "Id": "origin1", + "OriginAccessControlId": { + "Ref": "OriginAccessControlACB7EFE0CA7DB170D0C7D8E8DC4943CFAFE70B28" + }, + "S3OriginConfig": {} + } + ], + "PriceClass": "PriceClass_100", + "ViewerCertificate": { + "CloudFrontDefaultCertificate": true + } + } + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac-l2.js.snapshot/integ.json b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac-l2.js.snapshot/integ.json new file mode 100644 index 0000000000000..a072c6ae24b6f --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac-l2.js.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "31.0.0", + "testCases": { + "S3OriginAccessControlL2/DefaultTest": { + "stacks": [ + "integ-cloudfront-s3-oac-l2" + ], + "assertionStack": "S3OriginAccessControlL2/DefaultTest/DeployAssert", + "assertionStackName": "S3OriginAccessControlL2DefaultTestDeployAssertEF946D96" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac-l2.js.snapshot/manifest.json b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac-l2.js.snapshot/manifest.json new file mode 100644 index 0000000000000..509bee31bd12d --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac-l2.js.snapshot/manifest.json @@ -0,0 +1,141 @@ +{ + "version": "31.0.0", + "artifacts": { + "integ-cloudfront-s3-oac-l2.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "integ-cloudfront-s3-oac-l2.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "integ-cloudfront-s3-oac-l2": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "integ-cloudfront-s3-oac-l2.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/20078edfd7667744ee6ac099a0ef64f0fbb29b904e809322a43939a7c92deca0.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "integ-cloudfront-s3-oac-l2.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "integ-cloudfront-s3-oac-l2.assets" + ], + "metadata": { + "/integ-cloudfront-s3-oac-l2/Bucket/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Bucket83908E77" + } + ], + "/integ-cloudfront-s3-oac-l2/OACL2/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "OACL2C7CC1987" + } + ], + "/integ-cloudfront-s3-oac-l2/Distribution/CFDistribution": [ + { + "type": "aws:cdk:logicalId", + "data": "DistributionCFDistribution882A7313" + } + ], + "/integ-cloudfront-s3-oac-l2/OriginAccessControlACB7EFE0CA7DB170D0C7D8E8DC4943CF/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "OriginAccessControlACB7EFE0CA7DB170D0C7D8E8DC4943CFAFE70B28" + } + ], + "/integ-cloudfront-s3-oac-l2/Distribution2/CFDistribution": [ + { + "type": "aws:cdk:logicalId", + "data": "Distribution2CFDistribution297A8304" + } + ], + "/integ-cloudfront-s3-oac-l2/Distribution3/CFDistribution": [ + { + "type": "aws:cdk:logicalId", + "data": "Distribution3CFDistribution15BC87C4" + } + ], + "/integ-cloudfront-s3-oac-l2/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/integ-cloudfront-s3-oac-l2/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "integ-cloudfront-s3-oac-l2" + }, + "S3OriginAccessControlL2DefaultTestDeployAssertEF946D96.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "S3OriginAccessControlL2DefaultTestDeployAssertEF946D96.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "S3OriginAccessControlL2DefaultTestDeployAssertEF946D96": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "S3OriginAccessControlL2DefaultTestDeployAssertEF946D96.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "S3OriginAccessControlL2DefaultTestDeployAssertEF946D96.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "S3OriginAccessControlL2DefaultTestDeployAssertEF946D96.assets" + ], + "metadata": { + "/S3OriginAccessControlL2/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/S3OriginAccessControlL2/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "S3OriginAccessControlL2/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac-l2.js.snapshot/tree.json b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac-l2.js.snapshot/tree.json new file mode 100644 index 0000000000000..b431c945f7a29 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac-l2.js.snapshot/tree.json @@ -0,0 +1,388 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "integ-cloudfront-s3-oac-l2": { + "id": "integ-cloudfront-s3-oac-l2", + "path": "integ-cloudfront-s3-oac-l2", + "children": { + "Bucket": { + "id": "Bucket", + "path": "integ-cloudfront-s3-oac-l2/Bucket", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-cloudfront-s3-oac-l2/Bucket/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::S3::Bucket", + "aws:cdk:cloudformation:props": {} + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.CfnBucket", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.Bucket", + "version": "0.0.0" + } + }, + "OACL2": { + "id": "OACL2", + "path": "integ-cloudfront-s3-oac-l2/OACL2", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-cloudfront-s3-oac-l2/OACL2/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::CloudFront::OriginAccessControl", + "aws:cdk:cloudformation:props": { + "originAccessControlConfig": { + "name": "integcloudfronts3oacl2OACL20729B516", + "originAccessControlOriginType": "s3", + "signingBehavior": "always", + "signingProtocol": "sigv4" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cloudfront.CfnOriginAccessControl", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cloudfront.OriginAccessControl", + "version": "0.0.0" + } + }, + "Distribution": { + "id": "Distribution", + "path": "integ-cloudfront-s3-oac-l2/Distribution", + "children": { + "CFDistribution": { + "id": "CFDistribution", + "path": "integ-cloudfront-s3-oac-l2/Distribution/CFDistribution", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::CloudFront::Distribution", + "aws:cdk:cloudformation:props": { + "distributionConfig": { + "enabled": true, + "defaultRootObject": "index.html", + "httpVersion": "http2", + "priceClass": "PriceClass_100", + "ipv6Enabled": true, + "origins": [ + { + "id": "origin1", + "domainName": { + "Fn::GetAtt": [ + "Bucket83908E77", + "RegionalDomainName" + ] + }, + "s3OriginConfig": {}, + "originAccessControlId": { + "Ref": "OACL2C7CC1987" + }, + "connectionAttempts": 3, + "connectionTimeout": 10 + } + ], + "defaultCacheBehavior": { + "allowedMethods": [ + "GET", + "HEAD" + ], + "cachedMethods": [ + "GET", + "HEAD" + ], + "compress": true, + "forwardedValues": { + "queryString": false, + "cookies": { + "forward": "none" + } + }, + "targetOriginId": "origin1", + "viewerProtocolPolicy": "redirect-to-https" + }, + "viewerCertificate": { + "cloudFrontDefaultCertificate": true + } + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cloudfront.CfnDistribution", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cloudfront.CloudFrontWebDistribution", + "version": "0.0.0" + } + }, + "OriginAccessControlACB7EFE0CA7DB170D0C7D8E8DC4943CF": { + "id": "OriginAccessControlACB7EFE0CA7DB170D0C7D8E8DC4943CF", + "path": "integ-cloudfront-s3-oac-l2/OriginAccessControlACB7EFE0CA7DB170D0C7D8E8DC4943CF", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-cloudfront-s3-oac-l2/OriginAccessControlACB7EFE0CA7DB170D0C7D8E8DC4943CF/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::CloudFront::OriginAccessControl", + "aws:cdk:cloudformation:props": { + "originAccessControlConfig": { + "name": "integcloudfronts3oacl2OriginEFE0CA7DB170D0C7D8E8DC4943CF506089EA", + "originAccessControlOriginType": "s3", + "signingBehavior": "always", + "signingProtocol": "sigv4" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cloudfront.CfnOriginAccessControl", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cloudfront.OriginAccessControl", + "version": "0.0.0" + } + }, + "Distribution2": { + "id": "Distribution2", + "path": "integ-cloudfront-s3-oac-l2/Distribution2", + "children": { + "CFDistribution": { + "id": "CFDistribution", + "path": "integ-cloudfront-s3-oac-l2/Distribution2/CFDistribution", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::CloudFront::Distribution", + "aws:cdk:cloudformation:props": { + "distributionConfig": { + "enabled": true, + "defaultRootObject": "index.html", + "httpVersion": "http2", + "priceClass": "PriceClass_100", + "ipv6Enabled": true, + "origins": [ + { + "id": "origin1", + "domainName": { + "Fn::GetAtt": [ + "Bucket83908E77", + "RegionalDomainName" + ] + }, + "s3OriginConfig": {}, + "originAccessControlId": { + "Ref": "OriginAccessControlACB7EFE0CA7DB170D0C7D8E8DC4943CFAFE70B28" + }, + "connectionAttempts": 3, + "connectionTimeout": 10 + } + ], + "defaultCacheBehavior": { + "allowedMethods": [ + "GET", + "HEAD" + ], + "cachedMethods": [ + "GET", + "HEAD" + ], + "compress": true, + "forwardedValues": { + "queryString": false, + "cookies": { + "forward": "none" + } + }, + "targetOriginId": "origin1", + "viewerProtocolPolicy": "redirect-to-https" + }, + "viewerCertificate": { + "cloudFrontDefaultCertificate": true + } + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cloudfront.CfnDistribution", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cloudfront.CloudFrontWebDistribution", + "version": "0.0.0" + } + }, + "Distribution3": { + "id": "Distribution3", + "path": "integ-cloudfront-s3-oac-l2/Distribution3", + "children": { + "CFDistribution": { + "id": "CFDistribution", + "path": "integ-cloudfront-s3-oac-l2/Distribution3/CFDistribution", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::CloudFront::Distribution", + "aws:cdk:cloudformation:props": { + "distributionConfig": { + "enabled": true, + "defaultRootObject": "index.html", + "httpVersion": "http2", + "priceClass": "PriceClass_100", + "ipv6Enabled": true, + "origins": [ + { + "id": "origin1", + "domainName": { + "Fn::GetAtt": [ + "Bucket83908E77", + "RegionalDomainName" + ] + }, + "s3OriginConfig": {}, + "originAccessControlId": { + "Ref": "OriginAccessControlACB7EFE0CA7DB170D0C7D8E8DC4943CFAFE70B28" + }, + "connectionAttempts": 3, + "connectionTimeout": 10 + } + ], + "defaultCacheBehavior": { + "allowedMethods": [ + "GET", + "HEAD" + ], + "cachedMethods": [ + "GET", + "HEAD" + ], + "compress": true, + "forwardedValues": { + "queryString": false, + "cookies": { + "forward": "none" + } + }, + "targetOriginId": "origin1", + "viewerProtocolPolicy": "redirect-to-https" + }, + "viewerCertificate": { + "cloudFrontDefaultCertificate": true + } + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cloudfront.CfnDistribution", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cloudfront.CloudFrontWebDistribution", + "version": "0.0.0" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "integ-cloudfront-s3-oac-l2/BootstrapVersion", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "integ-cloudfront-s3-oac-l2/CheckBootstrapVersion", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + }, + "S3OriginAccessControlL2": { + "id": "S3OriginAccessControlL2", + "path": "S3OriginAccessControlL2", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "S3OriginAccessControlL2/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "S3OriginAccessControlL2/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.270" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "S3OriginAccessControlL2/DefaultTest/DeployAssert", + "children": { + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "S3OriginAccessControlL2/DefaultTest/DeployAssert/BootstrapVersion", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "S3OriginAccessControlL2/DefaultTest/DeployAssert/CheckBootstrapVersion", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTest", + "version": "0.0.0" + } + }, + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.270" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac-l2.ts b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac-l2.ts new file mode 100644 index 0000000000000..393c5cd50e6a1 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac-l2.ts @@ -0,0 +1,49 @@ +import * as s3 from '@aws-cdk/aws-s3'; +import * as cdk from '@aws-cdk/core'; +import { IntegTest } from '@aws-cdk/integ-tests'; +import * as cloudfront from '../lib'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'integ-cloudfront-s3-oac-l2'); + +const bucket = new s3.Bucket(stack, 'Bucket', { removalPolicy: cdk.RemovalPolicy.DESTROY }); + +const oac = new cloudfront.OriginAccessControl(stack, 'OACL2', { + originType: cloudfront.OriginAccessControlOriginType.S3, +}); + +new cloudfront.CloudFrontWebDistribution(stack, 'Distribution', { + originConfigs: [{ + behaviors: [{ isDefaultBehavior: true }], + s3OriginSource: { + s3BucketSource: bucket, + originAccessControl: oac, + }, + }], +}); + +new cloudfront.CloudFrontWebDistribution(stack, 'Distribution2', { + originConfigs: [{ + behaviors: [{ isDefaultBehavior: true }], + s3OriginSource: { + s3BucketSource: bucket, + originAccessControl: cloudfront.OriginAccessControl.fromS3Defaults(stack), + }, + }], +}); + +new cloudfront.CloudFrontWebDistribution(stack, 'Distribution3', { + originConfigs: [{ + behaviors: [{ isDefaultBehavior: true }], + s3OriginSource: { + s3BucketSource: bucket, + originAccessControl: cloudfront.OriginAccessControl.fromS3Defaults(stack), + }, + }], +}); + +new IntegTest(app, 'S3OriginAccessControlL2', { + testCases: [stack], +}); + +app.synth(); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac.js.snapshot/S3OriginAccessControlDefaultTestDeployAssertFF6C26DC.assets.json b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac.js.snapshot/S3OriginAccessControlDefaultTestDeployAssertFF6C26DC.assets.json new file mode 100644 index 0000000000000..db0cb8d4eab63 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac.js.snapshot/S3OriginAccessControlDefaultTestDeployAssertFF6C26DC.assets.json @@ -0,0 +1,19 @@ +{ + "version": "31.0.0", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "S3OriginAccessControlDefaultTestDeployAssertFF6C26DC.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac.js.snapshot/S3OriginAccessControlDefaultTestDeployAssertFF6C26DC.template.json b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac.js.snapshot/S3OriginAccessControlDefaultTestDeployAssertFF6C26DC.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac.js.snapshot/S3OriginAccessControlDefaultTestDeployAssertFF6C26DC.template.json @@ -0,0 +1,36 @@ +{ + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac.js.snapshot/cdk.out b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac.js.snapshot/cdk.out new file mode 100644 index 0000000000000..7925065efbcc4 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"31.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac.js.snapshot/integ-cloudfront-s3-oac.assets.json b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac.js.snapshot/integ-cloudfront-s3-oac.assets.json new file mode 100644 index 0000000000000..2ec9b7ebc04b3 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac.js.snapshot/integ-cloudfront-s3-oac.assets.json @@ -0,0 +1,19 @@ +{ + "version": "31.0.0", + "files": { + "7a6a66f71ec422e3c5731fa2b2e351c1e1ddc742ca1faa36ffa0bf5dc3eb0577": { + "source": { + "path": "integ-cloudfront-s3-oac.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "7a6a66f71ec422e3c5731fa2b2e351c1e1ddc742ca1faa36ffa0bf5dc3eb0577.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac.js.snapshot/integ-cloudfront-s3-oac.template.json b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac.js.snapshot/integ-cloudfront-s3-oac.template.json new file mode 100644 index 0000000000000..d2e01c93e4ed9 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac.js.snapshot/integ-cloudfront-s3-oac.template.json @@ -0,0 +1,105 @@ +{ + "Resources": { + "Bucket83908E77": { + "Type": "AWS::S3::Bucket", + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "OAC": { + "Type": "AWS::CloudFront::OriginAccessControl", + "Properties": { + "OriginAccessControlConfig": { + "Name": "OACId", + "OriginAccessControlOriginType": "s3", + "SigningBehavior": "always", + "SigningProtocol": "sigv4" + } + } + }, + "DistributionCFDistribution882A7313": { + "Type": "AWS::CloudFront::Distribution", + "Properties": { + "DistributionConfig": { + "DefaultCacheBehavior": { + "AllowedMethods": [ + "GET", + "HEAD" + ], + "CachedMethods": [ + "GET", + "HEAD" + ], + "Compress": true, + "ForwardedValues": { + "Cookies": { + "Forward": "none" + }, + "QueryString": false + }, + "TargetOriginId": "origin1", + "ViewerProtocolPolicy": "redirect-to-https" + }, + "DefaultRootObject": "index.html", + "Enabled": true, + "HttpVersion": "http2", + "IPV6Enabled": true, + "Origins": [ + { + "ConnectionAttempts": 3, + "ConnectionTimeout": 10, + "DomainName": { + "Fn::GetAtt": [ + "Bucket83908E77", + "RegionalDomainName" + ] + }, + "Id": "origin1", + "OriginAccessControlId": { + "Ref": "OAC" + }, + "S3OriginConfig": {} + } + ], + "PriceClass": "PriceClass_100", + "ViewerCertificate": { + "CloudFrontDefaultCertificate": true + } + } + } + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac.js.snapshot/integ.json b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac.js.snapshot/integ.json new file mode 100644 index 0000000000000..8d1ef951d7dc9 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac.js.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "31.0.0", + "testCases": { + "S3OriginAccessControl/DefaultTest": { + "stacks": [ + "integ-cloudfront-s3-oac" + ], + "assertionStack": "S3OriginAccessControl/DefaultTest/DeployAssert", + "assertionStackName": "S3OriginAccessControlDefaultTestDeployAssertFF6C26DC" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac.js.snapshot/manifest.json b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac.js.snapshot/manifest.json new file mode 100644 index 0000000000000..aa7b23fea1dee --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac.js.snapshot/manifest.json @@ -0,0 +1,123 @@ +{ + "version": "31.0.0", + "artifacts": { + "integ-cloudfront-s3-oac.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "integ-cloudfront-s3-oac.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "integ-cloudfront-s3-oac": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "integ-cloudfront-s3-oac.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/7a6a66f71ec422e3c5731fa2b2e351c1e1ddc742ca1faa36ffa0bf5dc3eb0577.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "integ-cloudfront-s3-oac.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "integ-cloudfront-s3-oac.assets" + ], + "metadata": { + "/integ-cloudfront-s3-oac/Bucket/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "Bucket83908E77" + } + ], + "/integ-cloudfront-s3-oac/OAC": [ + { + "type": "aws:cdk:logicalId", + "data": "OAC" + } + ], + "/integ-cloudfront-s3-oac/Distribution/CFDistribution": [ + { + "type": "aws:cdk:logicalId", + "data": "DistributionCFDistribution882A7313" + } + ], + "/integ-cloudfront-s3-oac/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/integ-cloudfront-s3-oac/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "integ-cloudfront-s3-oac" + }, + "S3OriginAccessControlDefaultTestDeployAssertFF6C26DC.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "S3OriginAccessControlDefaultTestDeployAssertFF6C26DC.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "S3OriginAccessControlDefaultTestDeployAssertFF6C26DC": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "S3OriginAccessControlDefaultTestDeployAssertFF6C26DC.template.json", + "validateOnSynth": false, + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "S3OriginAccessControlDefaultTestDeployAssertFF6C26DC.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "S3OriginAccessControlDefaultTestDeployAssertFF6C26DC.assets" + ], + "metadata": { + "/S3OriginAccessControl/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/S3OriginAccessControl/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "S3OriginAccessControl/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac.js.snapshot/tree.json b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac.js.snapshot/tree.json new file mode 100644 index 0000000000000..e1c45e818ee5f --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac.js.snapshot/tree.json @@ -0,0 +1,219 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "integ-cloudfront-s3-oac": { + "id": "integ-cloudfront-s3-oac", + "path": "integ-cloudfront-s3-oac", + "children": { + "Bucket": { + "id": "Bucket", + "path": "integ-cloudfront-s3-oac/Bucket", + "children": { + "Resource": { + "id": "Resource", + "path": "integ-cloudfront-s3-oac/Bucket/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::S3::Bucket", + "aws:cdk:cloudformation:props": {} + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.CfnBucket", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-s3.Bucket", + "version": "0.0.0" + } + }, + "OAC": { + "id": "OAC", + "path": "integ-cloudfront-s3-oac/OAC", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::CloudFront::OriginAccessControl", + "aws:cdk:cloudformation:props": { + "originAccessControlConfig": { + "name": "OACId", + "originAccessControlOriginType": "s3", + "signingBehavior": "always", + "signingProtocol": "sigv4" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cloudfront.CfnOriginAccessControl", + "version": "0.0.0" + } + }, + "OACImport": { + "id": "OACImport", + "path": "integ-cloudfront-s3-oac/OACImport", + "constructInfo": { + "fqn": "@aws-cdk/core.Resource", + "version": "0.0.0" + } + }, + "Distribution": { + "id": "Distribution", + "path": "integ-cloudfront-s3-oac/Distribution", + "children": { + "CFDistribution": { + "id": "CFDistribution", + "path": "integ-cloudfront-s3-oac/Distribution/CFDistribution", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::CloudFront::Distribution", + "aws:cdk:cloudformation:props": { + "distributionConfig": { + "enabled": true, + "defaultRootObject": "index.html", + "httpVersion": "http2", + "priceClass": "PriceClass_100", + "ipv6Enabled": true, + "origins": [ + { + "id": "origin1", + "domainName": { + "Fn::GetAtt": [ + "Bucket83908E77", + "RegionalDomainName" + ] + }, + "s3OriginConfig": {}, + "originAccessControlId": { + "Ref": "OAC" + }, + "connectionAttempts": 3, + "connectionTimeout": 10 + } + ], + "defaultCacheBehavior": { + "allowedMethods": [ + "GET", + "HEAD" + ], + "cachedMethods": [ + "GET", + "HEAD" + ], + "compress": true, + "forwardedValues": { + "queryString": false, + "cookies": { + "forward": "none" + } + }, + "targetOriginId": "origin1", + "viewerProtocolPolicy": "redirect-to-https" + }, + "viewerCertificate": { + "cloudFrontDefaultCertificate": true + } + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cloudfront.CfnDistribution", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/aws-cloudfront.CloudFrontWebDistribution", + "version": "0.0.0" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "integ-cloudfront-s3-oac/BootstrapVersion", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "integ-cloudfront-s3-oac/CheckBootstrapVersion", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + }, + "S3OriginAccessControl": { + "id": "S3OriginAccessControl", + "path": "S3OriginAccessControl", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "S3OriginAccessControl/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "S3OriginAccessControl/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.270" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "S3OriginAccessControl/DefaultTest/DeployAssert", + "children": { + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "S3OriginAccessControl/DefaultTest/DeployAssert/BootstrapVersion", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnParameter", + "version": "0.0.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "S3OriginAccessControl/DefaultTest/DeployAssert/CheckBootstrapVersion", + "constructInfo": { + "fqn": "@aws-cdk/core.CfnRule", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.Stack", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests.IntegTest", + "version": "0.0.0" + } + }, + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.1.270" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/core.App", + "version": "0.0.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac.ts b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac.ts new file mode 100644 index 0000000000000..5ddd4b8e6be4b --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.cloudfront-s3-oac.ts @@ -0,0 +1,40 @@ +import * as s3 from '@aws-cdk/aws-s3'; +import * as cdk from '@aws-cdk/core'; +import { IntegTest } from '@aws-cdk/integ-tests'; +import * as cloudfront from '../lib'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'integ-cloudfront-s3-oac'); + +const bucket = new s3.Bucket(stack, 'Bucket', { removalPolicy: cdk.RemovalPolicy.DESTROY }); + +const oac = new cloudfront.CfnOriginAccessControl(stack, 'OAC', { + originAccessControlConfig: { + name: 'OACId', + originAccessControlOriginType: 's3', + signingBehavior: 'always', + signingProtocol: 'sigv4', + }, +}); + +const oacImport = cloudfront.OriginAccessControl.fromOriginAccessControlId( + stack, + 'OACImport', + oac.ref, +); + +new cloudfront.CloudFrontWebDistribution(stack, 'Distribution', { + originConfigs: [{ + behaviors: [{ isDefaultBehavior: true }], + s3OriginSource: { + s3BucketSource: bucket, + originAccessControl: oacImport, + }, + }], +}); + +new IntegTest(app, 'S3OriginAccessControl', { + testCases: [stack], +}); + +app.synth(); \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/oac.test.ts b/packages/@aws-cdk/aws-cloudfront/test/oac.test.ts new file mode 100644 index 0000000000000..be5045a1d5587 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/oac.test.ts @@ -0,0 +1,166 @@ +import { Template } from '@aws-cdk/assertions'; +import * as cdk from '@aws-cdk/core'; +import { Construct } from 'constructs'; +import { OriginAccessControl, OriginAccessControlOriginType, OriginAccessControlSigningBehavior } from '../lib'; + +describe('Origin Access Control', () => { + test('With required origin types', () => { + const stack = new cdk.Stack(); + + new OriginAccessControl(stack, 'OACMediaStore', { + originType: OriginAccessControlOriginType.MEDIASTORE, + }); + + new OriginAccessControl(stack, 'OACS3', { + originType: OriginAccessControlOriginType.S3, + }); + + const tmpl = Template.fromStack(stack); + tmpl.resourceCountIs('AWS::CloudFront::OriginAccessControl', 2); + tmpl.templateMatches( + { + Resources: { + OACMediaStoreCD88D134: { + Type: 'AWS::CloudFront::OriginAccessControl', + Properties: { + OriginAccessControlConfig: { + OriginAccessControlOriginType: 'mediastore', + SigningBehavior: 'always', + SigningProtocol: 'sigv4', + }, + }, + }, + OACS35438DEEA: { + Type: 'AWS::CloudFront::OriginAccessControl', + Properties: { + OriginAccessControlConfig: { + OriginAccessControlOriginType: 's3', + SigningBehavior: 'always', + SigningProtocol: 'sigv4', + }, + }, + }, + }, + }, + ); + }); + + test('With provided OAC name', () => { + const stack = new cdk.Stack(); + + new OriginAccessControl(stack, 'OACS3', { + originAccessControlName: 'OACPhysicalId', + originType: OriginAccessControlOriginType.S3, + }); + + const tmpl = Template.fromStack(stack); + tmpl.resourceCountIs('AWS::CloudFront::OriginAccessControl', 1); + tmpl.templateMatches( + { + Resources: { + OACS35438DEEA: { + Type: 'AWS::CloudFront::OriginAccessControl', + Properties: { + OriginAccessControlConfig: { + Name: 'OACPhysicalId', + }, + }, + }, + }, + }, + ); + }); + + test('Default stack singletons', () => { + const stack = new cdk.Stack(); + const nested = new Construct(stack, 'Construct'); + + OriginAccessControl.fromS3Defaults(stack); + OriginAccessControl.fromS3Defaults(stack); + OriginAccessControl.fromMediaStoreDefaults(stack); + OriginAccessControl.fromMediaStoreDefaults(nested); + OriginAccessControl.fromS3Defaults(nested); + OriginAccessControl.fromMediaStoreDefaults(nested); + + const tmpl = Template.fromStack(stack); + + tmpl.resourceCountIs('AWS::CloudFront::OriginAccessControl', 2); + tmpl.templateMatches( + { + Resources: { + OriginAccessControlACB7EFE0CA7DB170D0C7D8E8DC4943CFAFE70B28: { + Type: 'AWS::CloudFront::OriginAccessControl', + Properties: { + OriginAccessControlConfig: { + OriginAccessControlOriginType: 's3', + SigningBehavior: 'always', + SigningProtocol: 'sigv4', + }, + }, + }, + OriginAccessControl07ED4DB176D4FB972C6FA0038059996CB0D48653: { + Type: 'AWS::CloudFront::OriginAccessControl', + Properties: { + OriginAccessControlConfig: { + OriginAccessControlOriginType: 'mediastore', + SigningBehavior: 'always', + SigningProtocol: 'sigv4', + }, + }, + }, + }, + }, + ); + }); + + test('Default stack singletons with behavior overrides', () => { + const stack = new cdk.Stack(); + + OriginAccessControl.fromS3Defaults(stack, undefined, undefined); + OriginAccessControl.fromS3Defaults(stack, undefined, OriginAccessControlSigningBehavior.ALWAYS); + OriginAccessControl.fromS3Defaults(stack, undefined, OriginAccessControlSigningBehavior.NEVER); + OriginAccessControl.fromS3Defaults(stack, undefined, OriginAccessControlSigningBehavior.NEVER); + OriginAccessControl.fromS3Defaults(stack, undefined, OriginAccessControlSigningBehavior.NO_OVERRIDE); + OriginAccessControl.fromS3Defaults(stack, undefined, OriginAccessControlSigningBehavior.NO_OVERRIDE); + + OriginAccessControl.fromMediaStoreDefaults(stack, undefined, undefined); + OriginAccessControl.fromMediaStoreDefaults(stack, undefined, OriginAccessControlSigningBehavior.ALWAYS); + OriginAccessControl.fromMediaStoreDefaults(stack, undefined, OriginAccessControlSigningBehavior.NEVER); + OriginAccessControl.fromMediaStoreDefaults(stack, undefined, OriginAccessControlSigningBehavior.NEVER); + OriginAccessControl.fromMediaStoreDefaults(stack, undefined, OriginAccessControlSigningBehavior.NO_OVERRIDE); + OriginAccessControl.fromMediaStoreDefaults(stack, undefined, OriginAccessControlSigningBehavior.NO_OVERRIDE); + + const tmpl = Template.fromStack(stack); + tmpl.resourceCountIs('AWS::CloudFront::OriginAccessControl', 6); + for (const val of ['always', 'never', 'no-override']) { + const props = { OriginAccessControlConfig: { SigningBehavior: val } }; + tmpl.resourcePropertiesCountIs('AWS::CloudFront::OriginAccessControl', props, 2); + } + }); + + test('Defaults with explicit construct ids', () => { + const stack = new cdk.Stack(); + + OriginAccessControl.fromS3Defaults(stack, 'OACS1', undefined); + OriginAccessControl.fromS3Defaults(stack, 'OACS2', OriginAccessControlSigningBehavior.ALWAYS); + OriginAccessControl.fromS3Defaults(stack, 'OACS3', OriginAccessControlSigningBehavior.NEVER); + OriginAccessControl.fromS3Defaults(stack, 'OACS4', OriginAccessControlSigningBehavior.NEVER); + OriginAccessControl.fromS3Defaults(stack, 'OACS5', OriginAccessControlSigningBehavior.NO_OVERRIDE); + OriginAccessControl.fromS3Defaults(stack, 'OACS6', OriginAccessControlSigningBehavior.NO_OVERRIDE); + + OriginAccessControl.fromMediaStoreDefaults(stack, 'OACM1', undefined); + OriginAccessControl.fromMediaStoreDefaults(stack, 'OACM2', OriginAccessControlSigningBehavior.ALWAYS); + OriginAccessControl.fromMediaStoreDefaults(stack, 'OACM3', OriginAccessControlSigningBehavior.NEVER); + OriginAccessControl.fromMediaStoreDefaults(stack, 'OACM4', OriginAccessControlSigningBehavior.NEVER); + OriginAccessControl.fromMediaStoreDefaults(stack, 'OACM5', OriginAccessControlSigningBehavior.NO_OVERRIDE); + OriginAccessControl.fromMediaStoreDefaults(stack, 'OACM6', OriginAccessControlSigningBehavior.NO_OVERRIDE); + + const tmpl = Template.fromStack(stack); + tmpl.resourceCountIs('AWS::CloudFront::OriginAccessControl', 12); + for (const val of ['always', 'never', 'no-override']) { + const props = { OriginAccessControlConfig: { SigningBehavior: val } }; + tmpl.resourcePropertiesCountIs('AWS::CloudFront::OriginAccessControl', props, 4); + } + }); +}); + diff --git a/packages/@aws-cdk/aws-cloudfront/test/web-distribution.test.ts b/packages/@aws-cdk/aws-cloudfront/test/web-distribution.test.ts index e5b65d12915ed..29fcd24048ef4 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/web-distribution.test.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/web-distribution.test.ts @@ -14,6 +14,7 @@ import { GeoRestriction, KeyGroup, LambdaEdgeEventType, + OriginAccessControl, OriginAccessIdentity, PublicKey, SecurityPolicyProtocol, @@ -406,10 +407,32 @@ added the ellipsis so a user would know there was more to r...`, }], }, }); + }); + test('distribution with bucket and OAC', () => { + const stack = new cdk.Stack(); + const s3BucketSource = new s3.Bucket(stack, 'Bucket'); + const originAccessControl = OriginAccessControl.fromS3Defaults(stack); - }); + new CloudFrontWebDistribution(stack, 'AnAmazingWebsiteProbably', { + originConfigs: [{ + s3OriginSource: { s3BucketSource, originAccessControl }, + behaviors: [{ isDefaultBehavior: true }], + }], + }); + Template.fromStack(stack).hasResourceProperties('AWS::CloudFront::Distribution', { + DistributionConfig: { + Origins: [ + { + Id: 'origin1', + OriginAccessControlId: Match.anyValue(), + S3OriginConfig: { OriginAccessIdentity: Match.absent() }, + }, + ], + }, + }); + }); testDeprecated('distribution with trusted signers on default distribution', () => { const stack = new cdk.Stack();