-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(aws-lambda): allow placing Lambda in VPC #598
Changes from all commits
6ba0614
cfefb7e
e807777
d4ef24c
5f9e0f6
66111d7
d078fec
66900d3
5cf128f
37bd186
9fe130f
d11ffb0
305da53
30f78dc
4eb363a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
export * from './managed-policy'; | ||
export * from './role'; | ||
export * from './policy'; | ||
export * from './user'; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import cdk = require('@aws-cdk/cdk'); | ||
|
||
/** | ||
* A policy managed by AWS | ||
* | ||
* For this managed policy, you only need to know the name to be able to use it. | ||
* | ||
* Some managed policy names start with "service-role/", some start with | ||
* "job-function/", and some don't start with anything. Do include the | ||
* prefix when constructing this object. | ||
*/ | ||
export class AwsManagedPolicy { | ||
constructor(private readonly managedPolicyName: string) { | ||
} | ||
|
||
/** | ||
* The Arn of this managed policy | ||
*/ | ||
public get policyArn(): cdk.Arn { | ||
// the arn is in the form of - arn:aws:iam::aws:policy/<policyName> | ||
return cdk.Arn.fromComponents({ | ||
service: "iam", | ||
region: "", // no region for managed policy | ||
account: "aws", // the account for a managed policy is 'aws' | ||
resource: "policy", | ||
resourceName: this.managedPolicyName | ||
}); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import cdk = require('@aws-cdk/cdk'); | ||
import { Test } from 'nodeunit'; | ||
import { AwsManagedPolicy } from '../lib'; | ||
|
||
export = { | ||
'simple managed policy'(test: Test) { | ||
const mp = new AwsManagedPolicy("service-role/SomePolicy"); | ||
|
||
test.deepEqual(cdk.resolve(mp.policyArn), { | ||
"Fn::Join": ['', [ | ||
'arn', | ||
':', | ||
{ Ref: 'AWS::Partition' }, | ||
':', | ||
'iam', | ||
':', | ||
'', | ||
':', | ||
'aws', | ||
':', | ||
'policy', | ||
'/', | ||
'service-role/SomePolicy' | ||
]] | ||
}); | ||
|
||
test.done(); | ||
}, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
import ec2 = require('@aws-cdk/aws-ec2'); | ||
import iam = require('@aws-cdk/aws-iam'); | ||
import sqs = require('@aws-cdk/aws-sqs'); | ||
import cdk = require('@aws-cdk/cdk'); | ||
|
@@ -91,6 +92,34 @@ export interface FunctionProps { | |
*/ | ||
role?: iam.Role; | ||
|
||
/** | ||
* VPC network to place Lambda network interfaces | ||
* | ||
* Specify this if the Lambda function needs to access resources in a VPC. | ||
*/ | ||
vpc?: ec2.VpcNetworkRef; | ||
|
||
/** | ||
* Where to place the network interfaces within the VPC. | ||
* | ||
* Only used if 'vpc' is supplied. Note: internet access for Lambdas | ||
* requires a NAT gateway, so picking Public subnets is not allowed. | ||
* | ||
* @default All private subnets | ||
*/ | ||
vpcPlacement?: ec2.VpcPlacementStrategy; | ||
|
||
/** | ||
* What security group to associate with the Lambda's network interfaces. | ||
* | ||
* Only used if 'vpc' is supplied. | ||
* | ||
* @default If the function is placed within a VPC and a security group is | ||
* not specified, a dedicated security group will be created for this | ||
* function. | ||
*/ | ||
securityGroup?: ec2.SecurityGroupRef; | ||
|
||
/** | ||
* Enabled DLQ. If `deadLetterQueue` is undefined, | ||
* an SQS queue with default options will be defined for your Function. | ||
|
@@ -105,7 +134,6 @@ export interface FunctionProps { | |
* @default SQS queue with 14 day retention period if `deadLetterQueueEnabled` is `true` | ||
*/ | ||
deadLetterQueue?: sqs.QueueRef; | ||
|
||
} | ||
|
||
/** | ||
|
@@ -157,16 +185,19 @@ export class Function extends FunctionRef { | |
|
||
this.environment = props.environment || { }; | ||
|
||
const managedPolicyArns = new Array<cdk.Arn>(); | ||
|
||
// the arn is in the form of - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole | ||
managedPolicyArns.push(new iam.AwsManagedPolicy("service-role/AWSLambdaBasicExecutionRole").policyArn); | ||
|
||
if (props.vpc) { | ||
// Policy that will have ENI creation permissions | ||
managedPolicyArns.push(new iam.AwsManagedPolicy("service-role/AWSLambdaVPCAccessExecutionRole").policyArn); | ||
} | ||
|
||
this.role = props.role || new iam.Role(this, 'ServiceRole', { | ||
assumedBy: new cdk.ServicePrincipal('lambda.amazonaws.com'), | ||
// the arn is in the form of - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole | ||
managedPolicyArns: [ cdk.Arn.fromComponents({ | ||
service: "iam", | ||
region: "", // no region for managed policy | ||
account: "aws", // the account for a managed policy is 'aws' | ||
resource: "policy", | ||
resourceName: "service-role/AWSLambdaBasicExecutionRole", | ||
})], | ||
managedPolicyArns, | ||
}); | ||
|
||
for (const statement of (props.initialPolicy || [])) { | ||
|
@@ -183,6 +214,7 @@ export class Function extends FunctionRef { | |
role: this.role.roleArn, | ||
environment: new cdk.Token(() => this.renderEnvironment()), | ||
memorySize: props.memorySize, | ||
vpcConfig: this.configureVpc(props), | ||
deadLetterConfig: this.buildDeadLetterConfig(props), | ||
}); | ||
|
||
|
@@ -245,6 +277,40 @@ export class Function extends FunctionRef { | |
}; | ||
} | ||
|
||
/** | ||
* If configured, set up the VPC-related properties | ||
* | ||
* Returns the VpcConfig that should be added to the | ||
* Lambda creation properties. | ||
*/ | ||
private configureVpc(props: FunctionProps): cloudformation.FunctionResource.VpcConfigProperty | undefined { | ||
if (!props.vpc) { return undefined; } | ||
|
||
let securityGroup = props.securityGroup; | ||
if (!securityGroup) { | ||
securityGroup = new ec2.SecurityGroup(this, 'SecurityGroup', { | ||
vpc: props.vpc, | ||
description: 'Automatic security group for Lambda Function ' + this.uniqueId, | ||
}); | ||
} | ||
|
||
this._connections = new ec2.Connections({ securityGroup }); | ||
|
||
// Pick subnets, make sure they're not Public. Routing through an IGW | ||
// won't work because the ENIs don't get a Public IP. | ||
const subnets = props.vpc.subnets(props.vpcPlacement); | ||
for (const subnet of subnets) { | ||
if (props.vpc.publicSubnets.indexOf(subnet) > -1) { | ||
throw new Error('Not possible to place Lambda Functions in a Public subnet'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. mention what are the legal options (private/internal) and also why is this not possible There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can't mention Internal because in this PR support for selecting internal subnets hasn't landed yet. Also I'm told that my understanding of this is incomplete (by someone from the Lambda team), AND they're changing it early next year, AND we never explicate the reason on any other of our public documentation pages, we just say "no public for you". I don't want us to be the single source of truth for a factoid that's going to become wrong in a short while. |
||
} | ||
} | ||
|
||
return { | ||
subnetIds: subnets.map(s => s.subnetId), | ||
securityGroupIds: [securityGroup.securityGroupId] | ||
}; | ||
} | ||
|
||
private buildDeadLetterConfig(props: FunctionProps) { | ||
if (props.deadLetterQueue && props.deadLetterQueueEnabled === false) { | ||
throw Error('deadLetterQueue defined but deadLetterQueueEnabled explicitly set to false'); | ||
|
@@ -266,5 +332,4 @@ export class Function extends FunctionRef { | |
targetArn: deadLetterQueue.queueArn | ||
}; | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe
role.addManagedPolicy('service-role/xxx')
would be nicer?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, but you can have your own managed policies that you can attach, and they'll have a different account ('12345' vs 'aws'). Let's use ARNs to be safe.
Ultimately, this should be just "the object", not "the ARN", but I just wanted to make this copy/pasted code slighly better, not introduce a new modeling of ManagedPolicies as part of this CR.