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();