From f183e1ab6d80436c705f2ebe2b10c9d5c8bcbfa6 Mon Sep 17 00:00:00 2001 From: kaizen3031593 Date: Tue, 15 Feb 2022 17:29:47 -0500 Subject: [PATCH 1/3] add skippermissions --- .../@aws-cdk/aws-lambda/lib/function-base.ts | 30 +++++++++++++++++-- packages/@aws-cdk/aws-lambda/lib/function.ts | 1 + .../@aws-cdk/aws-lambda/test/function.test.ts | 16 +++++++++- 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda/lib/function-base.ts b/packages/@aws-cdk/aws-lambda/lib/function-base.ts index 1256383c471bd..b259476efad9d 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function-base.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function-base.ts @@ -180,6 +180,20 @@ export interface FunctionAttributes { */ readonly sameEnvironment?: boolean; + /** + * Setting this property informs the CDK that the imported function ALREADY HAS the necessary permissions + * for what you are trying to do. When not configured, the CDK attempts to auto-determine whether or not + * additional permissions are necessary on the function when grant APIs are used. If the CDK tried to add + * permissions on an imported lambda, it will fail. + * + * Set this property *ONLY IF* you are committing to manage the imported function's permissions outside of + * CDK. You are acknowledging that your CDK code alone will have insufficient permissions to access the + * imported function. + * + * @default false + */ + readonly skipPermissions?: boolean; + /** * The architecture of this Lambda Function (this is an optional attribute and defaults to X86_64). * @default - Architecture.X86_64 @@ -228,6 +242,15 @@ export abstract class FunctionBase extends Resource implements IFunction, ec2.IC */ protected abstract readonly canCreatePermissions: boolean; + /** + * Whether the user decides to skip adding permissions. + * The only use case is for cross-account, imported lambdas + * where the user commits to modifying the permisssions + * on the imported lambda outside CDK. + * @internal + */ + protected readonly _skipPermissions?: boolean; + /** * Actual connections object for this Lambda * @@ -342,9 +365,10 @@ export abstract class FunctionBase extends Resource implements IFunction, ec2.IC }); const permissionNode = this._functionNode().tryFindChild(identifier); - if (!permissionNode) { - throw new Error('Cannot modify permission to lambda function. Function is either imported or $LATEST version. ' - + 'If the function is imported from the same account use `fromFunctionAttributes()` API with the `sameEnvironment` flag.'); + if (!permissionNode && !this._skipPermissions) { + throw new Error('Cannot modify permission to lambda function. Function is either imported or $LATEST version.\n' + + 'If the function is imported from the same account use `fromFunctionAttributes()` API with the `sameEnvironment` flag.\n' + + 'If the function is imported from a different account and already has the correct permissions use `fromFunctionAttributes()` API with the `skipPermissions` flag.'); } return { statementAdded: true, policyDependable: permissionNode }; }, diff --git a/packages/@aws-cdk/aws-lambda/lib/function.ts b/packages/@aws-cdk/aws-lambda/lib/function.ts index 2b47aa8d7bca3..a44d92cff59b4 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function.ts @@ -453,6 +453,7 @@ export class Function extends FunctionBase { public readonly architecture = attrs.architecture ?? Architecture.X86_64; protected readonly canCreatePermissions = attrs.sameEnvironment ?? this._isStackAccount(); + protected readonly _skipPermissions = attrs.skipPermissions ?? false; constructor(s: Construct, i: string) { super(s, i, { diff --git a/packages/@aws-cdk/aws-lambda/test/function.test.ts b/packages/@aws-cdk/aws-lambda/test/function.test.ts index d4f1af8f02cf0..7d78ab15e4f55 100644 --- a/packages/@aws-cdk/aws-lambda/test/function.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/function.test.ts @@ -845,7 +845,6 @@ describe('function', () => { }); describe('grantInvoke', () => { - test('adds iam:InvokeFunction', () => { // GIVEN const stack = new cdk.Stack(); @@ -1094,6 +1093,21 @@ describe('function', () => { expect(() => { fn.grantInvoke(new iam.ServicePrincipal('elasticloadbalancing.amazonaws.com')); }) .toThrow(/Cannot modify permission to lambda function/); }); + + test('on an imported function (different account & w/ skipPermissions', () => { + // GIVEN + const stack = new cdk.Stack(undefined, undefined, { + env: { account: '111111111111' }, // Different account + }); + const fn = lambda.Function.fromFunctionAttributes(stack, 'Function', { + functionArn: 'arn:aws:lambda:us-east-1:123456789012:function:MyFn', + skipPermissions: true, + }); + + // THEN + expect(() => { fn.grantInvoke(new iam.ServicePrincipal('elasticloadbalancing.amazonaws.com')); }) + .not.toThrow(); + }); }); test('Can use metricErrors on a lambda Function', () => { From c9c1636b7b4fa363b750591f14b3769df40e23ce Mon Sep 17 00:00:00 2001 From: kaizen3031593 Date: Wed, 16 Feb 2022 10:28:08 -0500 Subject: [PATCH 2/3] pr comments --- packages/@aws-cdk/aws-lambda/README.md | 28 +++++++++++++++++++ .../@aws-cdk/aws-lambda/test/function.test.ts | 10 ++++--- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/aws-lambda/README.md b/packages/@aws-cdk/aws-lambda/README.md index f6c95dfe0d68f..9c7bae3d78d9a 100644 --- a/packages/@aws-cdk/aws-lambda/README.md +++ b/packages/@aws-cdk/aws-lambda/README.md @@ -480,6 +480,34 @@ fn.addEventSource(new eventsources.S3EventSource(bucket, { See the documentation for the __@aws-cdk/aws-lambda-event-sources__ module for more details. +## Imported Lambdas + +When referencing an imported lambda in the CDK, use `fromFunctionArn()` for most use cases: + +```ts +const fn = lambda.Function.fromFunctionArn( + this, + 'Function', + 'arn:aws:lambda:us-east-1:123456789012:function:MyFn', +); +``` + +The `fromFunctionAttributes()` API is available for more specific use cases: + +```ts +const fn = lambda.Function.fromFunctionAttributes(this, 'Function', { + functionArn: 'arn:aws:lambda:us-east-1:123456789012:function:MyFn', + // The following are optional properties for specific use cases and should be used with caution: + + // Use Case: imported function is in the same account as the stack. + sameEnvironment: true, + + // Use Case: imported function is in a different account and user commits to ensuring that the + // imported function has the correct permissions outside the CDK. + skipPermissions: true, +}); +``` + ## Lambda with DLQ A dead-letter queue can be automatically created for a Lambda function by diff --git a/packages/@aws-cdk/aws-lambda/test/function.test.ts b/packages/@aws-cdk/aws-lambda/test/function.test.ts index 7d78ab15e4f55..165c09fdea8bc 100644 --- a/packages/@aws-cdk/aws-lambda/test/function.test.ts +++ b/packages/@aws-cdk/aws-lambda/test/function.test.ts @@ -1090,8 +1090,9 @@ describe('function', () => { const fn = lambda.Function.fromFunctionArn(stack, 'Function', 'arn:aws:lambda:us-east-1:123456789012:function:MyFn'); // THEN - expect(() => { fn.grantInvoke(new iam.ServicePrincipal('elasticloadbalancing.amazonaws.com')); }) - .toThrow(/Cannot modify permission to lambda function/); + expect(() => { + fn.grantInvoke(new iam.ServicePrincipal('elasticloadbalancing.amazonaws.com')); + }).toThrow(/Cannot modify permission to lambda function/); }); test('on an imported function (different account & w/ skipPermissions', () => { @@ -1105,8 +1106,9 @@ describe('function', () => { }); // THEN - expect(() => { fn.grantInvoke(new iam.ServicePrincipal('elasticloadbalancing.amazonaws.com')); }) - .not.toThrow(); + expect(() => { + fn.grantInvoke(new iam.ServicePrincipal('elasticloadbalancing.amazonaws.com')); + }).not.toThrow(); }); }); From b633a1f0de3c32b4a84f909d556465e6b325efc5 Mon Sep 17 00:00:00 2001 From: kaizen3031593 Date: Wed, 16 Feb 2022 10:30:57 -0500 Subject: [PATCH 3/3] update readme --- packages/@aws-cdk/aws-lambda/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/@aws-cdk/aws-lambda/README.md b/packages/@aws-cdk/aws-lambda/README.md index 9c7bae3d78d9a..bdf1da7446641 100644 --- a/packages/@aws-cdk/aws-lambda/README.md +++ b/packages/@aws-cdk/aws-lambda/README.md @@ -499,7 +499,8 @@ const fn = lambda.Function.fromFunctionAttributes(this, 'Function', { functionArn: 'arn:aws:lambda:us-east-1:123456789012:function:MyFn', // The following are optional properties for specific use cases and should be used with caution: - // Use Case: imported function is in the same account as the stack. + // Use Case: imported function is in the same account as the stack. This tells the CDK that it + // can modify the function's permissions. sameEnvironment: true, // Use Case: imported function is in a different account and user commits to ensuring that the