diff --git a/bin/stacks/routing-lambda-stack.ts b/bin/stacks/routing-lambda-stack.ts index 2213898e93..994136d9c7 100644 --- a/bin/stacks/routing-lambda-stack.ts +++ b/bin/stacks/routing-lambda-stack.ts @@ -9,10 +9,20 @@ import * as aws_lambda from 'aws-cdk-lib/aws-lambda' import * as aws_lambda_nodejs from 'aws-cdk-lib/aws-lambda-nodejs' import * as aws_s3 from 'aws-cdk-lib/aws-s3' import * as aws_sns from 'aws-cdk-lib/aws-sns' +import * as ec2 from 'aws-cdk-lib/aws-ec2' import { Construct } from 'constructs' import * as path from 'path' import { DynamoDBTableProps } from './routing-database-stack' import { RetentionDays } from 'aws-cdk-lib/aws-logs' +import * as aws_route53 from 'aws-cdk-lib/aws-route53' +import * as aws_route53_targets from 'aws-cdk-lib/aws-route53-targets' + +const vpcEndpointServiceMap: Record = { + dev: 'com.amazonaws.vpce.us-east-2.vpce-svc-0945550ad67320638', + staging: 'com.amazonaws.vpce.us-east-2.vpce-svc-00e58a4116063039d', + prod: 'com.amazonaws.vpce.us-east-2.vpce-svc-04784c683b22cfabb', +} +const privateHostedZoneName = 'unihq.org' export interface RoutingLambdaStackProps extends cdk.NestedStackProps { poolCacheBucket: aws_s3.Bucket @@ -77,16 +87,79 @@ export class RoutingLambdaStack extends cdk.NestedStack { new CfnOutput(this, 'jsonRpcProviders', { value: JSON.stringify(jsonRpcProviders), }) + const stage = process.env.STAGE || 'dev' // Default to 'dev' if not set + + // TODO: Add if not dev , not create + const vpc = new ec2.Vpc(this, `RoutingLambdaVPC-${stage}`, { + maxAzs: 4, // Number of availability zones + subnetConfiguration: [ + { + name: `RoutingPublicSubnet-${stage}`, + subnetType: ec2.SubnetType.PUBLIC, // Public subnet with internet access + cidrMask: 24, // IP range for public subnet + }, + { + name: `RoutingPrivateSubnet-${stage}`, + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, // Private subnet with access to the internet via NAT Gateway + cidrMask: 24, // IP range for private subnet + }, + ], + natGateways: 1, // One NAT Gateway for private subnet internet access + }) + + const publicSubnets = vpc.selectSubnets({ subnetType: ec2.SubnetType.PUBLIC }) + const vpcEndpoint = new ec2.InterfaceVpcEndpoint(this, `AccessUnirpcEndpoint-${stage}`, { + vpc, + service: new ec2.InterfaceVpcEndpointService(vpcEndpointServiceMap[stage], 443), + subnets: publicSubnets, + privateDnsEnabled: false, // Enable private DNS for the endpoint + }) + + // Create a private hosted zone + const hostedZone = new aws_route53.PrivateHostedZone(this, 'UniHQHostedZone', { + zoneName: privateHostedZoneName, + vpc, + }) + + const recordName = `routing-${stage}.${privateHostedZoneName}` // e.g. routing-dev.unihq.org + new aws_route53.ARecord(this, 'RoutingRecord', { + zone: hostedZone, + recordName: recordName, + target: aws_route53.RecordTarget.fromAlias(new aws_route53_targets.InterfaceVpcEndpointTarget(vpcEndpoint)), + }) + + new cdk.CfnOutput(this, 'VpcEndpointId', { + value: vpcEndpoint.vpcEndpointId, + }) const lambdaRole = new aws_iam.Role(this, 'RoutingLambdaRole', { assumedBy: new aws_iam.ServicePrincipal('lambda.amazonaws.com'), managedPolicies: [ + // Basic Lambda execution role aws_iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + // General Lambda role aws_iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaRole'), + // CloudWatch for logs and monitoring aws_iam.ManagedPolicy.fromAwsManagedPolicyName('CloudWatchLambdaInsightsExecutionRolePolicy'), + // X-Ray for tracing aws_iam.ManagedPolicy.fromAwsManagedPolicyName('AWSXRayDaemonWriteAccess'), ], }) + + // Add inline policy for EC2 permissions + lambdaRole.addToPolicy( + new aws_iam.PolicyStatement({ + actions: [ + 'ec2:CreateNetworkInterface', + 'ec2:DescribeNetworkInterfaces', + 'ec2:DeleteNetworkInterface', + 'ec2:AssignPrivateIpAddresses', + 'ec2:UnassignPrivateIpAddresses', + ], + resources: ['*'], // Adjust this to restrict to specific resources if needed + }) + ) + poolCacheBucket.grantRead(lambdaRole) poolCacheBucket2.grantRead(lambdaRole) poolCacheBucket3.grantRead(lambdaRole) @@ -104,6 +177,7 @@ export class RoutingLambdaStack extends cdk.NestedStack { const cachingRoutingLambda = new aws_lambda_nodejs.NodejsFunction(this, 'CachingRoutingLambda', { role: lambdaRole, + vpc: vpc, runtime: aws_lambda.Runtime.NODEJS_18_X, entry: path.join(__dirname, '../../lib/handlers/index.ts'), handler: 'quoteHandler', @@ -171,6 +245,8 @@ export class RoutingLambdaStack extends cdk.NestedStack { runtime: aws_lambda.Runtime.NODEJS_18_X, entry: path.join(__dirname, '../../lib/handlers/index.ts'), handler: 'quoteHandler', + vpc: vpc, + vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }, // 11/8/23: URA currently calls the Routing API with a timeout of 10 seconds. // Set this lambda's timeout to be slightly lower to give them time to // log the response in the event of a failure on our end.