diff --git a/packages/@aws-cdk/aws-lambda/lib/function.ts b/packages/@aws-cdk/aws-lambda/lib/function.ts index 92b6d64774751..5d0fd05c00b17 100644 --- a/packages/@aws-cdk/aws-lambda/lib/function.ts +++ b/packages/@aws-cdk/aws-lambda/lib/function.ts @@ -299,6 +299,15 @@ export interface FunctionProps extends FunctionOptions { * @default - will not mount any filesystem */ readonly filesystem?: FileSystem; + + /** + * Lambda Functions in a public subnet can NOT access the internet. + * Use this property to acknowledge this limitation and still place the function in a public subnet. + * @see https://stackoverflow.com/questions/52992085/why-cant-an-aws-lambda-function-inside-a-public-subnet-in-a-vpc-connect-to-the/52994841#52994841 + * + * @default false + */ + readonly allowPublicSubnet?: boolean; } /** @@ -819,15 +828,13 @@ export class Function extends FunctionBase { } } - // Pick subnets, make sure they're not Public. Routing through an IGW - // won't work because the ENIs don't get a Public IP. - // Why are we not simply forcing vpcSubnets? Because you might still be choosing - // Isolated networks or selecting among 2 sets of Private subnets by name. + const allowPublicSubnet = props.allowPublicSubnet ?? false; const { subnetIds } = props.vpc.selectSubnets(props.vpcSubnets); const publicSubnetIds = new Set(props.vpc.publicSubnets.map(s => s.subnetId)); for (const subnetId of subnetIds) { - if (publicSubnetIds.has(subnetId)) { - throw new Error('Not possible to place Lambda Functions in a Public subnet'); + if (publicSubnetIds.has(subnetId) && !allowPublicSubnet) { + throw new Error('Lambda Functions in a public subnet can NOT access the internet. ' + + 'If you are aware of this limitation and would still like to place the function int a public subnet, set `allowPublicSubnet` to true'); } } diff --git a/packages/@aws-cdk/aws-lambda/test/test.vpc-lambda.ts b/packages/@aws-cdk/aws-lambda/test/test.vpc-lambda.ts index a3633400b4ccb..6e4f47de11c57 100644 --- a/packages/@aws-cdk/aws-lambda/test/test.vpc-lambda.ts +++ b/packages/@aws-cdk/aws-lambda/test/test.vpc-lambda.ts @@ -212,22 +212,133 @@ export = { test.done(); }, - 'picking public subnets is not allowed'(test: Test) { + 'can pick public subnet for Lambda'(test: Test) { // GIVEN const stack = new cdk.Stack(); const vpc = new ec2.Vpc(stack, 'VPC'); + // WHEN + new lambda.Function(stack, 'PublicLambda', { + allowPublicSubnet: true, + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + vpc, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, + }); + + // THEN + expect(stack).to(haveResource('AWS::Lambda::Function', { + VpcConfig: { + SecurityGroupIds: [ + {'Fn::GetAtt': [ 'PublicLambdaSecurityGroup61D896FD', 'GroupId' ]}, + ], + SubnetIds: [ + {Ref: 'VPCPublicSubnet1SubnetB4246D30'}, + {Ref: 'VPCPublicSubnet2Subnet74179F39'}, + ], + }, + })); + test.done(); + }, + + 'can pick private subnet for Lambda'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC'); + + // WHEN + new lambda.Function(stack, 'PrivateLambda', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + vpc, + vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE }, + }); + + // THEN + + expect(stack).to(haveResource('AWS::Lambda::Function', { + VpcConfig: { + SecurityGroupIds: [ + {'Fn::GetAtt': [ 'PrivateLambdaSecurityGroupF53C8342', 'GroupId' ]}, + ], + SubnetIds: [ + {Ref: 'VPCPrivateSubnet1Subnet8BCA10E0'}, + {Ref: 'VPCPrivateSubnet2SubnetCFCDAA7A'}, + ], + }, + })); + test.done(); + }, + + 'can pick isolated subnet for Lambda'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC', { + subnetConfiguration: [ + { + name: 'Isolated', + subnetType: ec2.SubnetType.ISOLATED, + }, + ], + }); + + // WHEN + new lambda.Function(stack, 'IsolatedLambda', { + code: new lambda.InlineCode('foo'), + handler: 'index.handler', + runtime: lambda.Runtime.NODEJS_10_X, + vpc, + vpcSubnets: { subnetType: ec2.SubnetType.ISOLATED }, + }); + + // THEN + + expect(stack).to(haveResource('AWS::Lambda::Function', { + VpcConfig: { + SecurityGroupIds: [ + {'Fn::GetAtt': [ 'IsolatedLambdaSecurityGroupCE25B6A9', 'GroupId' ]}, + ], + SubnetIds: [ + {Ref: 'VPCIsolatedSubnet1SubnetEBD00FC6'}, + {Ref: 'VPCIsolatedSubnet2Subnet4B1C8CAA'}, + ], + }, + })); + test.done(); + }, + + 'picking public subnet type is not allowed if not overriding allowPublicSubnet'(test: Test) { + // GIVEN + const stack = new cdk.Stack(); + const vpc = new ec2.Vpc(stack, 'VPC', { + subnetConfiguration: [ + { + name: 'Public', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + name: 'Private', + subnetType: ec2.SubnetType.PRIVATE, + }, + { + name: 'Isolated', + subnetType: ec2.SubnetType.ISOLATED, + }, + ], + }); + // WHEN test.throws(() => { - new lambda.Function(stack, 'Lambda', { + new lambda.Function(stack, 'PublicLambda', { code: new lambda.InlineCode('foo'), handler: 'index.handler', runtime: lambda.Runtime.NODEJS_10_X, vpc, vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, }); - }); - + }, /Lambda Functions in a public subnet/); test.done(); }, };