From 77f97039221981aea980b583e56ac88ed854a8e4 Mon Sep 17 00:00:00 2001 From: James Fleming Date: Thu, 27 Aug 2020 17:25:51 +0000 Subject: [PATCH 01/15] fix(lamba): Add Java 8 Corretto Runtime support --- packages/@aws-cdk/aws-lambda/lib/runtime.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/@aws-cdk/aws-lambda/lib/runtime.ts b/packages/@aws-cdk/aws-lambda/lib/runtime.ts index e32772fcb4007..523f68c3bd036 100644 --- a/packages/@aws-cdk/aws-lambda/lib/runtime.ts +++ b/packages/@aws-cdk/aws-lambda/lib/runtime.ts @@ -97,6 +97,11 @@ export class Runtime { */ public static readonly JAVA_8 = new Runtime('java8', RuntimeFamily.JAVA); + /** + * The Java 8 Corretto runtime (java8.al2) + */ + public static readonly JAVA_8_CORRETTO = new Runtime('java8.al2', RuntimeFamily.JAVA); + /** * The Java 11 runtime (java11) */ From 064b884eff7e4bd7a5a1df5a528017c166e1a84e Mon Sep 17 00:00:00 2001 From: James Fleming Date: Thu, 27 Aug 2020 17:29:58 +0000 Subject: [PATCH 02/15] Add custom AL2 runtime --- packages/@aws-cdk/aws-lambda/lib/runtime.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/@aws-cdk/aws-lambda/lib/runtime.ts b/packages/@aws-cdk/aws-lambda/lib/runtime.ts index 523f68c3bd036..56ffbecd19b3b 100644 --- a/packages/@aws-cdk/aws-lambda/lib/runtime.ts +++ b/packages/@aws-cdk/aws-lambda/lib/runtime.ts @@ -157,6 +157,11 @@ export class Runtime { */ public static readonly PROVIDED = new Runtime('provided', RuntimeFamily.OTHER); + /** + * The custom provided runtime (provided) + */ + public static readonly PROVIDED_AL2 = new Runtime('provided.al2', RuntimeFamily.OTHER); + /** * The name of this runtime, as expected by the Lambda resource. */ From c5bb55c37c03597139522e0bb42f094c1f6b647e Mon Sep 17 00:00:00 2001 From: Shiv Lakshminarayan Date: Fri, 28 Aug 2020 00:30:01 -0700 Subject: [PATCH 03/15] fix(cli): unable to upgrade new style bootstrap to version (#10030) We were reading the wrong key when reading version source for template resources. This prevented upgrade of bootstrap as the new version was being incorrectly returned as `0`. Fixes #10016 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/aws-cdk/lib/api/bootstrap/deploy-bootstrap.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/aws-cdk/lib/api/bootstrap/deploy-bootstrap.ts b/packages/aws-cdk/lib/api/bootstrap/deploy-bootstrap.ts index 9182e291fee17..53a38108b7d87 100644 --- a/packages/aws-cdk/lib/api/bootstrap/deploy-bootstrap.ts +++ b/packages/aws-cdk/lib/api/bootstrap/deploy-bootstrap.ts @@ -6,7 +6,7 @@ import * as fs from 'fs-extra'; import { Mode, SdkProvider } from '../aws-auth'; import { deployStack, DeployStackResult } from '../deploy-stack'; import { DEFAULT_TOOLKIT_STACK_NAME, ToolkitInfo } from '../toolkit-info'; -import { BOOTSTRAP_VERSION_OUTPUT, BootstrapEnvironmentOptions } from './bootstrap-props'; +import { BOOTSTRAP_VERSION_OUTPUT, BootstrapEnvironmentOptions, BOOTSTRAP_VERSION_RESOURCE } from './bootstrap-props'; /** * Perform the actual deployment of a bootstrap stack, given a template and some parameters @@ -61,7 +61,7 @@ export async function deployBootstrapStack( function bootstrapVersionFromTemplate(template: any): number { const versionSources = [ template.Outputs?.[BOOTSTRAP_VERSION_OUTPUT]?.Value, - template.Resources?.[BOOTSTRAP_VERSION_OUTPUT]?.Properties?.Value, + template.Resources?.[BOOTSTRAP_VERSION_RESOURCE]?.Properties?.Value, ]; for (const vs of versionSources) { From 8d3e422809c29da926bae878276619a59ae82ecb Mon Sep 17 00:00:00 2001 From: Shiv Lakshminarayan Date: Fri, 28 Aug 2020 00:30:01 -0700 Subject: [PATCH 04/15] fix(cli): unable to upgrade new style bootstrap to version (#10030) We were reading the wrong key when reading version source for template resources. This prevented upgrade of bootstrap as the new version was being incorrectly returned as `0`. Fixes #10016 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/aws-cdk/lib/api/bootstrap/deploy-bootstrap.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/aws-cdk/lib/api/bootstrap/deploy-bootstrap.ts b/packages/aws-cdk/lib/api/bootstrap/deploy-bootstrap.ts index 9182e291fee17..53a38108b7d87 100644 --- a/packages/aws-cdk/lib/api/bootstrap/deploy-bootstrap.ts +++ b/packages/aws-cdk/lib/api/bootstrap/deploy-bootstrap.ts @@ -6,7 +6,7 @@ import * as fs from 'fs-extra'; import { Mode, SdkProvider } from '../aws-auth'; import { deployStack, DeployStackResult } from '../deploy-stack'; import { DEFAULT_TOOLKIT_STACK_NAME, ToolkitInfo } from '../toolkit-info'; -import { BOOTSTRAP_VERSION_OUTPUT, BootstrapEnvironmentOptions } from './bootstrap-props'; +import { BOOTSTRAP_VERSION_OUTPUT, BootstrapEnvironmentOptions, BOOTSTRAP_VERSION_RESOURCE } from './bootstrap-props'; /** * Perform the actual deployment of a bootstrap stack, given a template and some parameters @@ -61,7 +61,7 @@ export async function deployBootstrapStack( function bootstrapVersionFromTemplate(template: any): number { const versionSources = [ template.Outputs?.[BOOTSTRAP_VERSION_OUTPUT]?.Value, - template.Resources?.[BOOTSTRAP_VERSION_OUTPUT]?.Properties?.Value, + template.Resources?.[BOOTSTRAP_VERSION_RESOURCE]?.Properties?.Value, ]; for (const vs of versionSources) { From 0748889de8c7a29d2456b4fcba6045cc0d41e58e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=91=A8=F0=9F=8F=BC=E2=80=8D=F0=9F=92=BB=20Romain=20M?= =?UTF-8?q?arcadier-Muller?= Date: Fri, 28 Aug 2020 11:27:02 +0200 Subject: [PATCH 05/15] chore(release): 1.61.1 --- CHANGELOG.md | 9 ++++++++- lerna.json | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e18651f1b19c..df38cbd61a42e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [1.61.1](https://github.com/aws/aws-cdk/compare/v1.61.0...v1.61.1) (2020-08-28) + + +### Bug Fixes + +* **cli:** unable to upgrade new style bootstrap to version ([#10030](https://github.com/aws/aws-cdk/issues/10030)) ([8d3e422](https://github.com/aws/aws-cdk/commit/8d3e422809c29da926bae878276619a59ae82ecb)), closes [#10016](https://github.com/aws/aws-cdk/issues/10016) + ## [1.61.0](https://github.com/aws/aws-cdk/compare/v1.60.0...v1.61.0) (2020-08-27) @@ -39,7 +46,7 @@ All notable changes to this project will be documented in this file. See [standa ### ⚠ BREAKING CHANGES TO EXPERIMENTAL FEATURES * **cloudfront:** Distribution: `.domains` must be specified if `certificate` is provided. -* **appsync:** **appsync.addXxxDataSource** `name` and `description` props are now optional and in an `DataSourceOptions` interface. +* **appsync:** **appsync.addXxxDataSource** `name` and `description` props are now optional and in an `DataSourceOptions` interface. - **appsync**: the props `name` and `description` in `addXxxDataSource` have been moved into new props `options` of type `DataSourceOptions` - **appsync**: `DataSourceOptions.name` defaults to id - **appsync**: `DataSourceOptions.description` defaults to undefined diff --git a/lerna.json b/lerna.json index 6cd02888d14d3..87746feb847f4 100644 --- a/lerna.json +++ b/lerna.json @@ -10,5 +10,5 @@ "tools/*" ], "rejectCycles": "true", - "version": "1.61.0" + "version": "1.61.1" } From 7f351ffeee30e1a2451e9b456c0d0a21002397da Mon Sep 17 00:00:00 2001 From: Jonathan Goldwasser Date: Fri, 28 Aug 2020 11:39:09 +0200 Subject: [PATCH 06/15] fix(custom-resources): buffers returned by AwsCustomResource are unusable (#9977) Convert buffers to strings when flattening the API response. Closes #9969, closes #10017 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../lib/aws-custom-resource/runtime/index.ts | 11 +++++----- .../aws-custom-resource-provider.test.ts | 21 +++++++++++++++++++ 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/runtime/index.ts b/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/runtime/index.ts index 38d0a7cf5cb19..9b311a5b84403 100644 --- a/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/runtime/index.ts +++ b/packages/@aws-cdk/custom-resources/lib/aws-custom-resource/runtime/index.ts @@ -13,11 +13,12 @@ export function flatten(object: object): { [key: string]: string } { {}, ...function _flatten(child: any, path: string[] = []): any { return [].concat(...Object.keys(child) - .map(key => - typeof child[key] === 'object' && child[key] !== null - ? _flatten(child[key], path.concat([key])) - : ({ [path.concat([key]).join('.')]: child[key] }), - )); + .map(key => { + const childKey = Buffer.isBuffer(child[key]) ? child[key].toString('utf8') : child[key]; + return typeof childKey === 'object' && childKey !== null + ? _flatten(childKey, path.concat([key])) + : ({ [path.concat([key]).join('.')]: childKey }); + })); }(object), ); } diff --git a/packages/@aws-cdk/custom-resources/test/aws-custom-resource/aws-custom-resource-provider.test.ts b/packages/@aws-cdk/custom-resources/test/aws-custom-resource/aws-custom-resource-provider.test.ts index 3b1cad57178de..44684ab4282e1 100644 --- a/packages/@aws-cdk/custom-resources/test/aws-custom-resource/aws-custom-resource-provider.test.ts +++ b/packages/@aws-cdk/custom-resources/test/aws-custom-resource/aws-custom-resource-provider.test.ts @@ -411,6 +411,24 @@ test('flatten correctly flattens a nested object', () => { }); }); +test('flatten correctly flattens an object with buffers', () => { + expect(flatten({ + body: Buffer.from('body'), + nested: { + buffer: Buffer.from('buffer'), + array: [ + Buffer.from('array.0'), + Buffer.from('array.1'), + ], + }, + })).toEqual({ + 'body': 'body', + 'nested.buffer': 'buffer', + 'nested.array.0': 'array.0', + 'nested.array.1': 'array.1', + }); +}); + test('installs the latest SDK', async () => { const tmpPath = '/tmp/node_modules/aws-sdk'; @@ -455,4 +473,7 @@ test('installs the latest SDK', async () => { expect(request.isDone()).toBeTruthy(); expect(() => require.resolve(tmpPath)).not.toThrow(); + + // clean up aws-sdk install + await fs.remove(tmpPath); }); From 5738dc17025355e3f94edc4af242253ebb3409f6 Mon Sep 17 00:00:00 2001 From: Nick Lynch Date: Fri, 28 Aug 2020 11:03:18 +0100 Subject: [PATCH 07/15] feat(rds): custom security groups for OptionGroups (#10011) Enables customers to provide a custom security group for any OptionGroup that requires a VPC and SecurityGroup. fixes #9240 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-rds/README.md | 24 ++++++++++ packages/@aws-cdk/aws-rds/lib/option-group.ts | 22 ++++++--- .../aws-rds/test/test.option-group.ts | 47 ++++++++++++++++++- 3 files changed, 86 insertions(+), 7 deletions(-) diff --git a/packages/@aws-cdk/aws-rds/README.md b/packages/@aws-cdk/aws-rds/README.md index 829e7237f33e6..b6883d7a75ccb 100644 --- a/packages/@aws-cdk/aws-rds/README.md +++ b/packages/@aws-cdk/aws-rds/README.md @@ -324,3 +324,27 @@ const instance = new rds.DatabaseInstance(this, 'Instance', { // ... }); ``` + +### Option Groups + +Some DB engines offer additional features that make it easier to manage data and databases, and to provide additional security for your database. +Amazon RDS uses option groups to enable and configure these features. An option group can specify features, called options, +that are available for a particular Amazon RDS DB instance. + +```ts +const vpc: ec2.IVpc = ...; +const securityGroup: ec2.ISecurityGroup = ...; +new rds.OptionGroup(stack, 'Options', { + engine: DatabaseInstanceEngine.oracleSe({ + version: OracleLegacyEngineVersion.VER_11_2, + }), + configurations: [ + { + name: 'OEM', + port: 5500, + vpc, + securityGroups: [securityGroup], // Optional - a default group will be created if not provided. + }, + ], +}); +``` diff --git a/packages/@aws-cdk/aws-rds/lib/option-group.ts b/packages/@aws-cdk/aws-rds/lib/option-group.ts index 418c71d1c7c6e..f2e8ddf69078d 100644 --- a/packages/@aws-cdk/aws-rds/lib/option-group.ts +++ b/packages/@aws-cdk/aws-rds/lib/option-group.ts @@ -53,6 +53,14 @@ export interface OptionConfiguration { * @default - no VPC */ readonly vpc?: ec2.IVpc; + + /** + * Optional list of security groups to use for this option, if `vpc` is specified. + * If no groups are provided, a default one will be created. + * + * @default - a default group will be created if `port` or `vpc` are specified. + */ + readonly securityGroups?: ec2.ISecurityGroup[]; } /** @@ -135,20 +143,22 @@ export class OptionGroup extends Resource implements IOptionGroup { throw new Error('`port` and `vpc` must be specified together.'); } - const securityGroup = new ec2.SecurityGroup(this, `SecurityGroup${config.name}`, { - description: `Security group for ${config.name} option`, - vpc: config.vpc, - }); + const securityGroups = config.securityGroups && config.securityGroups.length > 0 + ? config.securityGroups + : [new ec2.SecurityGroup(this, `SecurityGroup${config.name}`, { + description: `Security group for ${config.name} option`, + vpc: config.vpc, + })]; this.optionConnections[config.name] = new ec2.Connections({ - securityGroups: [securityGroup], + securityGroups: securityGroups, defaultPort: ec2.Port.tcp(config.port), }); configuration = { ...configuration, port: config.port, - vpcSecurityGroupMemberships: [securityGroup.securityGroupId], + vpcSecurityGroupMemberships: securityGroups.map(sg => sg.securityGroupId), }; } diff --git a/packages/@aws-cdk/aws-rds/test/test.option-group.ts b/packages/@aws-cdk/aws-rds/test/test.option-group.ts index 6152f45e881cd..d4129b6aebd5a 100644 --- a/packages/@aws-cdk/aws-rds/test/test.option-group.ts +++ b/packages/@aws-cdk/aws-rds/test/test.option-group.ts @@ -36,7 +36,7 @@ export = { test.done(); }, - 'option group with security groups'(test: Test) { + 'option group with new security group'(test: Test) { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); @@ -96,6 +96,51 @@ export = { test.done(); }, + 'option group with existing security group'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + + // WHEN + const securityGroup = new ec2.SecurityGroup(stack, 'CustomSecurityGroup', { vpc }); + new OptionGroup(stack, 'Options', { + engine: DatabaseInstanceEngine.oracleSe({ + version: OracleLegacyEngineVersion.VER_11_2, + }), + configurations: [ + { + name: 'OEM', + port: 1158, + vpc, + securityGroups: [securityGroup], + }, + ], + }); + + // THEN + expect(stack).to(haveResource('AWS::RDS::OptionGroup', { + EngineName: 'oracle-se', + MajorEngineVersion: '11.2', + OptionGroupDescription: 'Option group for oracle-se 11.2', + OptionConfigurations: [ + { + OptionName: 'OEM', + Port: 1158, + VpcSecurityGroupMemberships: [ + { + 'Fn::GetAtt': [ + 'CustomSecurityGroupE5E500E5', + 'GroupId', + ], + }, + ], + }, + ], + })); + + test.done(); + }, + 'throws when using an option with port and no vpc'(test: Test) { // GIVEN const stack = new cdk.Stack(); From ff33b5416116fd23cf160078bf53651096bde284 Mon Sep 17 00:00:00 2001 From: Nick Lynch Date: Fri, 28 Aug 2020 11:27:29 +0100 Subject: [PATCH 08/15] feat(cloudfront): import existing CloudFrontWebDistributions (#10007) Existing distributions can already been imported with the new (experimental) `Distribution` construct, but the functionality had never been backported to the stable construct. This makes it slightly more discoverable for users who are using the `CloudFrontWebDistribution` construct. Note: Opted in the README to only update the Distribution section, as it felt redundant to have the same README section twice for the two different classes. fixes #5607 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-cloudfront/README.md | 16 +++++++- .../aws-cloudfront/lib/web_distribution.ts | 40 +++++++++++++++++++ .../test/web_distribution.test.ts | 13 ++++++ 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-cloudfront/README.md b/packages/@aws-cdk/aws-cloudfront/README.md index ed8cb9d5eaea2..e4d111c5fb565 100644 --- a/packages/@aws-cdk/aws-cloudfront/README.md +++ b/packages/@aws-cdk/aws-cloudfront/README.md @@ -82,7 +82,7 @@ new cloudfront.Distribution(this, 'myDist', { }); ``` -## From an HTTP endpoint +#### From an HTTP endpoint Origins can also be created from any other HTTP endpoint, given the domain name, and optionally, other origin properties. @@ -246,6 +246,18 @@ new cloudfront.Distribution(this, 'myDist', { }); ``` +### Importing Distributions + +Existing distributions can be imported as well; note that like most imported constructs, an imported distribution cannot be modified. +However, it can be used as a reference for other higher-level constructs. + +```ts +const distribution = cloudfront.Distribution.fromDistributionAttributes(scope, 'ImportedDist', { + domainName: 'd111111abcdef8.cloudfront.net', + distributionId: '012345ABCDEF', +}); +``` + ## CloudFrontWebDistribution API - Stable ![cdk-constructs: Stable](https://img.shields.io/badge/cdk--constructs-stable-success.svg?style=for-the-badge) @@ -305,7 +317,7 @@ Example: [create a distrubution with an iam certificate example](test/example.iam-cert-alias.lit.ts) -#### Restrictions +### Restrictions CloudFront supports adding restrictions to your distribution. diff --git a/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts b/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts index 4a4ebcede0b9b..d0a77ae0239a0 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts @@ -628,6 +628,27 @@ interface BehaviorWithOrigin extends Behavior { readonly targetOriginId: string; } +/** + * Attributes used to import a Distribution. + * + * @experimental + */ +export interface CloudFrontWebDistributionAttributes { + /** + * The generated domain name of the Distribution, such as d111111abcdef8.cloudfront.net. + * + * @attribute + */ + readonly domainName: string; + + /** + * The distribution ID for this distribution. + * + * @attribute + */ + readonly distributionId: string; +} + /** * Amazon CloudFront is a global content delivery network (CDN) service that securely delivers data, videos, * applications, and APIs to your viewers with low latency and high transfer speeds. @@ -659,6 +680,25 @@ interface BehaviorWithOrigin extends Behavior { * @resource AWS::CloudFront::Distribution */ export class CloudFrontWebDistribution extends cdk.Resource implements IDistribution { + + /** + * Creates a construct that represents an external (imported) distribution. + */ + public static fromDistributionAttributes(scope: cdk.Construct, id: string, attrs: CloudFrontWebDistributionAttributes): IDistribution { + return new class extends cdk.Resource implements IDistribution { + public readonly domainName: string; + public readonly distributionDomainName: string; + public readonly distributionId: string; + + constructor() { + super(scope, id); + this.domainName = attrs.domainName; + this.distributionDomainName = attrs.domainName; + this.distributionId = attrs.distributionId; + } + }(); + } + /** * The logging bucket for this CloudFront distribution. * If logging is not enabled for this distribution - this property will be undefined. 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 c6cd639a7262a..d5ed69f694b7c 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/web_distribution.test.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/web_distribution.test.ts @@ -1288,4 +1288,17 @@ nodeunitShim({ }, }, }, + + 'existing distributions can be imported'(test: Test) { + const stack = new cdk.Stack(); + const dist = CloudFrontWebDistribution.fromDistributionAttributes(stack, 'ImportedDist', { + domainName: 'd111111abcdef8.cloudfront.net', + distributionId: '012345ABCDEF', + }); + + test.equals(dist.distributionDomainName, 'd111111abcdef8.cloudfront.net'); + test.equals(dist.distributionId, '012345ABCDEF'); + + test.done(); + }, }); From 9098e295826c09ef568bb8fc03c217ce8a15b822 Mon Sep 17 00:00:00 2001 From: Nick Lynch Date: Fri, 28 Aug 2020 11:52:06 +0100 Subject: [PATCH 09/15] fix(cloudfront): Distribution does not add edgelambda trust policy (#10006) The stable `CloudFrontWebDistribution` construct automatically adds the 'edgelambda.amazonaws.com' trust policy to the Lambda execution role when adding a Lambda@Edge function to the distribution. The newer `Distribution` construct was missing this functionality. Also added an integ test to validate the Lambda@Edge functions can actually be deployed. fixes #9998 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../lib/private/cache-behavior.ts | 18 ++- .../aws-cloudfront/test/distribution.test.ts | 36 ++++++ .../integ.distribution-lambda.expected.json | 103 ++++++++++++++++++ .../test/integ.distribution-lambda.ts | 25 +++++ 4 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda.expected.json create mode 100644 packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda.ts diff --git a/packages/@aws-cdk/aws-cloudfront/lib/private/cache-behavior.ts b/packages/@aws-cdk/aws-cloudfront/lib/private/cache-behavior.ts index 18b4d75aac2c7..09082c1dd4f8b 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/private/cache-behavior.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/private/cache-behavior.ts @@ -1,5 +1,6 @@ +import * as iam from '@aws-cdk/aws-iam'; import { CfnDistribution } from '../cloudfront.generated'; -import { AddBehaviorOptions, ViewerProtocolPolicy } from '../distribution'; +import { AddBehaviorOptions, EdgeLambda, ViewerProtocolPolicy } from '../distribution'; /** * Properties for specifying custom behaviors for origins. @@ -24,6 +25,8 @@ export class CacheBehavior { constructor(originId: string, private readonly props: CacheBehaviorProps) { this.originId = originId; + + this.grantEdgeLambdaFunctionExecutionRole(props.edgeLambdas); } /** @@ -55,4 +58,17 @@ export class CacheBehavior { : undefined, }; } + + private grantEdgeLambdaFunctionExecutionRole(edgeLambdas?: EdgeLambda[]) { + if (!edgeLambdas || edgeLambdas.length === 0) { return; } + edgeLambdas.forEach((edgeLambda) => { + const role = edgeLambda.functionVersion.role; + if (role && role instanceof iam.Role && role.assumeRolePolicy) { + role.assumeRolePolicy.addStatements(new iam.PolicyStatement({ + actions: ['sts:AssumeRole'], + principals: [new iam.ServicePrincipal('edgelambda.amazonaws.com')], + })); + } + }); + } } diff --git a/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts b/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts index 0a8b5933e7f59..b4368de309232 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts @@ -489,6 +489,42 @@ describe('with Lambda@Edge functions', () => { }); }); + test('edgelambda.amazonaws.com is added to the trust policy of lambda', () => { + new Distribution(stack, 'MyDist', { + defaultBehavior: { + origin, + edgeLambdas: [ + { + functionVersion: lambdaFunction.currentVersion, + eventType: LambdaEdgeEventType.ORIGIN_REQUEST, + }, + ], + }, + }); + + expect(stack).toHaveResource('AWS::IAM::Role', { + AssumeRolePolicyDocument: { + Statement: [ + { + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + Service: 'lambda.amazonaws.com', + }, + }, + { + Action: 'sts:AssumeRole', + Effect: 'Allow', + Principal: { + Service: 'edgelambda.amazonaws.com', + }, + }, + ], + Version: '2012-10-17', + }, + }); + }); + test('can add an edge lambdas to additional behaviors', () => { new Distribution(stack, 'MyDist', { defaultBehavior: { origin }, diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda.expected.json b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda.expected.json new file mode 100644 index 0000000000000..ef6030afded59 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda.expected.json @@ -0,0 +1,103 @@ +{ + "Resources": { + "LambdaServiceRoleA8ED4D3B": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "lambda.amazonaws.com" + } + }, + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "edgelambda.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition" + }, + ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ] + ] + } + ] + } + }, + "LambdaD247545B": { + "Type": "AWS::Lambda::Function", + "Properties": { + "Code": { + "ZipFile": "foo" + }, + "Handler": "index.handler", + "Role": { + "Fn::GetAtt": [ + "LambdaServiceRoleA8ED4D3B", + "Arn" + ] + }, + "Runtime": "nodejs10.x" + }, + "DependsOn": [ + "LambdaServiceRoleA8ED4D3B" + ] + }, + "LambdaCurrentVersionDF706F6A97fb843e9bd06fcd2bb15eeace80e13e": { + "Type": "AWS::Lambda::Version", + "Properties": { + "FunctionName": { + "Ref": "LambdaD247545B" + } + } + }, + "DistB3B78991": { + "Type": "AWS::CloudFront::Distribution", + "Properties": { + "DistributionConfig": { + "DefaultCacheBehavior": { + "ForwardedValues": { + "QueryString": false + }, + "LambdaFunctionAssociations": [ + { + "EventType": "origin-request", + "LambdaFunctionARN": { + "Ref": "LambdaCurrentVersionDF706F6A97fb843e9bd06fcd2bb15eeace80e13e" + } + } + ], + "TargetOriginId": "integdistributionlambdaDistOrigin133A13098", + "ViewerProtocolPolicy": "allow-all" + }, + "Enabled": true, + "HttpVersion": "http2", + "IPV6Enabled": true, + "Origins": [ + { + "CustomOriginConfig": { + "OriginProtocolPolicy": "https-only" + }, + "DomainName": "www.example.com", + "Id": "integdistributionlambdaDistOrigin133A13098" + } + ] + } + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda.ts b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda.ts new file mode 100644 index 0000000000000..52bc6c1a320c5 --- /dev/null +++ b/packages/@aws-cdk/aws-cloudfront/test/integ.distribution-lambda.ts @@ -0,0 +1,25 @@ +import * as lambda from '@aws-cdk/aws-lambda'; +import * as cdk from '@aws-cdk/core'; +import * as cloudfront from '../lib'; +import { TestOrigin } from './test-origin'; + +const app = new cdk.App(); +const stack = new cdk.Stack(app, 'integ-distribution-lambda', { env: { region: 'us-east-1' } }); + +const lambdaFunction = new lambda.Function(stack, 'Lambda', { + code: lambda.Code.fromInline('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, +}); + +new cloudfront.Distribution(stack, 'Dist', { + defaultBehavior: { + origin: new TestOrigin('www.example.com'), + edgeLambdas: [{ + functionVersion: lambdaFunction.currentVersion, + eventType: cloudfront.LambdaEdgeEventType.ORIGIN_REQUEST, + }], + }, +}); + +app.synth(); From 95c0332395d1203e8b00fda153fe08e70d0387c5 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Fri, 28 Aug 2020 13:18:26 +0200 Subject: [PATCH 10/15] fix(cli): AssumeRole profiles require a [default] profile (#10032) This works around a bug in the AWS SDK for JS that only surfaced when we switched to `AWS_STS_REGIONAL_ENDPOINTS=regional`, requiring a `[default]` profile with a region for all users. The bug was that the INI-file AssumeRole provider would ignore the region in the profile, and always fall back to the region in: * The profile specified using `$AWS_PROFILE` (which we don't use). * Otherwise the region in the `[default]` profile (which a user may or may not have). Traditionally it didn't really matter whether the STS client got a region or not because it would always connect to `us-east-1` no matter what, but when we switched to `AWS_STS_REGIONAL_ENDPOINTS=regional`, it became illegal to not have a region. Fix the upstream bug by basically replicating the important parts of `SharedIniFileCredentials` of the AWS SDK in our codebase and patching the bug. Reported upstreeam as https://github.com/aws/aws-sdk-js/issues/3418 Fixes #9937 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../lib/api/aws-auth/aws-sdk-inifile.ts | 161 ++++++++ .../lib/api/aws-auth/awscli-compatible.ts | 4 +- .../aws-cdk/test/api/sdk-provider.test.ts | 378 ++++++++++-------- 3 files changed, 380 insertions(+), 163 deletions(-) create mode 100644 packages/aws-cdk/lib/api/aws-auth/aws-sdk-inifile.ts diff --git a/packages/aws-cdk/lib/api/aws-auth/aws-sdk-inifile.ts b/packages/aws-cdk/lib/api/aws-auth/aws-sdk-inifile.ts new file mode 100644 index 0000000000000..af12393fff381 --- /dev/null +++ b/packages/aws-cdk/lib/api/aws-auth/aws-sdk-inifile.ts @@ -0,0 +1,161 @@ +import * as AWS from 'aws-sdk'; + + +/** + * Hack-fix + * + * There are a number of issues in the upstream version of SharedIniFileCredentials + * that need fixing: + * + * 1. The upstream aws-sdk contains an incorrect instantiation of an `AWS.STS` + * client, which *should* have taken the region from the requested profile + * but doesn't. It will use the region from the default profile, which + * may not exist, defaulting to `us-east-1` (since we switched to + * AWS_STS_REGIONAL_ENDPOINTS=regional, that default is not even allowed anymore + * and the absence of a default region will lead to an error). + * + * 2. The simple fix is to get the region from the `config` file. profiles + * are made up of a combination of `credentials` and `config`, and the region is + * generally in `config` with the rest in `credentials`. However, a bug in + * `getProfilesFromSharedConfig` overwrites ALL `config` data with `credentials` + * data, so we also need to do extra work to fish the `region` out of the config. + * + * See https://github.com/aws/aws-sdk-js/issues/3418 for all the gory details. + */ +export class PatchedSharedIniFileCredentials extends AWS.SharedIniFileCredentials { + declare private profile: string; + declare private filename: string; + declare private disableAssumeRole: boolean; + declare private options: Record; + declare private roleArn: string; + declare private httpOptions?: AWS.HTTPOptions; + declare private tokenCodeFn?: (mfaSerial: string, callback: (err?: Error, token?: string) => void) => void; + + public loadRoleProfile( + creds: Record>, + roleProfile: Record, + callback: (err?: Error, data?: any) => void) { + + // Need to duplicate the whole implementation here -- the function is long and has been written in + // such a way that there are no small monkey patches possible. + + if (this.disableAssumeRole) { + throw (AWS as any).util.error( + new Error('Role assumption profiles are disabled. ' + + 'Failed to load profile ' + this.profile + + ' from ' + creds.filename), + { code: 'SharedIniFileCredentialsProviderFailure' }, + ); + } + + var self = this; + var roleArn = roleProfile.role_arn; + var roleSessionName = roleProfile.role_session_name; + var externalId = roleProfile.external_id; + var mfaSerial = roleProfile.mfa_serial; + var sourceProfileName = roleProfile.source_profile; + + if (!sourceProfileName) { + throw (AWS as any).util.error( + new Error('source_profile is not set using profile ' + this.profile), + { code: 'SharedIniFileCredentialsProviderFailure' }, + ); + } + + var sourceProfileExistanceTest = creds[sourceProfileName]; + + if (typeof sourceProfileExistanceTest !== 'object') { + throw (AWS as any).util.error( + new Error('source_profile ' + sourceProfileName + ' using profile ' + + this.profile + ' does not exist'), + { code: 'SharedIniFileCredentialsProviderFailure' }, + ); + } + + var sourceCredentials = new AWS.SharedIniFileCredentials( + (AWS as any).util.merge(this.options || {}, { + profile: sourceProfileName, + preferStaticCredentials: true, + }), + ); + + // --------- THIS IS NEW ---------------------- + const profiles = loadProfilesProper(this.filename); + const region = profiles[this.profile]?.region ?? profiles.default?.region ?? 'us-east-1'; + // --------- /THIS IS NEW ---------------------- + + this.roleArn = roleArn; + var sts = new AWS.STS({ + credentials: sourceCredentials, + region, + httpOptions: this.httpOptions, + }); + + var roleParams: AWS.STS.AssumeRoleRequest = { + RoleArn: roleArn, + RoleSessionName: roleSessionName || 'aws-sdk-js-' + Date.now(), + }; + + if (externalId) { + roleParams.ExternalId = externalId; + } + + if (mfaSerial && self.tokenCodeFn) { + roleParams.SerialNumber = mfaSerial; + self.tokenCodeFn(mfaSerial, function(err, token) { + if (err) { + var message; + if (err instanceof Error) { + message = err.message; + } else { + message = err; + } + callback( + (AWS as any).util.error( + new Error('Error fetching MFA token: ' + message), + { code: 'SharedIniFileCredentialsProviderFailure' }, + )); + return; + } + + roleParams.TokenCode = token; + sts.assumeRole(roleParams, callback); + }); + return; + } + sts.assumeRole(roleParams, callback); + + } +} + +/** + * A function to load profiles from disk that MERGES credentials and config instead of overwriting + * + * @see https://github.com/aws/aws-sdk-js/blob/5ae5a7d7d24d1000dbc089cc15f8ed2c7b06c542/lib/util.js#L956 + */ +function loadProfilesProper(filename: string) { + const util = (AWS as any).util; // Does exists even though there aren't any typings for it + const iniLoader = util.iniLoader; + const profiles: Record> = {}; + let profilesFromConfig: Record> = {}; + if (process.env[util.configOptInEnv]) { + profilesFromConfig = iniLoader.loadFrom({ + isConfig: true, + filename: process.env[util.sharedConfigFileEnv], + }); + } + var profilesFromCreds: Record> = iniLoader.loadFrom({ + filename: filename || + (process.env[util.configOptInEnv] && process.env[util.sharedCredentialsFileEnv]), + }); + for (const [name, profile] of Object.entries(profilesFromConfig)) { + profiles[name] = profile; + } + for (const [name, profile] of Object.entries(profilesFromCreds)) { + profiles[name] = { + ...profiles[name], + ...profile, + }; + } + return profiles; +} diff --git a/packages/aws-cdk/lib/api/aws-auth/awscli-compatible.ts b/packages/aws-cdk/lib/api/aws-auth/awscli-compatible.ts index 6d85e08310128..515c0a548643a 100644 --- a/packages/aws-cdk/lib/api/aws-auth/awscli-compatible.ts +++ b/packages/aws-cdk/lib/api/aws-auth/awscli-compatible.ts @@ -6,6 +6,7 @@ import * as AWS from 'aws-sdk'; import * as fs from 'fs-extra'; import * as promptly from 'promptly'; import { debug } from '../../logging'; +import { PatchedSharedIniFileCredentials } from './aws-sdk-inifile'; import { SharedIniFile } from './sdk_ini_file'; /** @@ -44,7 +45,7 @@ export class AwsCliCompatible { // Force reading the `config` file if it exists by setting the appropriate // environment variable. await forceSdkToReadConfigIfPresent(); - sources.push(() => new AWS.SharedIniFileCredentials({ + sources.push(() => new PatchedSharedIniFileCredentials({ profile, filename: credentialsFileName(), httpOptions: options.httpOptions, @@ -310,3 +311,4 @@ async function tokenCodeFn(serialArn: string, cb: (err?: Error, token?: string) cb(err); } } + diff --git a/packages/aws-cdk/test/api/sdk-provider.test.ts b/packages/aws-cdk/test/api/sdk-provider.test.ts index 9d7bd5d701e39..e72225bbf82ee 100644 --- a/packages/aws-cdk/test/api/sdk-provider.test.ts +++ b/packages/aws-cdk/test/api/sdk-provider.test.ts @@ -32,53 +32,6 @@ beforeEach(() => { logging.setLogLevel(logging.LogLevel.TRACE); - bockfs({ - '/home/me/.bxt/credentials': dedent(` - [default] - aws_access_key_id=${uid}access - aws_secret_access_key=secret - - [foo] - aws_access_key_id=${uid}fooccess - aws_secret_access_key=secret - - [assumer] - aws_access_key_id=${uid}assumer - aws_secret_access_key=secret - - [mfa] - aws_access_key_id=${uid}mfaccess - aws_secret_access_key=secret - `), - '/home/me/.bxt/config': dedent(` - [default] - region=eu-bla-5 - - [profile foo] - region=eu-west-1 - - [profile boo] - aws_access_key_id=${uid}booccess - aws_secret_access_key=boocret - # No region here - - [profile assumable] - role_arn=arn:aws:iam::12356789012:role/Assumable - source_profile=assumer - - [profile assumer] - region=us-east-2 - - [profile mfa] - region=eu-west-1 - - [profile mfa-role] - source_profile=mfa - role_arn=arn:aws:iam::account:role/role - mfa_serial=arn:aws:iam::account:mfa/user - `), - }); - SDKMock.mock('STS', 'getCallerIdentity', (cb: AwsCallback) => { return cb(null, { Account: `${uid}the_account_#`, @@ -104,10 +57,8 @@ beforeEach(() => { defaultEnv = cxapi.EnvironmentUtils.make(`${uid}the_account_#`, 'def'); - // Set environment variables that we want - process.env.AWS_CONFIG_FILE = bockfs.path('/home/me/.bxt/config'); - process.env.AWS_SHARED_CREDENTIALS_FILE = bockfs.path('/home/me/.bxt/credentials'); // Scrub some environment variables that might be set if we're running on CodeBuild which will interfere with the tests. + delete process.env.AWS_PROFILE; delete process.env.AWS_REGION; delete process.env.AWS_DEFAULT_REGION; delete process.env.AWS_ACCESS_KEY_ID; @@ -122,146 +73,249 @@ afterEach(() => { bockfs.restore(); }); -describe('CLI compatible credentials loading', () => { - test('default config credentials', async () => { - // WHEN - const provider = await SdkProvider.withAwsCliCompatibleDefaults({ ...defaultCredOptions }); - - // THEN - expect(provider.defaultRegion).toEqual('eu-bla-5'); - await expect(provider.defaultAccount()).resolves.toEqual({ accountId: `${uid}the_account_#`, partition: 'aws-here' }); - const sdk = await provider.forEnvironment({ ...defaultEnv, region: 'rgn' }, Mode.ForReading); - expect(sdkConfig(sdk).credentials!.accessKeyId).toEqual(`${uid}access`); - expect(sdkConfig(sdk).region).toEqual('rgn'); - }); - - test('unknown account and region uses current', async () => { - // WHEN - const provider = await SdkProvider.withAwsCliCompatibleDefaults({ ...defaultCredOptions }); +describe('with default config files', () => { + beforeEach(() => { + bockfs({ + '/home/me/.bxt/credentials': dedent(` + [default] + aws_access_key_id=${uid}access + aws_secret_access_key=secret + + [foo] + aws_access_key_id=${uid}fooccess + aws_secret_access_key=secret + + [assumer] + aws_access_key_id=${uid}assumer + aws_secret_access_key=secret + + [mfa] + aws_access_key_id=${uid}mfaccess + aws_secret_access_key=secret + `), + '/home/me/.bxt/config': dedent(` + [default] + region=eu-bla-5 + + [profile foo] + region=eu-west-1 + + [profile boo] + aws_access_key_id=${uid}booccess + aws_secret_access_key=boocret + # No region here + + [profile assumable] + role_arn=arn:aws:iam::12356789012:role/Assumable + source_profile=assumer + + [profile assumer] + region=us-east-2 + + [profile mfa] + region=eu-west-1 + + [profile mfa-role] + source_profile=mfa + role_arn=arn:aws:iam::account:role/role + mfa_serial=arn:aws:iam::account:mfa/user + `), + }); - // THEN - const sdk = await provider.forEnvironment(cxapi.EnvironmentUtils.make(cxapi.UNKNOWN_ACCOUNT, cxapi.UNKNOWN_REGION), Mode.ForReading); - expect(sdkConfig(sdk).credentials!.accessKeyId).toEqual(`${uid}access`); - expect(sdkConfig(sdk).region).toEqual('eu-bla-5'); + // Set environment variables that we want + process.env.AWS_CONFIG_FILE = bockfs.path('/home/me/.bxt/config'); + process.env.AWS_SHARED_CREDENTIALS_FILE = bockfs.path('/home/me/.bxt/credentials'); }); - test('mixed profile credentials', async () => { - // WHEN - const provider = await SdkProvider.withAwsCliCompatibleDefaults({ ...defaultCredOptions, profile: 'foo' }); + describe('CLI compatible credentials loading', () => { + test('default config credentials', async () => { + // WHEN + const provider = await SdkProvider.withAwsCliCompatibleDefaults({ ...defaultCredOptions }); + + // THEN + expect(provider.defaultRegion).toEqual('eu-bla-5'); + await expect(provider.defaultAccount()).resolves.toEqual({ accountId: `${uid}the_account_#`, partition: 'aws-here' }); + const sdk = await provider.forEnvironment({ ...defaultEnv, region: 'rgn' }, Mode.ForReading); + expect(sdkConfig(sdk).credentials!.accessKeyId).toEqual(`${uid}access`); + expect(sdkConfig(sdk).region).toEqual('rgn'); + }); - // THEN - expect(provider.defaultRegion).toEqual('eu-west-1'); - await expect(provider.defaultAccount()).resolves.toEqual({ accountId: `${uid}the_account_#`, partition: 'aws-here' }); - const sdk = await provider.forEnvironment(defaultEnv, Mode.ForReading); - expect(sdkConfig(sdk).credentials!.accessKeyId).toEqual(`${uid}fooccess`); - }); + test('unknown account and region uses current', async () => { + // WHEN + const provider = await SdkProvider.withAwsCliCompatibleDefaults({ ...defaultCredOptions }); - test('pure config credentials', async () => { - // WHEN - const provider = await SdkProvider.withAwsCliCompatibleDefaults({ ...defaultCredOptions, profile: 'boo' }); + // THEN + const sdk = await provider.forEnvironment(cxapi.EnvironmentUtils.make(cxapi.UNKNOWN_ACCOUNT, cxapi.UNKNOWN_REGION), Mode.ForReading); + expect(sdkConfig(sdk).credentials!.accessKeyId).toEqual(`${uid}access`); + expect(sdkConfig(sdk).region).toEqual('eu-bla-5'); + }); - // THEN - expect(provider.defaultRegion).toEqual('eu-bla-5'); // Fall back to default config - await expect(provider.defaultAccount()).resolves.toEqual({ accountId: `${uid}the_account_#`, partition: 'aws-here' }); - const sdk = await provider.forEnvironment(defaultEnv, Mode.ForReading); - expect(sdkConfig(sdk).credentials!.accessKeyId).toEqual(`${uid}booccess`); - }); + test('mixed profile credentials', async () => { + // WHEN + const provider = await SdkProvider.withAwsCliCompatibleDefaults({ ...defaultCredOptions, profile: 'foo' }); - test('mfa_serial in profile will ask user for token', async () => { - // WHEN - const provider = await SdkProvider.withAwsCliCompatibleDefaults({ ...defaultCredOptions, profile: 'mfa-role' }); - - // THEN - try { - await provider.withAssumedRole('arn:aws:iam::account:role/role', undefined, undefined); - } catch (e) { - // Mock response was set to fail with message test to make sure we don't call STS - expect(e.message).toEqual('Error fetching MFA token: test'); - } - }); + // THEN + expect(provider.defaultRegion).toEqual('eu-west-1'); + await expect(provider.defaultAccount()).resolves.toEqual({ accountId: `${uid}the_account_#`, partition: 'aws-here' }); + const sdk = await provider.forEnvironment(defaultEnv, Mode.ForReading); + expect(sdkConfig(sdk).credentials!.accessKeyId).toEqual(`${uid}fooccess`); + }); - test('different account throws', async () => { - const provider = await SdkProvider.withAwsCliCompatibleDefaults({ ...defaultCredOptions, profile: 'boo' }); + test('pure config credentials', async () => { + // WHEN + const provider = await SdkProvider.withAwsCliCompatibleDefaults({ ...defaultCredOptions, profile: 'boo' }); - await expect(provider.forEnvironment({ ...defaultEnv, account: `${uid}some_account_#` }, Mode.ForReading)).rejects.toThrow('Need to perform AWS calls'); - }); + // THEN + expect(provider.defaultRegion).toEqual('eu-bla-5'); // Fall back to default config + await expect(provider.defaultAccount()).resolves.toEqual({ accountId: `${uid}the_account_#`, partition: 'aws-here' }); + const sdk = await provider.forEnvironment(defaultEnv, Mode.ForReading); + expect(sdkConfig(sdk).credentials!.accessKeyId).toEqual(`${uid}booccess`); + }); - test('even when using a profile to assume another profile, STS calls goes through the proxy', async () => { - // Messy mocking - let called = false; - jest.mock('proxy-agent', () => { - // eslint-disable-next-line @typescript-eslint/no-require-imports - class FakeAgent extends require('https').Agent { - public addRequest(_: any, __: any) { - // FIXME: this error takes 6 seconds to be completely handled. It - // might be retries in the SDK somewhere, or something about the Node - // event loop. I've spent an hour trying to figure it out and I can't, - // and I gave up. We'll just have to live with this until someone gets - // inspired. - const error = new Error('ABORTED BY TEST'); - (error as any).code = 'RequestAbortedError'; - (error as any).retryable = false; - called = true; - throw error; - } + test('mfa_serial in profile will ask user for token', async () => { + // WHEN + const provider = await SdkProvider.withAwsCliCompatibleDefaults({ ...defaultCredOptions, profile: 'mfa-role' }); + + // THEN + try { + await provider.withAssumedRole('arn:aws:iam::account:role/role', undefined, undefined); + } catch (e) { + // Mock response was set to fail with message test to make sure we don't call STS + expect(e.message).toEqual('Error fetching MFA token: test'); } - return FakeAgent; }); - // WHEN - const provider = await SdkProvider.withAwsCliCompatibleDefaults({ - ...defaultCredOptions, - profile: 'assumable', - httpOptions: { - proxyAddress: 'http://DOESNTMATTER/', - }, + test('different account throws', async () => { + const provider = await SdkProvider.withAwsCliCompatibleDefaults({ ...defaultCredOptions, profile: 'boo' }); + + await expect(provider.forEnvironment({ ...defaultEnv, account: `${uid}some_account_#` }, Mode.ForReading)).rejects.toThrow('Need to perform AWS calls'); }); - await provider.defaultAccount(); + test('even when using a profile to assume another profile, STS calls goes through the proxy', async () => { + // Messy mocking + let called = false; + jest.mock('proxy-agent', () => { + // eslint-disable-next-line @typescript-eslint/no-require-imports + class FakeAgent extends require('https').Agent { + public addRequest(_: any, __: any) { + // FIXME: this error takes 6 seconds to be completely handled. It + // might be retries in the SDK somewhere, or something about the Node + // event loop. I've spent an hour trying to figure it out and I can't, + // and I gave up. We'll just have to live with this until someone gets + // inspired. + const error = new Error('ABORTED BY TEST'); + (error as any).code = 'RequestAbortedError'; + (error as any).retryable = false; + called = true; + throw error; + } + } + return FakeAgent; + }); - // THEN -- the fake proxy agent got called, we don't care about the result - expect(called).toEqual(true); - }); + // WHEN + const provider = await SdkProvider.withAwsCliCompatibleDefaults({ + ...defaultCredOptions, + profile: 'assumable', + httpOptions: { + proxyAddress: 'http://DOESNTMATTER/', + }, + }); + + await provider.defaultAccount(); + + // THEN -- the fake proxy agent got called, we don't care about the result + expect(called).toEqual(true); + }); + + test('error we get from assuming a role is useful', async () => { + // GIVEN + // Because of the way ChainableTemporaryCredentials gets its STS client, it's not mockable + // using 'mock-aws-sdk'. So instead, we have to mess around with its internals. + function makeAssumeRoleFail(s: ISDK) { + (s as any).credentials.service.assumeRole = jest.fn().mockImplementation((_request, cb) => { + cb(new Error('Nope!')); + }); + } - test('error we get from assuming a role is useful', async () => { - // GIVEN - // Because of the way ChainableTemporaryCredentials gets its STS client, it's not mockable - // using 'mock-aws-sdk'. So instead, we have to mess around with its internals. - function makeAssumeRoleFail(s: ISDK) { - (s as any).credentials.service.assumeRole = jest.fn().mockImplementation((_request, cb) => { - cb(new Error('Nope!')); + const provider = await SdkProvider.withAwsCliCompatibleDefaults({ + ...defaultCredOptions, + httpOptions: { + proxyAddress: 'http://localhost:8080/', + }, }); - } - const provider = await SdkProvider.withAwsCliCompatibleDefaults({ - ...defaultCredOptions, - httpOptions: { - proxyAddress: 'http://localhost:8080/', - }, + // WHEN + const sdk = await provider.withAssumedRole('bla.role.arn', undefined, undefined); + makeAssumeRoleFail(sdk); + + // THEN - error message contains both a helpful hint and the underlying AssumeRole message + await expect(sdk.s3().listBuckets().promise()).rejects.toThrow('did you bootstrap'); + await expect(sdk.s3().listBuckets().promise()).rejects.toThrow('Nope!'); }); + }); - // WHEN - const sdk = await provider.withAssumedRole('bla.role.arn', undefined, undefined); - makeAssumeRoleFail(sdk); + describe('Plugins', () => { + test('does not use plugins if current credentials are for expected account', async () => { + const provider = await SdkProvider.withAwsCliCompatibleDefaults({ ...defaultCredOptions }); + await provider.forEnvironment(defaultEnv, Mode.ForReading); + expect(pluginQueried).toEqual(false); + }); - // THEN - error message contains both a helpful hint and the underlying AssumeRole message - await expect(sdk.s3().listBuckets().promise()).rejects.toThrow('did you bootstrap'); - await expect(sdk.s3().listBuckets().promise()).rejects.toThrow('Nope!'); + test('uses plugin for other account', async () => { + const provider = await SdkProvider.withAwsCliCompatibleDefaults({ ...defaultCredOptions }); + await provider.forEnvironment({ ...defaultEnv, account: `${uid}plugin_account_#` }, Mode.ForReading); + expect(pluginQueried).toEqual(true); + }); }); }); -describe('Plugins', () => { - test('does not use plugins if current credentials are for expected account', async () => { - const provider = await SdkProvider.withAwsCliCompatibleDefaults({ ...defaultCredOptions }); - await provider.forEnvironment(defaultEnv, Mode.ForReading); - expect(pluginQueried).toEqual(false); +test('can assume role without a [default] profile', async () => { + // GIVEN + bockfs({ + '/home/me/.bxt/credentials': dedent(` + [assumer] + aws_access_key_id=${uid}assumer + aws_secret_access_key=secret + + [assumable] + role_arn=arn:aws:iam::12356789012:role/Assumable + source_profile=assumer + `), + '/home/me/.bxt/config': dedent(` + [profile assumable] + region=eu-bla-5 + `), + }); + + SDKMock.mock('STS', 'assumeRole', (_request: AWS.STS.AssumeRoleRequest, cb: AwsCallback) => { + return cb(null, { + Credentials: { + AccessKeyId: `${uid}access`, // Needs UID in here otherwise key will be cached + Expiration: new Date(Date.now() + 10000), + SecretAccessKey: 'b', + SessionToken: 'c', + }, + }); }); - test('uses plugin for other account', async () => { - const provider = await SdkProvider.withAwsCliCompatibleDefaults({ ...defaultCredOptions }); - await provider.forEnvironment({ ...defaultEnv, account: `${uid}plugin_account_#` }, Mode.ForReading); - expect(pluginQueried).toEqual(true); + // Set environment variables that we want + process.env.AWS_CONFIG_FILE = bockfs.path('/home/me/.bxt/config'); + process.env.AWS_SHARED_CREDENTIALS_FILE = bockfs.path('/home/me/.bxt/credentials'); + + // WHEN + const provider = await SdkProvider.withAwsCliCompatibleDefaults({ + ...defaultCredOptions, + profile: 'assumable', + httpOptions: { + proxyAddress: 'http://DOESNTMATTER/', + }, }); + + const account = await provider.defaultAccount(); + + // THEN + expect(account?.accountId).toEqual(`${uid}the_account_#`); }); /** From d748f4400e28fcb0933df6c57df36740381deff3 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Fri, 28 Aug 2020 13:42:30 +0200 Subject: [PATCH 11/15] fix(lambda): grantInvoke fails for imported IAM identities (#9957) We used to erroneously assume that IAM identities imported into the same Stack object (imported Roles specifically) would always belong to the same account as the resources in the stack, and so try to add `Invoke` permissions to the identity policy, which would silently fail. In a recent change, we started recognizing the account of the Role properly and so now we detect that we must actually ALSO add permission to the Lambda itself (resource policies). Unfortunately the Lambda IAM-to-Lambda-Permissions translator had a list of special recognized classes that did not include imported Roles, and so this would fail. Add another case where we try a more generic fallback by parsing the policy principal. This should catch most simple principals that Lambda Permissions supports. Fixes #9883. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../@aws-cdk/aws-lambda/lib/function-base.ts | 27 +++++++- .../@aws-cdk/aws-lambda/test/test.lambda.ts | 62 ++++++++++++++++++- 2 files changed, 87 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda/lib/function-base.ts b/packages/@aws-cdk/aws-lambda/lib/function-base.ts index f7b048969b185..96eb204852563 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function-base.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function-base.ts @@ -346,12 +346,24 @@ export abstract class FunctionBase extends Resource implements IFunction { return this.node; } + /** + * Translate IPrincipal to something we can pass to AWS::Lambda::Permissions + * + * Do some nasty things because `Permission` supports a subset of what the + * full IAM principal language supports, and we may not be able to parse strings + * outright because they may be tokens. + * + * Try to recognize some specific Principal classes first, then try a generic + * fallback. + */ private parsePermissionPrincipal(principal?: iam.IPrincipal) { if (!principal) { return undefined; } - // use duck-typing, not instance of + // Try some specific common classes first. + // use duck-typing, not instance of + // @deprecated: after v2, we can change these to 'instanceof' if ('accountId' in principal) { return (principal as iam.AccountPrincipal).accountId; } @@ -364,6 +376,19 @@ export abstract class FunctionBase extends Resource implements IFunction { return (principal as iam.ArnPrincipal).arn; } + // Try a best-effort approach to support simple principals that are not any of the predefined + // classes, but are simple enough that they will fit into the Permission model. Main target + // here: imported Roles, Users, Groups. + // + // The principal cannot have conditions and must have a single { AWS: [arn] } entry. + const json = principal.policyFragment.principalJson; + if (Object.keys(principal.policyFragment.conditions).length === 0 && json.AWS) { + if (typeof json.AWS === 'string') { return json.AWS; } + if (Array.isArray(json.AWS) && json.AWS.length === 1 && typeof json.AWS[0] === 'string') { + return json.AWS[0]; + } + } + throw new Error(`Invalid principal type for Lambda permission statement: ${principal.constructor.name}. ` + 'Supported: AccountPrincipal, ArnPrincipal, ServicePrincipal'); } diff --git a/packages/@aws-cdk/aws-lambda/test/test.lambda.ts b/packages/@aws-cdk/aws-lambda/test/test.lambda.ts index 2a86ee4bbfbe9..c399a70e5e6ec 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.lambda.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.lambda.ts @@ -1,5 +1,5 @@ import * as path from 'path'; -import { ABSENT, expect, haveResource, MatchStyle, ResourcePart } from '@aws-cdk/assert'; +import { ABSENT, expect, haveResource, MatchStyle, ResourcePart, arrayWith, objectLike } from '@aws-cdk/assert'; import * as ec2 from '@aws-cdk/aws-ec2'; import * as iam from '@aws-cdk/aws-iam'; import * as logs from '@aws-cdk/aws-logs'; @@ -1168,6 +1168,66 @@ export = { }, }, + 'grantInvoke with an imported role (in the same account)'(test: Test) { + // GIVEN + const stack = new cdk.Stack(undefined, undefined, { + env: { account: '123456789012' }, + }); + const fn = new lambda.Function(stack, 'Function', { + code: lambda.Code.fromInline('xxx'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + }); + + // WHEN + fn.grantInvoke(iam.Role.fromRoleArn(stack, 'ForeignRole', 'arn:aws:iam::123456789012:role/someRole')); + + // THEN + expect(stack).to(haveResource('AWS::IAM::Policy', { + PolicyDocument: objectLike({ + Statement: arrayWith( + { + Action: 'lambda:InvokeFunction', + Effect: 'Allow', + Resource: { 'Fn::GetAtt': ['Function76856677', 'Arn'] }, + }, + ), + }), + Roles: ['someRole'], + })); + + test.done(); + }, + + 'grantInvoke with an imported role (from a different account)'(test: Test) { + // GIVEN + const stack = new cdk.Stack(undefined, undefined, { + env: { account: '3333' }, + }); + const fn = new lambda.Function(stack, 'Function', { + code: lambda.Code.fromInline('xxx'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + }); + + // WHEN + fn.grantInvoke(iam.Role.fromRoleArn(stack, 'ForeignRole', 'arn:aws:iam::123456789012:role/someRole')); + + // THEN + expect(stack).to(haveResource('AWS::Lambda::Permission', { + Action: 'lambda:InvokeFunction', + FunctionName: { + 'Fn::GetAtt': [ + 'Function76856677', + 'Arn', + ], + }, + Principal: 'arn:aws:iam::123456789012:role/someRole', + })); + + test.done(); + }, + 'Can use metricErrors on a lambda Function'(test: Test) { // GIVEN const stack = new cdk.Stack(); From d10b829a5014430cb28086a709b61f592f5a3f1d Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Fri, 28 Aug 2020 14:29:29 +0200 Subject: [PATCH 12/15] chore(monocdk-experiment): `constructs` should NOT be a dependency (#10037) Move `constructs` from `dependencies` to `devDependencies`. The whole point of the monocdk reorganization is that dependencies whose types are exposed in a package's public API (of which `constructs`, delivering the `Construct` class, is definitely one), should NOT be in `dependencies` but in `peerDependencies`. It was probably added to `dependencies` to make the build work. The correct place to put a dependency for build purposes is in `devDependencies.` ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/monocdk-experiment/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/monocdk-experiment/package.json b/packages/monocdk-experiment/package.json index c77e56abe1f37..e4eb7cf36e3cc 100644 --- a/packages/monocdk-experiment/package.json +++ b/packages/monocdk-experiment/package.json @@ -89,7 +89,6 @@ ], "dependencies": { "case": "1.6.3", - "constructs": "^3.0.4", "fs-extra": "^9.0.1", "jsonschema": "^1.2.5", "minimatch": "^3.0.4", @@ -97,6 +96,7 @@ "yaml": "1.10.0" }, "devDependencies": { + "constructs": "^3.0.4", "@aws-cdk/alexa-ask": "0.0.0", "@aws-cdk/app-delivery": "0.0.0", "@aws-cdk/assets": "0.0.0", From 9ffb2682c167fe92e302bc322d60b9ae37de934a Mon Sep 17 00:00:00 2001 From: Nick Lynch Date: Fri, 28 Aug 2020 13:54:12 +0100 Subject: [PATCH 13/15] feat(cloudfront): support includeBody for Lambda@Edge (#10008) The `includeBody` flag enables a Lambda@Edge function to receive the body of the request or response. Enabled this flag for both the `CloudFrontWebDistribution` and `Distribution` constructs. fixes #7085 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-cloudfront/README.md | 1 + .../aws-cloudfront/lib/distribution.ts | 9 +++++ .../lib/private/cache-behavior.ts | 11 +++++- .../aws-cloudfront/lib/web_distribution.ts | 15 ++++++++ .../aws-cloudfront/test/distribution.test.ts | 2 ++ .../test/private/cache-behavior.test.ts | 30 ++++++++++++++-- .../test/web_distribution.test.ts | 34 +++++++++++++++++++ 7 files changed, 99 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/aws-cloudfront/README.md b/packages/@aws-cdk/aws-cloudfront/README.md index e4d111c5fb565..fd3aac1b32bba 100644 --- a/packages/@aws-cdk/aws-cloudfront/README.md +++ b/packages/@aws-cdk/aws-cloudfront/README.md @@ -207,6 +207,7 @@ new cloudfront.Distribution(this, 'myDist', { { functionVersion: myFunc.currentVersion, eventType: cloudfront.LambdaEdgeEventType.ORIGIN_REQUEST, + includeBody: true, // Optional - defaults to false }, ], }, diff --git a/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts b/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts index a53d38fb054be..117e79cc26345 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/distribution.ts @@ -621,6 +621,15 @@ export interface EdgeLambda { /** The type of event in response to which should the function be invoked. */ readonly eventType: LambdaEdgeEventType; + + /** + * Allows a Lambda function to have read access to the body content. + * Only valid for "request" event types (`ORIGIN_REQUEST` or `VIEWER_REQUEST`). + * See https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-include-body-access.html + * + * @default false + */ + readonly includeBody?: boolean; } /** diff --git a/packages/@aws-cdk/aws-cloudfront/lib/private/cache-behavior.ts b/packages/@aws-cdk/aws-cloudfront/lib/private/cache-behavior.ts index 09082c1dd4f8b..2eca5e66362f5 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/private/cache-behavior.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/private/cache-behavior.ts @@ -1,6 +1,6 @@ import * as iam from '@aws-cdk/aws-iam'; import { CfnDistribution } from '../cloudfront.generated'; -import { AddBehaviorOptions, EdgeLambda, ViewerProtocolPolicy } from '../distribution'; +import { AddBehaviorOptions, EdgeLambda, LambdaEdgeEventType, ViewerProtocolPolicy } from '../distribution'; /** * Properties for specifying custom behaviors for origins. @@ -26,6 +26,7 @@ export class CacheBehavior { constructor(originId: string, private readonly props: CacheBehaviorProps) { this.originId = originId; + this.validateEdgeLambdas(props.edgeLambdas); this.grantEdgeLambdaFunctionExecutionRole(props.edgeLambdas); } @@ -54,11 +55,19 @@ export class CacheBehavior { ? this.props.edgeLambdas.map(edgeLambda => ({ lambdaFunctionArn: edgeLambda.functionVersion.edgeArn, eventType: edgeLambda.eventType.toString(), + includeBody: edgeLambda.includeBody, })) : undefined, }; } + private validateEdgeLambdas(edgeLambdas?: EdgeLambda[]) { + const includeBodyEventTypes = [LambdaEdgeEventType.ORIGIN_REQUEST, LambdaEdgeEventType.VIEWER_REQUEST]; + if (edgeLambdas && edgeLambdas.some(lambda => lambda.includeBody && !includeBodyEventTypes.includes(lambda.eventType))) { + throw new Error('\'includeBody\' can only be true for ORIGIN_REQUEST or VIEWER_REQUEST event types.'); + } + } + private grantEdgeLambdaFunctionExecutionRole(edgeLambdas?: EdgeLambda[]) { if (!edgeLambdas || edgeLambdas.length === 0) { return; } edgeLambdas.forEach((edgeLambda) => { diff --git a/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts b/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts index d0a77ae0239a0..972726dac93af 100644 --- a/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts +++ b/packages/@aws-cdk/aws-cloudfront/lib/web_distribution.ts @@ -424,6 +424,15 @@ export interface LambdaFunctionAssociation { * A version of the lambda to associate */ readonly lambdaFunction: lambda.IVersion; + + /** + * Allows a Lambda function to have read access to the body content. + * Only valid for "request" event types (`ORIGIN_REQUEST` or `VIEWER_REQUEST`). + * See https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/lambda-include-body-access.html + * + * @default false + */ + readonly includeBody?: boolean; } export interface ViewerCertificateOptions { @@ -932,11 +941,17 @@ export class CloudFrontWebDistribution extends cdk.Resource implements IDistribu toReturn = Object.assign(toReturn, { pathPattern: input.pathPattern }); } if (input.lambdaFunctionAssociations) { + const includeBodyEventTypes = [LambdaEdgeEventType.ORIGIN_REQUEST, LambdaEdgeEventType.VIEWER_REQUEST]; + if (input.lambdaFunctionAssociations.some(fna => fna.includeBody && !includeBodyEventTypes.includes(fna.eventType))) { + throw new Error('\'includeBody\' can only be true for ORIGIN_REQUEST or VIEWER_REQUEST event types.'); + } + toReturn = Object.assign(toReturn, { lambdaFunctionAssociations: input.lambdaFunctionAssociations .map(fna => ({ eventType: fna.eventType, lambdaFunctionArn: fna.lambdaFunction && fna.lambdaFunction.edgeArn, + includeBody: fna.includeBody, })), }); diff --git a/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts b/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts index b4368de309232..4a0bd425c2cc2 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/distribution.test.ts @@ -468,6 +468,7 @@ describe('with Lambda@Edge functions', () => { { functionVersion: lambdaFunction.currentVersion, eventType: LambdaEdgeEventType.ORIGIN_REQUEST, + includeBody: true, }, ], }, @@ -479,6 +480,7 @@ describe('with Lambda@Edge functions', () => { LambdaFunctionAssociations: [ { EventType: 'origin-request', + IncludeBody: true, LambdaFunctionARN: { Ref: 'FunctionCurrentVersion4E2B2261477a5ae8059bbaa7813f752292c0f65e', }, diff --git a/packages/@aws-cdk/aws-cloudfront/test/private/cache-behavior.test.ts b/packages/@aws-cdk/aws-cloudfront/test/private/cache-behavior.test.ts index 2570e889d8587..58111d9fc2ef8 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/private/cache-behavior.test.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/private/cache-behavior.test.ts @@ -1,13 +1,15 @@ import '@aws-cdk/assert/jest'; +import * as lambda from '@aws-cdk/aws-lambda'; import { App, Stack } from '@aws-cdk/core'; -import { AllowedMethods, CachedMethods, ViewerProtocolPolicy } from '../../lib'; +import { AllowedMethods, CachedMethods, LambdaEdgeEventType, ViewerProtocolPolicy } from '../../lib'; import { CacheBehavior } from '../../lib/private/cache-behavior'; let app: App; +let stack: Stack; beforeEach(() => { app = new App(); - new Stack(app, 'Stack', { + stack = new Stack(app, 'Stack', { env: { account: '1234', region: 'testregion' }, }); }); @@ -26,6 +28,7 @@ test('renders the minimum template with an origin and path specified', () => { }); test('renders with all properties specified', () => { + const fnVersion = lambda.Version.fromVersionArn(stack, 'Version', 'arn:aws:lambda:testregion:111111111111:function:myTestFun:v1'); const behavior = new CacheBehavior('origin_id', { pathPattern: '*', allowedMethods: AllowedMethods.ALLOW_ALL, @@ -35,6 +38,11 @@ test('renders with all properties specified', () => { forwardQueryStringCacheKeys: ['user_id', 'auth'], smoothStreaming: true, viewerProtocolPolicy: ViewerProtocolPolicy.HTTPS_ONLY, + edgeLambdas: [{ + eventType: LambdaEdgeEventType.ORIGIN_REQUEST, + includeBody: true, + functionVersion: fnVersion, + }], }); expect(behavior._renderBehavior()).toEqual({ @@ -49,5 +57,23 @@ test('renders with all properties specified', () => { }, smoothStreaming: true, viewerProtocolPolicy: 'https-only', + lambdaFunctionAssociations: [{ + lambdaFunctionArn: 'arn:aws:lambda:testregion:111111111111:function:myTestFun:v1', + eventType: 'origin-request', + includeBody: true, + }], }); }); + +test('throws if edgeLambda includeBody is set for wrong event type', () => { + const fnVersion = lambda.Version.fromVersionArn(stack, 'Version', 'arn:aws:lambda:testregion:111111111111:function:myTestFun:v1'); + + expect(() => new CacheBehavior('origin_id', { + pathPattern: '*', + edgeLambdas: [{ + eventType: LambdaEdgeEventType.ORIGIN_RESPONSE, + includeBody: true, + functionVersion: fnVersion, + }], + })).toThrow(/'includeBody' can only be true for ORIGIN_REQUEST or VIEWER_REQUEST event types./); +}); 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 d5ed69f694b7c..ea2fbf3f22295 100644 --- a/packages/@aws-cdk/aws-cloudfront/test/web_distribution.test.ts +++ b/packages/@aws-cdk/aws-cloudfront/test/web_distribution.test.ts @@ -444,6 +444,7 @@ nodeunitShim({ lambdaFunctionAssociations: [{ eventType: LambdaEdgeEventType.ORIGIN_REQUEST, lambdaFunction: lambdaFunction.currentVersion, + includeBody: true, }], }, ], @@ -457,6 +458,7 @@ nodeunitShim({ 'LambdaFunctionAssociations': [ { 'EventType': 'origin-request', + 'IncludeBody': true, 'LambdaFunctionARN': { 'Ref': 'LambdaCurrentVersionDF706F6A97fb843e9bd06fcd2bb15eeace80e13e', }, @@ -545,6 +547,38 @@ nodeunitShim({ test.done(); }, + 'throws when associating a lambda with includeBody and a response event type'(test: Test) { + const app = new cdk.App(); + const stack = new cdk.Stack(app, 'Stack'); + const sourceBucket = new s3.Bucket(stack, 'Bucket'); + + const fnVersion = lambda.Version.fromVersionArn(stack, 'Version', 'arn:aws:lambda:testregion:111111111111:function:myTestFun:v1'); + + test.throws(() => { + new CloudFrontWebDistribution(stack, 'AnAmazingWebsiteProbably', { + originConfigs: [ + { + s3OriginSource: { + s3BucketSource: sourceBucket, + }, + behaviors: [ + { + isDefaultBehavior: true, + lambdaFunctionAssociations: [{ + eventType: LambdaEdgeEventType.VIEWER_RESPONSE, + includeBody: true, + lambdaFunction: fnVersion, + }], + }, + ], + }, + ], + }); + }, /'includeBody' can only be true for ORIGIN_REQUEST or VIEWER_REQUEST event types./); + + test.done(); + }, + 'distribution has a defaultChild'(test: Test) { const stack = new cdk.Stack(); const sourceBucket = new s3.Bucket(stack, 'Bucket'); From 6114045a4861efc7364f94490b734df5cf019726 Mon Sep 17 00:00:00 2001 From: Hyeonsoo David Lee Date: Sat, 29 Aug 2020 06:59:35 +0900 Subject: [PATCH 14/15] fix(appsync): `GraphQLApi.UserPoolConfig` requires `DefaultAction` (#10031) Set UserPoolConfig default action to 'allow' fixes #10028 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts | 2 +- packages/@aws-cdk/aws-appsync/test/appsync-auth.test.ts | 2 ++ packages/@aws-cdk/aws-appsync/test/integ.graphql.ts | 2 -- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts index 554bc3be77464..effa59310b6bc 100644 --- a/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts +++ b/packages/@aws-cdk/aws-appsync/lib/graphqlapi.ts @@ -531,7 +531,7 @@ export class GraphQLApi extends GraphqlApiBase { userPoolId: config.userPool.userPoolId, awsRegion: config.userPool.stack.region, appIdClientRegex: config.appIdClientRegex, - defaultAction: config.defaultAction, + defaultAction: config.defaultAction || UserPoolDefaultAction.ALLOW, }; } diff --git a/packages/@aws-cdk/aws-appsync/test/appsync-auth.test.ts b/packages/@aws-cdk/aws-appsync/test/appsync-auth.test.ts index e9ad68e4e66e0..fabde1d59c95e 100644 --- a/packages/@aws-cdk/aws-appsync/test/appsync-auth.test.ts +++ b/packages/@aws-cdk/aws-appsync/test/appsync-auth.test.ts @@ -250,6 +250,7 @@ describe('AppSync User Pool Authorization', () => { AuthenticationType: 'AMAZON_COGNITO_USER_POOLS', UserPoolConfig: { AwsRegion: { Ref: 'AWS::Region' }, + DefaultAction: 'ALLOW', UserPoolId: { Ref: 'pool056F3F7E' }, }, }); @@ -371,6 +372,7 @@ describe('AppSync User Pool Authorization', () => { AuthenticationType: 'AMAZON_COGNITO_USER_POOLS', UserPoolConfig: { AwsRegion: { Ref: 'AWS::Region' }, + DefaultAction: 'ALLOW', UserPoolId: { Ref: 'pool056F3F7E' }, }, AdditionalAuthenticationProviders: [ diff --git a/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts b/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts index 715864b209bc1..9882fead1cf12 100644 --- a/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts +++ b/packages/@aws-cdk/aws-appsync/test/integ.graphql.ts @@ -9,7 +9,6 @@ import { MappingTemplate, PrimaryKey, Schema, - UserPoolDefaultAction, Values, } from '../lib'; @@ -42,7 +41,6 @@ const api = new GraphQLApi(stack, 'Api', { authorizationType: AuthorizationType.USER_POOL, userPoolConfig: { userPool, - defaultAction: UserPoolDefaultAction.ALLOW, }, }, additionalAuthorizationModes: [ From ba51ea34e5b3f3c3cf337754d339f724b395211e Mon Sep 17 00:00:00 2001 From: mirgj <17768831+mirgj@users.noreply.github.com> Date: Sat, 29 Aug 2020 06:24:40 +0800 Subject: [PATCH 15/15] fix(aws-stepfunctions-tasks): SageMaker create training job has incorrect property name for AttributeNames (#10026) Rename mistyped property `AtttributeNames` to `AttributeNames` Fixes #10014 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../lib/sagemaker/create-training-job.ts | 2 +- .../test/sagemaker/create-training-job.test.ts | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-training-job.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-training-job.ts index f8b4c73d00320..16a74559cf98f 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-training-job.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/lib/sagemaker/create-training-job.ts @@ -253,7 +253,7 @@ export class SageMakerCreateTrainingJob extends sfn.TaskStateBase implements iam ...(channel.dataSource.s3DataSource.s3DataDistributionType ? { S3DataDistributionType: channel.dataSource.s3DataSource.s3DataDistributionType } : {}), - ...(channel.dataSource.s3DataSource.attributeNames ? { AtttributeNames: channel.dataSource.s3DataSource.attributeNames } : {}), + ...(channel.dataSource.s3DataSource.attributeNames ? { AttributeNames: channel.dataSource.s3DataSource.attributeNames } : {}), }, }, ...(channel.compressionType ? { CompressionType: channel.compressionType } : {}), diff --git a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-training-job.test.ts b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-training-job.test.ts index e4cd2ef1ce77f..b61ebd4173762 100644 --- a/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-training-job.test.ts +++ b/packages/@aws-cdk/aws-stepfunctions-tasks/test/sagemaker/create-training-job.test.ts @@ -153,6 +153,7 @@ test('create complex training job', () => { recordWrapperType: tasks.RecordWrapperType.RECORD_IO, dataSource: { s3DataSource: { + attributeNames: ['source-ref', 'class'], s3DataType: tasks.S3DataType.S3_PREFIX, s3Location: tasks.S3Location.fromBucket(s3.Bucket.fromBucketName(stack, 'InputBucketA', 'mybucket'), 'mytrainpath'), }, @@ -165,6 +166,7 @@ test('create complex training job', () => { recordWrapperType: tasks.RecordWrapperType.RECORD_IO, dataSource: { s3DataSource: { + attributeNames: ['source-ref', 'class'], s3DataType: tasks.S3DataType.S3_PREFIX, s3Location: tasks.S3Location.fromBucket(s3.Bucket.fromBucketName(stack, 'InputBucketB', 'mybucket'), 'mytestpath'), }, @@ -230,6 +232,7 @@ test('create complex training job', () => { ContentType: 'image/jpeg', DataSource: { S3DataSource: { + AttributeNames: ['source-ref', 'class'], S3DataType: 'S3Prefix', S3Uri: { 'Fn::Join': ['', ['https://s3.', { Ref: 'AWS::Region' }, '.', { Ref: 'AWS::URLSuffix' }, '/mybucket/mytrainpath']], @@ -244,6 +247,7 @@ test('create complex training job', () => { ContentType: 'image/jpeg', DataSource: { S3DataSource: { + AttributeNames: ['source-ref', 'class'], S3DataType: 'S3Prefix', S3Uri: { 'Fn::Join': ['', ['https://s3.', { Ref: 'AWS::Region' }, '.', { Ref: 'AWS::URLSuffix' }, '/mybucket/mytestpath']],