Applicable when
- Lambda requires access to RDS Database which is under VPC
Non-applicable when
- Lambda needs any specific network configuration to access database/resource (when database/resource is not part of a VPC)
Implementation
First, we need to get our already existing VPC and Security Group:
import { App, Stack } from '@aws-cdk/core';
export class BackendStack extends Stack {
private readonly vpc: IVpc;
private readonly securityGroup: ISecurityGroup;
constructor(app: App, private readonly rdsOutputs: KeyMap<RdsOutputs>,) {
this.vpc = Vpc.fromLookup(this, 'VPC', {
vpcId: this.node.tryGetContext('defaultVpcId'),
});
this.securityGroup = SecurityGroup.fromSecurityGroupId(
this,
'SecurityGroup',
this.rdsOutputs[RdsOutputs.SecurityGroupId]
);
// then we pass the VPC and Security group to our nested lambda stack
this.lambdaStack = new LambdaStack(
this,
withEnv('lambda-stack'),
this.rdsOutputs,
this.vpc,
this.securityGroup
);
}
}
Lambda stack configuration:
import { ISecurityGroup, ISubnet, IVpc } from '@aws-cdk/aws-ec2';
import { Policy, PolicyStatement, Role, ServicePrincipal } from '@aws-cdk/aws-iam';
import { Code, Function as LambdaFunction, Runtime } from '@aws-cdk/aws-lambda';
import { Construct, Duration, NestedStack, NestedStackProps } from '@aws-cdk/core';
import { CdkContextVpcSubnet } from '../model/cdk-context-vpc-subnet.model';
import { KeyMap } from '../model/key-map.model';
import { LambdaName } from '../model/lambda-name.enum';
import { RdsOutputs } from '../model/rds-outputs.enum';
import {
FIVE,
LAMBDA_CODE_PATH,
LAMBDA_NAME_PATTERN,
withEnv,
} from '../util/const';
import { getVpcProviderPropertyName } from '../util/get-vpc-provider-property-name';
// Your cdk.json or cdk.context.json will contain all the generated details about your VPC
// It contains a long property name which starts with `vpc-provider:`
// This methods just retrieves that property name since it is dynamic.
const vpcProviderPropertyName = getVpcProviderPropertyName();
export class LambdaStack extends NestedStack {
constructor(
scope: Construct,
id: string,
private readonly rdsOutputs: KeyMap<RdsOutputs>,
private readonly vpc: IVpc,
private readonly securityGroup: ISecurityGroup,
props?: NestedStackProps
) {
super(scope, id, props);
// Lambdas execution role
this.executionRole = new Role(this, withEnv('lambda-execution'), {
roleName: withEnv('lambda-execution'),
assumedBy: new ServicePrincipal('lambda.amazonaws.com'),
});
// Create policy to allow lambda to create a connection to the VPC network
const lambdaPolicy = new Policy(this, 'policy', {
statements: [
PolicyStatement.fromJson({
Effect: 'Allow',
Action: ['ec2:CreateNetworkInterface', 'ec2:DescribeNetworkInterfaces', 'ec2:DeleteNetworkInterface'],
Resource: '*',
})
],
});
this.executionRole.attachInlinePolicy(lambdaPolicy);
this.createLambdas();
}
private createLambdas(): void {
this.createLambda('detect-custom-labels/index.handler', LambdaName.DetectCustomLabels);
}
public createLambda(lambdaHandlerPath: string, name: LambdaName): LambdaFunction {
const lambdaName = withEnv(name || lambdaHandlerPath).replace(LAMBDA_NAME_PATTERN, '_');
const vpcProvider = this.node.tryGetContext(vpcProviderPropertyName);
const [privateSubnetGroup] = vpcProvider.subnetGroups;
const privateSubnets: ISubnet[] = privateSubnetGroup.subnets.map((subnet: CdkContextVpcSubnet) => ({
subnetId: subnet.subnetId,
availabilityZone: subnet.availabilityZone,
routeTable: subnet.routeTableId,
}));
return new LambdaFunction(this, lambdaName, {
handler: lambdaHandlerPath,
functionName: lambdaName,
runtime: Runtime.NODEJS_12_X,
code: Code.fromAsset(LAMBDA_CODE_PATH),
timeout: Duration.minutes(FIVE),
role: this.executionRole,
vpc: this.vpc,
securityGroups: [this.securityGroup],
vpcSubnets: {
availabilityZones: privateSubnetGroup.subnets.map((subnet: CdkContextVpcSubnet) => subnet.availabilityZone),
subnets: privateSubnets,
},
});
}
}
Comments
0 comments
Please sign in to leave a comment.