Refactor stack.ts: reorganize imports, enhance security group definitions, and improve Fargate service configuration

This commit is contained in:
faris Aljohari
2025-07-09 04:46:53 -06:00
parent 30166810ca
commit ab59a310d9

View File

@ -1,12 +1,12 @@
import * as cdk from 'aws-cdk-lib'; import * as cdk from 'aws-cdk-lib';
import * as acm from 'aws-cdk-lib/aws-certificatemanager';
import * as ec2 from 'aws-cdk-lib/aws-ec2'; import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as ecr from 'aws-cdk-lib/aws-ecr';
import * as ecs from 'aws-cdk-lib/aws-ecs'; import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as ecsPatterns from 'aws-cdk-lib/aws-ecs-patterns'; import * as ecsPatterns from 'aws-cdk-lib/aws-ecs-patterns';
import * as rds from 'aws-cdk-lib/aws-rds';
import * as ecr from 'aws-cdk-lib/aws-ecr';
import * as logs from 'aws-cdk-lib/aws-logs';
import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2'; import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2';
import * as acm from 'aws-cdk-lib/aws-certificatemanager'; import * as logs from 'aws-cdk-lib/aws-logs';
import * as rds from 'aws-cdk-lib/aws-rds';
import * as route53 from 'aws-cdk-lib/aws-route53'; import * as route53 from 'aws-cdk-lib/aws-route53';
import { Construct } from 'constructs'; import { Construct } from 'constructs';
import * as dotenv from 'dotenv'; import * as dotenv from 'dotenv';
@ -49,11 +49,15 @@ export class BackendStack extends cdk.Stack {
}); });
// Security Groups // Security Groups
const dbSecurityGroup = new ec2.SecurityGroup(this, 'DatabaseSecurityGroup', { const dbSecurityGroup = new ec2.SecurityGroup(
vpc: this.vpc, this,
description: 'Security group for RDS PostgreSQL', 'DatabaseSecurityGroup',
allowAllOutbound: false, {
}); vpc: this.vpc,
description: 'Security group for RDS PostgreSQL',
allowAllOutbound: false,
},
);
const ecsSecurityGroup = new ec2.SecurityGroup(this, 'EcsSecurityGroup', { const ecsSecurityGroup = new ec2.SecurityGroup(this, 'EcsSecurityGroup', {
vpc: this.vpc, vpc: this.vpc,
@ -71,55 +75,70 @@ export class BackendStack extends cdk.Stack {
ecsSecurityGroup.addIngressRule( ecsSecurityGroup.addIngressRule(
albSecurityGroup, albSecurityGroup,
ec2.Port.tcp(3000), ec2.Port.tcp(3000),
'Allow ALB to connect to ECS service' 'Allow ALB to connect to ECS service',
); );
// Allow ECS to connect to RDS // Allow ECS to connect to RDS
dbSecurityGroup.addIngressRule( dbSecurityGroup.addIngressRule(
ecsSecurityGroup, ecsSecurityGroup,
ec2.Port.tcp(5432), ec2.Port.tcp(5432),
'Allow ECS to connect to PostgreSQL' 'Allow ECS to connect to PostgreSQL',
); );
// Temporary access for admin IP // Temporary access for admin IP
dbSecurityGroup.addIngressRule( dbSecurityGroup.addIngressRule(
ec2.Peer.ipv4('216.126.231.231/32'), ec2.Peer.ipv4('216.126.231.231/32'),
ec2.Port.tcp(5432), ec2.Port.tcp(5432),
'Temporary access from admin IP' 'Temporary access from admin IP',
); );
// Allow HTTP/HTTPS traffic to ALB // Allow HTTP/HTTPS traffic to ALB
albSecurityGroup.addIngressRule( albSecurityGroup.addIngressRule(
ec2.Peer.anyIpv4(), ec2.Peer.anyIpv4(),
ec2.Port.tcp(80), ec2.Port.tcp(80),
'Allow HTTP traffic' 'Allow HTTP traffic',
); );
albSecurityGroup.addIngressRule( albSecurityGroup.addIngressRule(
ec2.Peer.anyIpv4(), ec2.Peer.anyIpv4(),
ec2.Port.tcp(443), ec2.Port.tcp(443),
'Allow HTTPS traffic' 'Allow HTTPS traffic',
); );
const dbCluster = rds.DatabaseCluster.fromDatabaseClusterAttributes(this, 'SyncrowDatabase', { const dbCluster = rds.DatabaseCluster.fromDatabaseClusterAttributes(
clusterIdentifier: 'syncrow-backend', this,
instanceIdentifiers: ['syncrowdatabase-instance-1'], 'SyncrowDatabase',
engine: rds.DatabaseClusterEngine.auroraPostgres({ {
version: rds.AuroraPostgresEngineVersion.VER_16_6, clusterIdentifier: 'syncrow-backend',
}), instanceIdentifiers: ['syncrowdatabase-instance-1'],
port: 5432, engine: rds.DatabaseClusterEngine.auroraPostgres({
securityGroups: [ version: rds.AuroraPostgresEngineVersion.VER_16_6,
ec2.SecurityGroup.fromSecurityGroupId(this, 'ImportedDbSecurityGroup', 'sg-07e163f588b2bac25') }),
], port: 5432,
clusterEndpointAddress: 'syncrow-backend.cluster-criskv1sdkq4.me-central-1.rds.amazonaws.com', securityGroups: [
}); ec2.SecurityGroup.fromSecurityGroupId(
this,
'ImportedDbSecurityGroup',
'sg-07e163f588b2bac25',
),
],
clusterEndpointAddress:
'syncrow-backend.cluster-criskv1sdkq4.me-central-1.rds.amazonaws.com',
},
);
// Import the existing database secret separately // Import the existing database secret separately
const dbSecret = rds.DatabaseSecret.fromSecretCompleteArn(this, 'ImportedDbSecret', const dbSecret = rds.DatabaseSecret.fromSecretCompleteArn(
'arn:aws:secretsmanager:me-central-1:482311766496:secret:rds!cluster-43ec14cd-9301-43e2-aa79-d330a429a126-v0JDQN' this,
'ImportedDbSecret',
'arn:aws:secretsmanager:me-central-1:482311766496:secret:rds!cluster-43ec14cd-9301-43e2-aa79-d330a429a126-v0JDQN',
); );
// ECR Repository for Docker images - import existing repository // ECR Repository for Docker images - import existing repository
const ecrRepository = ecr.Repository.fromRepositoryName(this, 'SyncrowBackendRepo', 'syncrow-backend'); const ecrRepository = ecr.Repository.fromRepositoryName(
this,
'SyncrowBackendRepo',
'syncrow-backend',
);
// Output the correct ECR URI for this region // Output the correct ECR URI for this region
new cdk.CfnOutput(this, 'EcrRepositoryUriRegional', { new cdk.CfnOutput(this, 'EcrRepositoryUriRegional', {
@ -143,128 +162,165 @@ export class BackendStack extends cdk.Stack {
// Use existing wildcard certificate or create new one // Use existing wildcard certificate or create new one
const apiCertificate = props?.certificateArn const apiCertificate = props?.certificateArn
? acm.Certificate.fromCertificateArn(this, 'ApiCertificate', props.certificateArn) ? acm.Certificate.fromCertificateArn(
this,
'ApiCertificate',
props.certificateArn,
)
: new acm.Certificate(this, 'ApiCertificate', { : new acm.Certificate(this, 'ApiCertificate', {
domainName: 'api.syncrow.me', domainName: 'api.syncrow.me',
validation: acm.CertificateValidation.fromDns(), validation: acm.CertificateValidation.fromDns(),
}); });
// ECS Fargate Service with Application Load Balancer // ECS Fargate Service with Application Load Balancer
const fargateService = new ecsPatterns.ApplicationLoadBalancedFargateService(this, 'SyncrowBackendService', { const fargateService =
cluster, new ecsPatterns.ApplicationLoadBalancedFargateService(
memoryLimitMiB: 1024, this,
cpu: 512, 'SyncrowBackendService',
desiredCount: 1, {
domainName: 'api.syncrow.me', cluster,
domainZone: route53.HostedZone.fromLookup(this, 'SyncrowZone', { memoryLimitMiB: 1024,
domainName: 'syncrow.me', cpu: 512,
}), desiredCount: 1,
certificate: apiCertificate, domainName: 'api.syncrow.me',
protocol: elbv2.ApplicationProtocol.HTTPS, domainZone: route53.HostedZone.fromLookup(this, 'SyncrowZone', {
redirectHTTP: true, domainName: 'syncrow.me',
taskImageOptions: { }),
image: ecs.ContainerImage.fromEcrRepository(ecrRepository, 'latest'), certificate: apiCertificate,
containerPort: 3000, protocol: elbv2.ApplicationProtocol.HTTPS,
enableLogging: true, redirectHTTP: true,
environment: { taskImageOptions: {
// App settings image: ecs.ContainerImage.fromEcrRepository(
NODE_ENV: process.env.NODE_ENV || 'production', ecrRepository,
PORT: process.env.PORT || '3000', 'latest',
BASE_URL: process.env.BASE_URL || '', ),
containerPort: 3000,
enableLogging: true,
environment: {
// App settings
NODE_ENV: process.env.NODE_ENV || 'production',
PORT: process.env.PORT || '3000',
BASE_URL: process.env.BASE_URL || '',
// Database connection (CDK provides these automatically) // Database connection (CDK provides these automatically)
AZURE_POSTGRESQL_HOST: dbCluster.clusterEndpoint.hostname, AZURE_POSTGRESQL_HOST: dbCluster.clusterEndpoint.hostname,
AZURE_POSTGRESQL_PORT: '5432', AZURE_POSTGRESQL_PORT: '5432',
AZURE_POSTGRESQL_DATABASE: props?.databaseName || 'syncrow', AZURE_POSTGRESQL_DATABASE: props?.databaseName || 'postgres',
AZURE_POSTGRESQL_USER: 'postgres', AZURE_POSTGRESQL_USER: 'postgres',
AZURE_POSTGRESQL_SSL: process.env.AZURE_POSTGRESQL_SSL || 'false', AZURE_POSTGRESQL_SSL: process.env.AZURE_POSTGRESQL_SSL || 'false',
AZURE_POSTGRESQL_SYNC: process.env.AZURE_POSTGRESQL_SYNC || 'false', AZURE_POSTGRESQL_SYNC:
process.env.AZURE_POSTGRESQL_SYNC || 'false',
// JWT Configuration - CRITICAL: These must be set // JWT Configuration - CRITICAL: These must be set
JWT_SECRET: process.env.JWT_SECRET || 'syncrow-jwt-secret-key-2025-production-environment-very-secure-random-string', JWT_SECRET:
JWT_SECRET_REFRESH: process.env.JWT_SECRET_REFRESH || 'syncrow-refresh-secret-key-2025-production-environment-different-secure-string', process.env.JWT_SECRET ||
JWT_EXPIRE_TIME: process.env.JWT_EXPIRE_TIME || '1h', 'syncrow-jwt-secret-key-2025-production-environment-very-secure-random-string',
JWT_EXPIRE_TIME_REFRESH: process.env.JWT_EXPIRE_TIME_REFRESH || '7d', JWT_SECRET_REFRESH:
process.env.JWT_SECRET_REFRESH ||
'syncrow-refresh-secret-key-2025-production-environment-different-secure-string',
JWT_EXPIRE_TIME: process.env.JWT_EXPIRE_TIME || '1h',
JWT_EXPIRE_TIME_REFRESH:
process.env.JWT_EXPIRE_TIME_REFRESH || '7d',
// Firebase Configuration // Firebase Configuration
FIREBASE_API_KEY: process.env.FIREBASE_API_KEY || '', FIREBASE_API_KEY: process.env.FIREBASE_API_KEY || '',
FIREBASE_AUTH_DOMAIN: process.env.FIREBASE_AUTH_DOMAIN || '', FIREBASE_AUTH_DOMAIN: process.env.FIREBASE_AUTH_DOMAIN || '',
FIREBASE_PROJECT_ID: process.env.FIREBASE_PROJECT_ID || '', FIREBASE_PROJECT_ID: process.env.FIREBASE_PROJECT_ID || '',
FIREBASE_STORAGE_BUCKET: process.env.FIREBASE_STORAGE_BUCKET || '', FIREBASE_STORAGE_BUCKET:
FIREBASE_MESSAGING_SENDER_ID: process.env.FIREBASE_MESSAGING_SENDER_ID || '', process.env.FIREBASE_STORAGE_BUCKET || '',
FIREBASE_APP_ID: process.env.FIREBASE_APP_ID || '', FIREBASE_MESSAGING_SENDER_ID:
FIREBASE_MEASUREMENT_ID: process.env.FIREBASE_MEASUREMENT_ID || '', process.env.FIREBASE_MESSAGING_SENDER_ID || '',
FIREBASE_DATABASE_URL: process.env.FIREBASE_DATABASE_URL || '', FIREBASE_APP_ID: process.env.FIREBASE_APP_ID || '',
FIREBASE_MEASUREMENT_ID:
process.env.FIREBASE_MEASUREMENT_ID || '',
FIREBASE_DATABASE_URL: process.env.FIREBASE_DATABASE_URL || '',
// Tuya IoT Configuration // Tuya IoT Configuration
TUYA_EU_URL: process.env.TUYA_EU_URL || 'https://openapi.tuyaeu.com', TUYA_EU_URL:
TUYA_ACCESS_ID: process.env.TUYA_ACCESS_ID || '', process.env.TUYA_EU_URL || 'https://openapi.tuyaeu.com',
TUYA_ACCESS_KEY: process.env.TUYA_ACCESS_KEY || '', TUYA_ACCESS_ID: process.env.TUYA_ACCESS_ID || '',
TRUN_ON_TUYA_SOCKET: process.env.TRUN_ON_TUYA_SOCKET || '', TUYA_ACCESS_KEY: process.env.TUYA_ACCESS_KEY || '',
TRUN_ON_TUYA_SOCKET: process.env.TRUN_ON_TUYA_SOCKET || '',
// Email Configuration // Email Configuration
SMTP_HOST: process.env.SMTP_HOST || '', SMTP_HOST: process.env.SMTP_HOST || '',
SMTP_PORT: process.env.SMTP_PORT || '587', SMTP_PORT: process.env.SMTP_PORT || '587',
SMTP_SECURE: process.env.SMTP_SECURE || 'true', SMTP_SECURE: process.env.SMTP_SECURE || 'true',
SMTP_USER: process.env.SMTP_USER || '', SMTP_USER: process.env.SMTP_USER || '',
SMTP_PASSWORD: process.env.SMTP_PASSWORD || '', SMTP_PASSWORD: process.env.SMTP_PASSWORD || '',
// Mailtrap Configuration // Mailtrap Configuration
MAILTRAP_API_TOKEN: process.env.MAILTRAP_API_TOKEN || '', MAILTRAP_API_TOKEN: process.env.MAILTRAP_API_TOKEN || '',
MAILTRAP_INVITATION_TEMPLATE_UUID: process.env.MAILTRAP_INVITATION_TEMPLATE_UUID || '', MAILTRAP_INVITATION_TEMPLATE_UUID:
MAILTRAP_EDIT_USER_TEMPLATE_UUID: process.env.MAILTRAP_EDIT_USER_TEMPLATE_UUID || '', process.env.MAILTRAP_INVITATION_TEMPLATE_UUID || '',
MAILTRAP_DISABLE_TEMPLATE_UUID: process.env.MAILTRAP_DISABLE_TEMPLATE_UUID || '', MAILTRAP_EDIT_USER_TEMPLATE_UUID:
MAILTRAP_ENABLE_TEMPLATE_UUID: process.env.MAILTRAP_ENABLE_TEMPLATE_UUID || '', process.env.MAILTRAP_EDIT_USER_TEMPLATE_UUID || '',
MAILTRAP_DELETE_USER_TEMPLATE_UUID: process.env.MAILTRAP_DELETE_USER_TEMPLATE_UUID || '', MAILTRAP_DISABLE_TEMPLATE_UUID:
process.env.MAILTRAP_DISABLE_TEMPLATE_UUID || '',
MAILTRAP_ENABLE_TEMPLATE_UUID:
process.env.MAILTRAP_ENABLE_TEMPLATE_UUID || '',
MAILTRAP_DELETE_USER_TEMPLATE_UUID:
process.env.MAILTRAP_DELETE_USER_TEMPLATE_UUID || '',
// OneSignal Push Notifications // OneSignal Push Notifications
ONESIGNAL_APP_ID: process.env.ONESIGNAL_APP_ID || '', ONESIGNAL_APP_ID: process.env.ONESIGNAL_APP_ID || '',
ONESIGNAL_API_KEY: process.env.ONESIGNAL_API_KEY || '', ONESIGNAL_API_KEY: process.env.ONESIGNAL_API_KEY || '',
// Admin Configuration // Admin Configuration
SUPER_ADMIN_EMAIL: process.env.SUPER_ADMIN_EMAIL || 'admin@yourdomain.com', SUPER_ADMIN_EMAIL:
SUPER_ADMIN_PASSWORD: process.env.SUPER_ADMIN_PASSWORD || 'YourSecureAdminPassword123!', process.env.SUPER_ADMIN_EMAIL || 'admin@yourdomain.com',
SUPER_ADMIN_PASSWORD:
process.env.SUPER_ADMIN_PASSWORD ||
'YourSecureAdminPassword123!',
// Google OAuth // Google OAuth
GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID || '', GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID || '',
GOOGLE_CLIENT_SECRET: process.env.GOOGLE_CLIENT_SECRET || '', GOOGLE_CLIENT_SECRET: process.env.GOOGLE_CLIENT_SECRET || '',
// Other Configuration // Other Configuration
OTP_LIMITER: process.env.OTP_LIMITER || '5', OTP_LIMITER: process.env.OTP_LIMITER || '5',
SECRET_KEY: process.env.SECRET_KEY || 'another-random-secret-key-for-general-encryption', SECRET_KEY:
ACCESS_KEY: process.env.ACCESS_KEY || '', process.env.SECRET_KEY ||
DB_SYNC: process.env.DB_SYNC || 'txsrue', 'another-random-secret-key-for-general-encryption',
ACCESS_KEY: process.env.ACCESS_KEY || '',
DB_SYNC: process.env.DB_SYNC || 'txsrue',
// Redis (used?) // Redis (used?)
AZURE_REDIS_CONNECTIONSTRING: process.env.AZURE_REDIS_CONNECTIONSTRING || '', AZURE_REDIS_CONNECTIONSTRING:
process.env.AZURE_REDIS_CONNECTIONSTRING || '',
// Docker Registry (for deployment) // Docker Registry (for deployment)
DOCKER_REGISTRY_SERVER_URL: process.env.DOCKER_REGISTRY_SERVER_URL || '', DOCKER_REGISTRY_SERVER_URL:
DOCKER_REGISTRY_SERVER_USERNAME: process.env.DOCKER_REGISTRY_SERVER_USERNAME || '', process.env.DOCKER_REGISTRY_SERVER_URL || '',
DOCKER_REGISTRY_SERVER_PASSWORD: process.env.DOCKER_REGISTRY_SERVER_PASSWORD || '', DOCKER_REGISTRY_SERVER_USERNAME:
process.env.DOCKER_REGISTRY_SERVER_USERNAME || '',
DOCKER_REGISTRY_SERVER_PASSWORD:
process.env.DOCKER_REGISTRY_SERVER_PASSWORD || '',
// Doppler (if used for secrets management) // Doppler (if used for secrets management)
DOPPLER_PROJECT: process.env.DOPPLER_PROJECT || '', DOPPLER_PROJECT: process.env.DOPPLER_PROJECT || '',
DOPPLER_CONFIG: process.env.DOPPLER_CONFIG || '', DOPPLER_CONFIG: process.env.DOPPLER_CONFIG || '',
DOPPLER_ENVIRONMENT: process.env.DOPPLER_ENVIRONMENT || '', DOPPLER_ENVIRONMENT: process.env.DOPPLER_ENVIRONMENT || '',
// Azure specific // Azure specific
WEBSITES_ENABLE_APP_SERVICE_STORAGE: process.env.WEBSITES_ENABLE_APP_SERVICE_STORAGE || 'false', WEBSITES_ENABLE_APP_SERVICE_STORAGE:
process.env.WEBSITES_ENABLE_APP_SERVICE_STORAGE || 'false',
},
secrets: {
AZURE_POSTGRESQL_PASSWORD: ecs.Secret.fromSecretsManager(
dbSecret,
'password',
),
},
logDriver: ecs.LogDrivers.awsLogs({
streamPrefix: 'syncrow-backend',
logGroup,
}),
},
publicLoadBalancer: true,
securityGroups: [ecsSecurityGroup],
}, },
secrets: { );
AZURE_POSTGRESQL_PASSWORD: ecs.Secret.fromSecretsManager(
dbSecret,
'password'
),
},
logDriver: ecs.LogDrivers.awsLogs({
streamPrefix: 'syncrow-backend',
logGroup,
}),
},
publicLoadBalancer: true,
securityGroups: [ecsSecurityGroup],
});
// Add security group to load balancer after creation // Add security group to load balancer after creation
fargateService.loadBalancer.addSecurityGroup(albSecurityGroup); fargateService.loadBalancer.addSecurityGroup(albSecurityGroup);