import * as cdk from "aws-cdk-lib"; import * as ec2 from "aws-cdk-lib/aws-ec2"; import * as rds from "aws-cdk-lib/aws-rds"; import * as secretsmanager from "aws-cdk-lib/aws-secretsmanager"; import { Construct } from "constructs"; interface RdsStackProps extends cdk.StackProps { /** * Environment name (dev, staging, prod) */ environment: string; /** * VPC to deploy the database in */ vpc: ec2.IVpc; /** * Database credentials secret from SecretsStack */ databaseSecret: secretsmanager.ISecret; /** * Database name (default: rentall) */ databaseName?: string; /** * Instance type (default: t3.micro for Free Tier) */ instanceType?: ec2.InstanceType; /** * Allocated storage in GB (default: 20) */ allocatedStorage?: number; /** * Enable Multi-AZ deployment (default: false for dev/staging) */ multiAz?: boolean; /** * Backup retention days (default: 7) */ backupRetentionDays?: number; } export class RdsStack extends cdk.Stack { /** * The RDS database instance */ public readonly database: rds.DatabaseInstance; /** * Security group for the database */ public readonly databaseSecurityGroup: ec2.SecurityGroup; /** * Database endpoint address */ public readonly dbEndpoint: string; /** * Database port */ public readonly dbPort: number; constructor(scope: Construct, id: string, props: RdsStackProps) { super(scope, id, props); const { environment, vpc, databaseSecret, databaseName = "rentall", instanceType = ec2.InstanceType.of( ec2.InstanceClass.T3, ec2.InstanceSize.MICRO ), allocatedStorage = 20, multiAz = false, backupRetentionDays = 7, } = props; // Security group for the database this.databaseSecurityGroup = new ec2.SecurityGroup( this, "DatabaseSecurityGroup", { vpc, securityGroupName: `rentall-db-sg-${environment}`, description: `Security group for RDS database (${environment})`, allowAllOutbound: false, } ); // Create the RDS instance this.database = new rds.DatabaseInstance(this, "Database", { instanceIdentifier: `rentall-db-${environment}`, engine: rds.DatabaseInstanceEngine.postgres({ version: rds.PostgresEngineVersion.VER_15, }), instanceType, vpc, vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED, }, securityGroups: [this.databaseSecurityGroup], credentials: rds.Credentials.fromSecret(databaseSecret), databaseName, allocatedStorage, maxAllocatedStorage: allocatedStorage * 2, // Allow storage autoscaling up to 2x storageType: rds.StorageType.GP2, multiAz, autoMinorVersionUpgrade: true, deletionProtection: environment === "prod", removalPolicy: environment === "prod" ? cdk.RemovalPolicy.RETAIN : cdk.RemovalPolicy.DESTROY, backupRetention: cdk.Duration.days(backupRetentionDays), preferredBackupWindow: "03:00-04:00", // UTC preferredMaintenanceWindow: "Sun:04:00-Sun:05:00", // UTC storageEncrypted: true, monitoringInterval: cdk.Duration.seconds(60), enablePerformanceInsights: true, performanceInsightRetention: rds.PerformanceInsightRetention.DEFAULT, // 7 days (free) parameterGroup: new rds.ParameterGroup(this, "ParameterGroup", { engine: rds.DatabaseInstanceEngine.postgres({ version: rds.PostgresEngineVersion.VER_15, }), parameters: { // Enforce SSL connections "rds.force_ssl": "1", // Log slow queries (> 1 second) log_min_duration_statement: "1000", }, }), publiclyAccessible: false, }); this.dbEndpoint = this.database.dbInstanceEndpointAddress; this.dbPort = this.database.dbInstanceEndpointPort ? parseInt(this.database.dbInstanceEndpointPort) : 5432; // Outputs new cdk.CfnOutput(this, "DatabaseEndpoint", { value: this.database.dbInstanceEndpointAddress, description: "Database endpoint address", exportName: `DatabaseEndpoint-${environment}`, }); new cdk.CfnOutput(this, "DatabasePort", { value: this.database.dbInstanceEndpointPort, description: "Database port", exportName: `DatabasePort-${environment}`, }); new cdk.CfnOutput(this, "DatabaseSecurityGroupId", { value: this.databaseSecurityGroup.securityGroupId, description: "Database security group ID", exportName: `DatabaseSecurityGroupId-${environment}`, }); new cdk.CfnOutput(this, "DatabaseInstanceIdentifier", { value: this.database.instanceIdentifier, description: "Database instance identifier", exportName: `DatabaseInstanceIdentifier-${environment}`, }); } }