From 192c2568138efa2c27cf7646961d346a73300b17 Mon Sep 17 00:00:00 2001 From: Ruben Ernst <4404015+Ruben-E@users.noreply.github.com> Date: Thu, 17 Dec 2020 10:45:56 +0100 Subject: [PATCH 1/3] feat(custom-resources): supported custom role for provider lambda Added support to pass a custom role to the provider which the lambda will use. Can be used a.o. to pass a permission boundary. closes #12126 --- packages/@aws-cdk/custom-resources/README.md | 6 +- .../lib/provider-framework/provider.ts | 15 +++++ .../test/provider-framework/provider.test.ts | 55 +++++++++++++++++++ 3 files changed, 75 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/custom-resources/README.md b/packages/@aws-cdk/custom-resources/README.md index 0be6398f162c0..d5f426f7287be 100644 --- a/packages/@aws-cdk/custom-resources/README.md +++ b/packages/@aws-cdk/custom-resources/README.md @@ -33,14 +33,18 @@ the actual handler. ```ts import { CustomResource } from '@aws-cdk/core'; import * as logs from '@aws-cdk/aws-logs'; +import * as iam from '@aws-cdk/aws-iam'; import * as cr from '@aws-cdk/custom-resources'; const onEvent = new lambda.Function(this, 'MyHandler', { /* ... */ }); +const myRole = new iam.Role(this, 'MyRole', { /* ... */ }); + const myProvider = new cr.Provider(this, 'MyProvider', { onEventHandler: onEvent, isCompleteHandler: isComplete, // optional async "waiter" - logRetention: logs.RetentionDays.ONE_DAY // default is INFINITE + logRetention: logs.RetentionDays.ONE_DAY, // default is INFINITE + role: myRole, // must be assumable by the `lambda.amazonaws.com` service principal }); new CustomResource(this, 'Resource1', { serviceToken: myProvider.serviceToken }); diff --git a/packages/@aws-cdk/custom-resources/lib/provider-framework/provider.ts b/packages/@aws-cdk/custom-resources/lib/provider-framework/provider.ts index a61e7ab475939..fff1c24810dbb 100644 --- a/packages/@aws-cdk/custom-resources/lib/provider-framework/provider.ts +++ b/packages/@aws-cdk/custom-resources/lib/provider-framework/provider.ts @@ -1,5 +1,6 @@ import * as path from 'path'; import * as cfn from '@aws-cdk/aws-cloudformation'; +import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; import * as logs from '@aws-cdk/aws-logs'; import { Construct as CoreConstruct, Duration } from '@aws-cdk/core'; @@ -70,6 +71,16 @@ export interface ProviderProps { * @default logs.RetentionDays.INFINITE */ readonly logRetention?: logs.RetentionDays; + + /** + * AWS Lambda execution role. + * + * The role that will be assumed by the AWS Lambda. + * Must be assumable by the 'lambda.amazonaws.com' service principal. + * + * @default - A default role will be created. + */ + readonly role?: iam.Role; } /** @@ -97,6 +108,7 @@ export class Provider extends CoreConstruct implements cfn.ICustomResourceProvid private readonly entrypoint: lambda.Function; private readonly logRetention?: logs.RetentionDays; + private readonly role?: iam.Role; constructor(scope: Construct, id: string, props: ProviderProps) { super(scope, id); @@ -111,6 +123,8 @@ export class Provider extends CoreConstruct implements cfn.ICustomResourceProvid this.logRetention = props.logRetention; + this.role = props.role; + const onEventFunction = this.createFunction(consts.FRAMEWORK_ON_EVENT_HANDLER_NAME); if (this.isCompleteHandler) { @@ -153,6 +167,7 @@ export class Provider extends CoreConstruct implements cfn.ICustomResourceProvid handler: `framework.${entrypoint}`, timeout: FRAMEWORK_HANDLER_TIMEOUT, logRetention: this.logRetention, + role: this.role, }); fn.addEnvironment(consts.USER_ON_EVENT_FUNCTION_ARN_ENV, this.onEventHandler.functionArn); diff --git a/packages/@aws-cdk/custom-resources/test/provider-framework/provider.test.ts b/packages/@aws-cdk/custom-resources/test/provider-framework/provider.test.ts index 0d0f98f634d07..2a77bedf93b45 100644 --- a/packages/@aws-cdk/custom-resources/test/provider-framework/provider.test.ts +++ b/packages/@aws-cdk/custom-resources/test/provider-framework/provider.test.ts @@ -1,4 +1,5 @@ import * as path from 'path'; +import * as iam from '@aws-cdk/aws-iam'; import * as lambda from '@aws-cdk/aws-lambda'; import * as logs from '@aws-cdk/aws-logs'; import { Duration, Stack } from '@aws-cdk/core'; @@ -217,3 +218,57 @@ describe('log retention', () => { expect(stack).not.toHaveResource('Custom::LogRetention'); }); }); + +describe('role', () => { + it('uses custom role when present', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + new cr.Provider(stack, 'MyProvider', { + onEventHandler: new lambda.Function(stack, 'MyHandler', { + code: lambda.Code.fromAsset(path.join(__dirname, './integration-test-fixtures/s3-file-handler')), + handler: 'index.onEvent', + runtime: lambda.Runtime.NODEJS_10_X, + }), + role: new iam.Role(stack, 'MyRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole')], + }), + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::Lambda::Function', { + Role: { + 'Fn::GetAtt': [ + 'MyRoleF48FFE04', + 'Arn', + ], + }, + }); + }); + + it('uses default role otherwise', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + new cr.Provider(stack, 'MyProvider', { + onEventHandler: new lambda.Function(stack, 'MyHandler', { + code: lambda.Code.fromAsset(path.join(__dirname, './integration-test-fixtures/s3-file-handler')), + handler: 'index.onEvent', + runtime: lambda.Runtime.NODEJS_10_X, + }), + }); + + // THEN + expect(stack).toHaveResourceLike('AWS::Lambda::Function', { + Role: { + 'Fn::GetAtt': [ + 'MyProviderframeworkonEventServiceRole8761E48D', + 'Arn', + ], + }, + }); + }); +}); From 4bb352bb8e3120ae8a99b674671b91079975bb31 Mon Sep 17 00:00:00 2001 From: Ruben Ernst <4404015+Ruben-E@users.noreply.github.com> Date: Thu, 17 Dec 2020 11:12:42 +0100 Subject: [PATCH 2/3] Using interface IRole instead of class directly --- .../custom-resources/lib/provider-framework/provider.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/@aws-cdk/custom-resources/lib/provider-framework/provider.ts b/packages/@aws-cdk/custom-resources/lib/provider-framework/provider.ts index fff1c24810dbb..aae0cef03fe68 100644 --- a/packages/@aws-cdk/custom-resources/lib/provider-framework/provider.ts +++ b/packages/@aws-cdk/custom-resources/lib/provider-framework/provider.ts @@ -80,7 +80,7 @@ export interface ProviderProps { * * @default - A default role will be created. */ - readonly role?: iam.Role; + readonly role?: iam.IRole; } /** @@ -108,7 +108,7 @@ export class Provider extends CoreConstruct implements cfn.ICustomResourceProvid private readonly entrypoint: lambda.Function; private readonly logRetention?: logs.RetentionDays; - private readonly role?: iam.Role; + private readonly role?: iam.IRole; constructor(scope: Construct, id: string, props: ProviderProps) { super(scope, id); From d4b8a3adae45c93f7a7c889cff18ae2d4a1b94f3 Mon Sep 17 00:00:00 2001 From: Ruben Ernst <4404015+Ruben-E@users.noreply.github.com> Date: Tue, 23 Mar 2021 09:43:15 +0100 Subject: [PATCH 3/3] Kicking build --- .../@aws-cdk/custom-resources/lib/provider-framework/provider.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/@aws-cdk/custom-resources/lib/provider-framework/provider.ts b/packages/@aws-cdk/custom-resources/lib/provider-framework/provider.ts index 0dd625a93c264..8d99963627a4a 100644 --- a/packages/@aws-cdk/custom-resources/lib/provider-framework/provider.ts +++ b/packages/@aws-cdk/custom-resources/lib/provider-framework/provider.ts @@ -179,7 +179,6 @@ export class Provider extends CoreConstruct implements ICustomResourceProvider { interval: retry.interval, maxAttempts: retry.maxAttempts, }); - // the on-event entrypoint is going to start the execution of the waiter onEventFunction.addEnvironment(consts.WAITER_STATE_MACHINE_ARN_ENV, waiterStateMachine.stateMachineArn); waiterStateMachine.grantStartExecution(onEventFunction);