infrastructure updates

This commit is contained in:
jackiettran
2026-01-15 17:17:06 -05:00
parent a3ef343326
commit 826e4f2ed5
4 changed files with 288 additions and 25 deletions

View File

@@ -3,6 +3,7 @@ import * as lambda from "aws-cdk-lib/aws-lambda";
import * as iam from "aws-cdk-lib/aws-iam";
import * as scheduler from "aws-cdk-lib/aws-scheduler";
import * as sqs from "aws-cdk-lib/aws-sqs";
import * as ec2 from "aws-cdk-lib/aws-ec2";
import { Construct } from "constructs";
import * as path from "path";
@@ -36,6 +37,16 @@ interface ConditionCheckLambdaStackProps extends cdk.StackProps {
* Whether emails are enabled
*/
emailEnabled?: boolean;
/**
* VPC for Lambda function (required for network isolation)
*/
vpc: ec2.IVpc;
/**
* Security group for Lambda function
*/
lambdaSecurityGroup: ec2.ISecurityGroup;
}
export class ConditionCheckLambdaStack extends cdk.Stack {
@@ -73,6 +84,8 @@ export class ConditionCheckLambdaStack extends cdk.Stack {
sesFromEmail,
sesFromName = "Village Share",
emailEnabled = true,
vpc,
lambdaSecurityGroup,
} = props;
// Dead Letter Queue for failed Lambda invocations
@@ -126,6 +139,13 @@ export class ConditionCheckLambdaStack extends cdk.Stack {
})
);
// VPC permissions - use AWS managed policy for Lambda VPC access
lambdaRole.addManagedPolicy(
iam.ManagedPolicy.fromAwsManagedPolicyName(
"service-role/AWSLambdaVPCAccessExecutionRole"
)
);
// Lambda function
this.lambdaFunction = new lambda.Function(
this,
@@ -171,6 +191,12 @@ export class ConditionCheckLambdaStack extends cdk.Stack {
deadLetterQueue: this.deadLetterQueue,
retryAttempts: 2,
description: "Sends condition check reminder emails for rentals",
// VPC configuration for network isolation
vpc,
vpcSubnets: {
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
},
securityGroups: [lambdaSecurityGroup],
}
);

View File

@@ -4,6 +4,7 @@ import * as iam from "aws-cdk-lib/aws-iam";
import * as s3 from "aws-cdk-lib/aws-s3";
import * as s3n from "aws-cdk-lib/aws-s3-notifications";
import * as sqs from "aws-cdk-lib/aws-sqs";
import * as ec2 from "aws-cdk-lib/aws-ec2";
import { Construct } from "constructs";
import * as path from "path";
@@ -22,6 +23,16 @@ interface ImageProcessorLambdaStackProps extends cdk.StackProps {
* Frontend URL for CORS configuration
*/
frontendUrl: string;
/**
* VPC for Lambda function (required for network isolation)
*/
vpc: ec2.IVpc;
/**
* Security group for Lambda function
*/
lambdaSecurityGroup: ec2.ISecurityGroup;
}
export class ImageProcessorLambdaStack extends cdk.Stack {
@@ -47,7 +58,7 @@ export class ImageProcessorLambdaStack extends cdk.Stack {
) {
super(scope, id, props);
const { environment, databaseUrl, frontendUrl } = props;
const { environment, databaseUrl, frontendUrl, vpc, lambdaSecurityGroup } = props;
// Dead Letter Queue for failed Lambda invocations
this.deadLetterQueue = new sqs.Queue(this, "ImageProcessorDLQ", {
@@ -143,6 +154,13 @@ export class ImageProcessorLambdaStack extends cdk.Stack {
})
);
// VPC permissions - use AWS managed policy for Lambda VPC access
lambdaRole.addManagedPolicy(
iam.ManagedPolicy.fromAwsManagedPolicyName(
"service-role/AWSLambdaVPCAccessExecutionRole"
)
);
// Lambda function
this.lambdaFunction = new lambda.Function(this, "ImageProcessorLambda", {
functionName: `image-processor-${environment}`,
@@ -183,6 +201,12 @@ export class ImageProcessorLambdaStack extends cdk.Stack {
retryAttempts: 2,
description:
"Processes uploaded images: extracts metadata and strips EXIF",
// VPC configuration for network isolation
vpc,
vpcSubnets: {
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
},
securityGroups: [lambdaSecurityGroup],
});
// S3 event notification for staging uploads

View File

@@ -0,0 +1,176 @@
import * as cdk from "aws-cdk-lib";
import * as ec2 from "aws-cdk-lib/aws-ec2";
import { Construct } from "constructs";
interface VpcStackProps extends cdk.StackProps {
/**
* Environment name (staging, prod)
*/
environment: string;
/**
* Maximum number of AZs to use (default: 2)
*/
maxAzs?: number;
/**
* Number of NAT Gateways (default: 1 for cost optimization)
* Use 2 for high availability in production
*/
natGateways?: number;
}
export class VpcStack extends cdk.Stack {
/**
* The VPC created by this stack
*/
public readonly vpc: ec2.Vpc;
/**
* Security group for Lambda functions
*/
public readonly lambdaSecurityGroup: ec2.SecurityGroup;
/**
* S3 Gateway endpoint (free)
*/
public readonly s3Endpoint: ec2.GatewayVpcEndpoint;
constructor(scope: Construct, id: string, props: VpcStackProps) {
super(scope, id, props);
const { environment, maxAzs = 2, natGateways = 1 } = props;
// Create VPC with public and private subnets
this.vpc = new ec2.Vpc(this, "VillageShareVpc", {
vpcName: `village-share-vpc-${environment}`,
ipAddresses: ec2.IpAddresses.cidr("10.0.0.0/16"),
maxAzs,
natGateways,
subnetConfiguration: [
{
name: "Public",
subnetType: ec2.SubnetType.PUBLIC,
cidrMask: 24,
mapPublicIpOnLaunch: false,
},
{
name: "Private",
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
cidrMask: 24,
},
{
name: "Isolated",
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
cidrMask: 24,
},
],
// Enable DNS support for VPC endpoints
enableDnsHostnames: true,
enableDnsSupport: true,
});
// Security group for Lambda functions
this.lambdaSecurityGroup = new ec2.SecurityGroup(
this,
"LambdaSecurityGroup",
{
vpc: this.vpc,
securityGroupName: `lambda-sg-${environment}`,
description: "Security group for Lambda functions in VPC",
allowAllOutbound: true, // Lambda needs outbound for AWS services
}
);
// Security group for VPC endpoints
const vpcEndpointSecurityGroup = new ec2.SecurityGroup(
this,
"VpcEndpointSecurityGroup",
{
vpc: this.vpc,
securityGroupName: `vpc-endpoint-sg-${environment}`,
description: "Security group for VPC Interface Endpoints",
allowAllOutbound: false,
}
);
// Allow HTTPS traffic from Lambda security group to VPC endpoints
vpcEndpointSecurityGroup.addIngressRule(
this.lambdaSecurityGroup,
ec2.Port.tcp(443),
"Allow HTTPS from Lambda functions"
);
// S3 Gateway Endpoint (FREE - no NAT charges for S3 traffic)
this.s3Endpoint = this.vpc.addGatewayEndpoint("S3Endpoint", {
service: ec2.GatewayVpcEndpointAwsService.S3,
subnets: [{ subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }],
});
// SES Interface Endpoint (for sending emails without NAT)
this.vpc.addInterfaceEndpoint("SesEndpoint", {
service: ec2.InterfaceVpcEndpointAwsService.SES,
subnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
securityGroups: [vpcEndpointSecurityGroup],
privateDnsEnabled: true,
});
// SQS Interface Endpoint (for DLQ access)
this.vpc.addInterfaceEndpoint("SqsEndpoint", {
service: ec2.InterfaceVpcEndpointAwsService.SQS,
subnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
securityGroups: [vpcEndpointSecurityGroup],
privateDnsEnabled: true,
});
// CloudWatch Logs Interface Endpoint
this.vpc.addInterfaceEndpoint("CloudWatchLogsEndpoint", {
service: ec2.InterfaceVpcEndpointAwsService.CLOUDWATCH_LOGS,
subnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
securityGroups: [vpcEndpointSecurityGroup],
privateDnsEnabled: true,
});
// Scheduler Interface Endpoint (for EventBridge Scheduler)
// Note: EventBridge Scheduler uses the scheduler.{region}.amazonaws.com endpoint
this.vpc.addInterfaceEndpoint("SchedulerEndpoint", {
service: new ec2.InterfaceVpcEndpointService(
`com.amazonaws.${cdk.Stack.of(this).region}.scheduler`
),
subnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS },
securityGroups: [vpcEndpointSecurityGroup],
privateDnsEnabled: true,
});
// Add tags to subnets for easy identification
cdk.Tags.of(this.vpc).add("Environment", environment);
cdk.Tags.of(this.vpc).add("Project", "village-share");
// Outputs
new cdk.CfnOutput(this, "VpcId", {
value: this.vpc.vpcId,
description: "VPC ID",
exportName: `VpcId-${environment}`,
});
new cdk.CfnOutput(this, "VpcCidr", {
value: this.vpc.vpcCidrBlock,
description: "VPC CIDR block",
exportName: `VpcCidr-${environment}`,
});
new cdk.CfnOutput(this, "PrivateSubnetIds", {
value: this.vpc
.selectSubnets({ subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS })
.subnetIds.join(","),
description: "Private subnet IDs",
exportName: `PrivateSubnetIds-${environment}`,
});
new cdk.CfnOutput(this, "LambdaSecurityGroupId", {
value: this.lambdaSecurityGroup.securityGroupId,
description: "Security group ID for Lambda functions",
exportName: `LambdaSecurityGroupId-${environment}`,
});
}
}