import { Stack, StackProps, aws_apigateway as apigateway, aws_lambda, aws_ec2, Duration, } from 'aws-cdk-lib'; import * as aws_lambda_python_alpha from '@aws-cdk/aws-lambda-python-alpha'; import { Construct } from 'constructs'; import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs'; import { join } from 'path'; import { Runtime } from 'aws-cdk-lib/aws-lambda'; import { BuildConfig } from '../utils/build-config'; import { Period } from 'aws-cdk-lib/aws-apigateway'; import { RetentionDays } from 'aws-cdk-lib/aws-logs'; import { VpcEndpoint, VpcEndpointService } from 'aws-cdk-lib/aws-ec2'; import { PolicyStatement, PolicyDocument, Effect, AnyPrincipal } from 'aws-cdk-lib/aws-iam'; export class MccAPIInfraStack extends Stack { public readonly api: apigateway.RestApi; constructor( scope: Construct, id: string, buildConfig: BuildConfig, props: StackProps, ) { super(scope, id, props); const stackName = `${buildConfig.Environment}${buildConfig.ApplicationShortName}`; const vpc = aws_ec2.Vpc.fromLookup(this, `${stackName}Vpc`, { vpcId: buildConfig.Parameters.VPC, }); const executeApiVpcEndpointSecurityGroup = new aws_ec2.SecurityGroup(this, `${stackName}ExecuteApiVPCEsg`, { vpc, description: 'Security group for API Gateway VPC Endpoint', allowAllOutbound: true, }); executeApiVpcEndpointSecurityGroup.addIngressRule( aws_ec2.Peer.ipv4('10.0.0.0/8'), aws_ec2.Port.tcp(443), 'Allow inbound HTTPS traffic from 10.0.0.0/8' ); const executeApiVpcEndpoint = new aws_ec2.InterfaceVpcEndpoint(this, `${stackName}ExecuteApiVPCE`, { vpc, service: aws_ec2.InterfaceVpcEndpointAwsService.APIGATEWAY, privateDnsEnabled: true, subnets: { subnetType: aws_ec2.SubnetType.PRIVATE_WITH_EGRESS }, securityGroups: [executeApiVpcEndpointSecurityGroup] }); const lambdaFunction = new aws_lambda_python_alpha.PythonFunction(this, `${stackName}MCCApp`, { runtime: Runtime.PYTHON_3_11, handler: 'lambda_handler', timeout: Duration.seconds(30), entry: join(__dirname, '../../src'), index: 'run.py', memorySize: 1024, vpc, logRetention: RetentionDays.ONE_WEEK, environment: { POSTGRES_USER: process.env.POSTGRES_USER!, POSTGRES_PASSWORD: process.env.POSTGRES_PASSWORD!, POSTGRES_DB: 'mccdb', FLASK_APP: process.env.FLASK_APP!, FLASK_ENV: process.env.FLASK_ENV!, DATABASE_URL: process.env.DATABASE_URL!, HOST_ENV: process.env.HOST_ENV!, SECRET_KEY: process.env.SECRET_KEY!, }, }); const lambdaDBUpgradeFunction = new aws_lambda_python_alpha.PythonFunction(this, `${stackName}MCCAppDBUpgrade`, { runtime: Runtime.PYTHON_3_11, handler: 'lambda_handler_upgrade_db', timeout: Duration.minutes(5), entry: join(__dirname, '../../src'), index: 'run.py', memorySize: 1024, vpc, logRetention: RetentionDays.ONE_WEEK, functionName: `${stackName}MCCAppDBUpgrade`, environment: { POSTGRES_USER: process.env.POSTGRES_USER!, POSTGRES_PASSWORD: process.env.POSTGRES_PASSWORD!, POSTGRES_DB: 'mccdb', FLASK_APP: process.env.FLASK_APP!, FLASK_ENV: process.env.FLASK_ENV!, DATABASE_URL: process.env.DATABASE_URL!, HOST_ENV: process.env.HOST_ENV!, SECRET_KEY: process.env.SECRET_KEY!, }, }); const api = new apigateway.LambdaRestApi(this, `${stackName}MCCAPI`, { handler: lambdaFunction, proxy: true, deployOptions: { stageName: buildConfig.Environment, }, restApiName: `${buildConfig.ApplicationName} API`, description: `API for ${buildConfig.ApplicationName} in ${buildConfig.Environment}`, endpointConfiguration: { types: [apigateway.EndpointType.PRIVATE], vpcEndpoints: [executeApiVpcEndpoint], }, policy: new PolicyDocument({ statements: [ new PolicyStatement({ effect: Effect.ALLOW, principals: [new AnyPrincipal()], actions: ['execute-api:Invoke'], resources: ['execute-api:/*/*/*'], // Optionally, restrict by source VPC or VPC endpoint conditions: { StringEquals: { 'aws:SourceVpce': executeApiVpcEndpoint.vpcEndpointId } } }), new PolicyStatement({ effect: Effect.DENY, principals: [new AnyPrincipal()], actions: ['execute-api:Invoke'], resources: ['execute-api:/*/*/*'], // Optionally, restrict by source VPC or VPC endpoint conditions: { StringNotEquals: { 'aws:SourceVpce': executeApiVpcEndpoint.vpcEndpointId } } }), ], }), }); this.api = api; } }