349 lines
14 KiB
TypeScript
349 lines
14 KiB
TypeScript
import * as cdk from 'aws-cdk-lib';
|
|
import { Construct } from 'constructs';
|
|
import * as ec2 from 'aws-cdk-lib/aws-ec2';
|
|
import * as iam from 'aws-cdk-lib/aws-iam';
|
|
import * as dlm from 'aws-cdk-lib/aws-dlm';
|
|
import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
|
|
import * as s3 from 'aws-cdk-lib/aws-s3';
|
|
import * as cloudfront from 'aws-cdk-lib/aws-cloudfront';
|
|
import * as origins from 'aws-cdk-lib/aws-cloudfront-origins';
|
|
|
|
export class AwsCdkStack extends cdk.Stack {
|
|
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
|
|
super(scope, id, props);
|
|
|
|
// 1. Look up the existing Default VPC (Free, avoids NAT Gateway charges)
|
|
const vpc = ec2.Vpc.fromLookup(this, 'DefaultVpc', {
|
|
isDefault: true,
|
|
});
|
|
|
|
// 2. Create a Security Group for the Mail Server
|
|
const securityGroup = new ec2.SecurityGroup(this, 'internalsSG', {
|
|
vpc,
|
|
description: 'Security Group for self-hosted mail server',
|
|
allowAllOutbound: true,
|
|
});
|
|
|
|
// Allow SSH
|
|
securityGroup.addIngressRule(ec2.Peer.ipv4('16.113.57.0/24'), ec2.Port.tcp(22), 'Allow SSH access');
|
|
|
|
// Web Traffic (HTTP, HTTPS for Admin UI & Webmail / SSL certificates)
|
|
securityGroup.addIngressRule(ec2.Peer.ipv4('16.113.57.0/24'), ec2.Port.tcp(80), 'Allow HTTP');
|
|
securityGroup.addIngressRule(ec2.Peer.ipv4('16.113.57.0/24'), ec2.Port.tcp(443), 'Allow HTTPS');
|
|
|
|
// Email Service
|
|
securityGroup.addIngressRule(ec2.Peer.ipv4('16.113.57.0/24'), ec2.Port.tcp(9002), 'Allow Email Service');
|
|
|
|
// WireGuard VPN
|
|
securityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.udp(5010), 'Allow WireGuard VPN');
|
|
securityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(51821), 'Allow WireGuard Web UI');
|
|
|
|
// Woodpecker CI (Port 8000 open to public; Nginx on EC2 handles path-based security)
|
|
securityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(8000), 'Allow Woodpecker Reverse Proxy');
|
|
|
|
// Forgejo Git Service (Port 3000 and 2222 open to public IPv4 and IPv6)
|
|
securityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(3000), 'Allow Forgejo Web UI IPv4');
|
|
securityGroup.addIngressRule(ec2.Peer.anyIpv6(), ec2.Port.tcp(3000), 'Allow Forgejo Web UI IPv6');
|
|
securityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(2222), 'Allow Forgejo SSH IPv4');
|
|
securityGroup.addIngressRule(ec2.Peer.anyIpv6(), ec2.Port.tcp(2222), 'Allow Forgejo SSH IPv6');
|
|
|
|
// Django App Backends (Prod and Beta) - Open to public so frontend users can connect
|
|
securityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(8080), 'Allow Django Beta Backend (Public)');
|
|
securityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(8090), 'Allow Django Production Backend (Public)');
|
|
securityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(8082), 'Allow Django Customer Backend (Public)');
|
|
|
|
// pgAdmin 4 Web UI - Open to public
|
|
securityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(5050), 'Allow pgAdmin 4 Web UI IPv4');
|
|
securityGroup.addIngressRule(ec2.Peer.anyIpv6(), ec2.Port.tcp(5050), 'Allow pgAdmin 4 Web UI IPv6');
|
|
|
|
// pgweb Web UI - Restricted to specific IP
|
|
securityGroup.addIngressRule(ec2.Peer.ipv4('16.113.57.127/32'), ec2.Port.tcp(9001), 'Allow pgweb Web UI access');
|
|
|
|
// PostgreSQL Database - Open to public
|
|
securityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(5432), 'Allow PostgreSQL Database IPv4');
|
|
securityGroup.addIngressRule(ec2.Peer.anyIpv6(), ec2.Port.tcp(5432), 'Allow PostgreSQL Database IPv6');
|
|
|
|
|
|
// Access for betasuppliers.tipro.in
|
|
const betaSuppliersIpsV4 = ['84.32.84.217/32', '88.222.222.1/32'];
|
|
const betaSuppliersIpsV6 = ['2a02:4780:84:d14f:651c:db33:43dc:f853/128', '2a02:4780:84:f767:730d:c6:443b:2d78/128'];
|
|
const allowedPorts = [80, 443, 8080, 8090];
|
|
|
|
for (const ip of betaSuppliersIpsV4) {
|
|
for (const port of allowedPorts) {
|
|
securityGroup.addIngressRule(ec2.Peer.ipv4(ip), ec2.Port.tcp(port), `Allow port ${port} access for betasuppliers.tipro.in`);
|
|
}
|
|
}
|
|
for (const ip of betaSuppliersIpsV6) {
|
|
for (const port of allowedPorts) {
|
|
securityGroup.addIngressRule(ec2.Peer.ipv6(ip), ec2.Port.tcp(port), `Allow port ${port} access for betasuppliers.tipro.in`);
|
|
}
|
|
}
|
|
|
|
|
|
// 3a. Create DB Credentials Secret in AWS Secrets Manager
|
|
const dbSecret = new secretsmanager.Secret(this, 'SellerCentralDbSecret', {
|
|
secretName: 'seller-central/db-credentials',
|
|
description: 'Database credentials for the Seller Central application',
|
|
secretObjectValue: {
|
|
username: cdk.SecretValue.unsafePlainText('vignesh'),
|
|
host: cdk.SecretValue.unsafePlainText('127.0.0.1'),
|
|
password: cdk.SecretValue.unsafePlainText('vtechnosoft@123A'),
|
|
dbname: cdk.SecretValue.unsafePlainText('sellerprofile'),
|
|
},
|
|
});
|
|
|
|
|
|
// Create IAM Role for EC2 Instance (to allow CloudWatch Agent to write logs & metrics)
|
|
const role = new iam.Role(this, 'internalsRole', {
|
|
assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'),
|
|
managedPolicies: [
|
|
iam.ManagedPolicy.fromAwsManagedPolicyName('CloudWatchAgentServerPolicy'),
|
|
],
|
|
});
|
|
role.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonSSMManagedInstanceCore'));
|
|
role.addManagedPolicy(iam.ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ContainerRegistryPowerUser'));
|
|
|
|
// Grant EC2 role permission to read the DB secret
|
|
dbSecret.grantRead(role);
|
|
|
|
// S3 access for Forgejo storage
|
|
role.addToPolicy(new iam.PolicyStatement({
|
|
actions: [
|
|
's3:ListBucket',
|
|
's3:GetBucketLocation',
|
|
's3:GetObject',
|
|
's3:PutObject',
|
|
's3:DeleteObject',
|
|
],
|
|
resources: [
|
|
'arn:aws:s3:::forgeocodestorage-764709663363-ap-south-2-an',
|
|
'arn:aws:s3:::forgeocodestorage-764709663363-ap-south-2-an/*',
|
|
],
|
|
}));
|
|
|
|
// S3 access for Betasupplierdocument storage
|
|
role.addToPolicy(new iam.PolicyStatement({
|
|
actions: [
|
|
's3:ListBucket',
|
|
's3:GetBucketLocation',
|
|
's3:GetObject',
|
|
's3:PutObject',
|
|
's3:DeleteObject',
|
|
],
|
|
resources: [
|
|
'arn:aws:s3:::betasupplierdocument-764709663363-ap-south-2-an',
|
|
'arn:aws:s3:::betasupplierdocument-764709663363-ap-south-2-an/*',
|
|
],
|
|
}));
|
|
|
|
// Create the S3 bucket for supplier documents
|
|
const supplierDocumentBucket = new s3.Bucket(this, 'BetaSupplierDocumentStorageBucket', {
|
|
bucketName: 'betasupplierdocumentstorage',
|
|
removalPolicy: cdk.RemovalPolicy.DESTROY,
|
|
autoDeleteObjects: true,
|
|
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
|
|
encryption: s3.BucketEncryption.S3_MANAGED,
|
|
});
|
|
|
|
// Create CloudFront Distribution for the S3 bucket
|
|
const distribution = new cloudfront.Distribution(this, 'SupplierDocumentDistribution', {
|
|
defaultBehavior: {
|
|
origin: new origins.S3Origin(supplierDocumentBucket),
|
|
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
|
|
allowedMethods: cloudfront.AllowedMethods.ALLOW_GET_HEAD,
|
|
cachedMethods: cloudfront.CachedMethods.CACHE_GET_HEAD,
|
|
cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED,
|
|
},
|
|
});
|
|
|
|
// Grant EC2 IAM role read/write permissions to the new S3 bucket
|
|
supplierDocumentBucket.grantReadWrite(role);
|
|
|
|
// 3. Define the EC2 Instance (t4g.medium)
|
|
const instance = new ec2.Instance(this, 'internalsInstance', {
|
|
vpc,
|
|
vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC },
|
|
instanceType: ec2.InstanceType.of(ec2.InstanceClass.T4G, ec2.InstanceSize.MEDIUM),
|
|
machineImage: ec2.MachineImage.fromSsmParameter(
|
|
'/aws/service/canonical/ubuntu/server/24.04/stable/current/arm64/hvm/ebs-gp3/ami-id'
|
|
),
|
|
securityGroup,
|
|
role,
|
|
keyName: 'vignesh_tipro_apsouth2',
|
|
detailedMonitoring: true,
|
|
blockDevices: [
|
|
{
|
|
deviceName: '/dev/sda1',
|
|
volume: ec2.BlockDeviceVolume.ebs(50, { // 50GB Root Volume
|
|
volumeType: ec2.EbsDeviceVolumeType.GP3,
|
|
}),
|
|
},
|
|
],
|
|
});
|
|
|
|
// 4. Structured User Data installation
|
|
instance.addUserData(
|
|
// Ensure folder structures exist
|
|
'mkdir -p /home/ubuntu/basic_requirements',
|
|
'mkdir -p /home/ubuntu/build_tools',
|
|
|
|
// 4.1 Write and run installation for basic requirements (Python 3, pip, venv, git, build-essential)
|
|
'cat <<\'EOF\' > /home/ubuntu/basic_requirements/install.sh',
|
|
'#!/bin/bash',
|
|
'set -e',
|
|
'apt-get update -y',
|
|
'apt-get install -y ca-certificates curl gnupg lsb-release wget python3 python3-pip python3-venv git build-essential',
|
|
'EOF',
|
|
'chmod +x /home/ubuntu/basic_requirements/install.sh',
|
|
'/home/ubuntu/basic_requirements/install.sh',
|
|
|
|
// 4.2 Setup permissions for all user folders
|
|
'chown -R ubuntu:ubuntu /home/ubuntu/basic_requirements',
|
|
'chown -R ubuntu:ubuntu /home/ubuntu/build_tools',
|
|
|
|
// 4.3 Install CloudWatch Agent (ARM64 package for Graviton instance)
|
|
'wget https://s3.amazonaws.com/amazoncloudwatch-agent/ubuntu/arm64/latest/amazon-cloudwatch-agent.deb',
|
|
'dpkg -i -E ./amazon-cloudwatch-agent.deb',
|
|
'mkdir -p /opt/aws/amazon-cloudwatch-agent/etc',
|
|
'cat <<\'EOF\' > /opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json',
|
|
'{',
|
|
' "agent": {',
|
|
' "metrics_collection_interval": 60,',
|
|
' "run_as_user": "root"',
|
|
' },',
|
|
' "metrics": {',
|
|
' "metrics_collected": {',
|
|
' "disk": {',
|
|
' "measurement": ["used_percent"],',
|
|
' "metrics_collection_interval": 60,',
|
|
' "resources": ["/"]',
|
|
' },',
|
|
' "mem": {',
|
|
' "measurement": ["mem_used_percent"],',
|
|
' "metrics_collection_interval": 60',
|
|
' }',
|
|
' }',
|
|
' },',
|
|
' "logs": {',
|
|
' "logs_collected": {',
|
|
' "files": {',
|
|
' "collect_list": [',
|
|
' {',
|
|
' "file_path": "/home/ubuntu/seller_central_backend/logs/app.log",',
|
|
' "log_group_name": "/seller-central-backend/app",',
|
|
' "log_stream_name": "{instance_id}",',
|
|
' "retention_in_days": 30',
|
|
' },',
|
|
' {',
|
|
' "file_path": "/home/ubuntu/seller_central_backend/logs/error.log",',
|
|
' "log_group_name": "/seller-central-backend/error",',
|
|
' "log_stream_name": "{instance_id}",',
|
|
' "retention_in_days": 30',
|
|
' },',
|
|
' {',
|
|
' "file_path": "/home/ubuntu/seller_central_backend/logs/requests.log",',
|
|
' "log_group_name": "/seller-central-backend/requests",',
|
|
' "log_stream_name": "{instance_id}",',
|
|
' "retention_in_days": 30',
|
|
' },',
|
|
' {',
|
|
' "file_path": "/home/ubuntu/seller_central_backend/logs/gunicorn-access.log",',
|
|
' "log_group_name": "/seller-central-backend/gunicorn-access",',
|
|
' "log_stream_name": "{instance_id}",',
|
|
' "retention_in_days": 30',
|
|
' },',
|
|
' {',
|
|
' "file_path": "/home/ubuntu/seller_central_backend/logs/gunicorn-error.log",',
|
|
' "log_group_name": "/seller-central-backend/gunicorn-error",',
|
|
' "log_stream_name": "{instance_id}",',
|
|
' "retention_in_days": 30',
|
|
' }',
|
|
' ]',
|
|
' }',
|
|
' }',
|
|
' }',
|
|
'}',
|
|
'EOF',
|
|
'/opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -s -c file:/opt/aws/amazon-cloudwatch-agent/etc/amazon-cloudwatch-agent.json'
|
|
);
|
|
|
|
// 5. Associate your pre-created Elastic IP using its Allocation ID
|
|
new ec2.CfnEIPAssociation(this, 'internalsEIPAssociation', {
|
|
allocationId: 'eipalloc-0d1630fa0633874cb',
|
|
instanceId: instance.instanceId,
|
|
});
|
|
|
|
// 6. Automated Backup: Tag the EC2 instance and configure DLM Lifecycle Policy
|
|
cdk.Tags.of(instance).add('Backup', 'Daily');
|
|
|
|
// Create the IAM Role for DLM to manage snapshots
|
|
const dlmRole = new iam.Role(this, 'DLMLifecycleRole', {
|
|
assumedBy: new iam.ServicePrincipal('dlm.amazonaws.com'),
|
|
description: 'Role for DLM to manage EC2 daily snapshots',
|
|
});
|
|
|
|
dlmRole.addToPolicy(new iam.PolicyStatement({
|
|
actions: [
|
|
'ec2:CreateSnapshot',
|
|
'ec2:CreateSnapshots',
|
|
'ec2:DeleteSnapshot',
|
|
'ec2:DescribeInstances',
|
|
'ec2:DescribeVolumes',
|
|
'ec2:DescribeSnapshots',
|
|
'ec2:CreateTags',
|
|
],
|
|
resources: ['*'],
|
|
}));
|
|
|
|
// Create the DLM Lifecycle Policy (snapshots are stored automatically in S3)
|
|
new dlm.CfnLifecyclePolicy(this, 'DailySnapshotPolicy', {
|
|
description: 'Daily EC2 Instance Snapshot Policy',
|
|
executionRoleArn: dlmRole.roleArn,
|
|
state: 'ENABLED',
|
|
policyDetails: {
|
|
resourceTypes: ['INSTANCE'],
|
|
targetTags: [
|
|
{
|
|
key: 'Backup',
|
|
value: 'Daily',
|
|
},
|
|
],
|
|
schedules: [
|
|
{
|
|
name: 'DailySnapshotSchedule',
|
|
createRule: {
|
|
interval: 24,
|
|
intervalUnit: 'HOURS',
|
|
times: ['20:00'], // Runs daily at 20:00 UTC (1:30 AM IST)
|
|
},
|
|
retainRule: {
|
|
count: 7, // Keep the last 7 daily snapshots
|
|
},
|
|
copyTags: true,
|
|
},
|
|
],
|
|
},
|
|
});
|
|
|
|
// Outputs
|
|
new cdk.CfnOutput(this, 'internalsPublicIP', {
|
|
value: '16.113.57.127',
|
|
description: 'The Elastic IP address of your Mail Server',
|
|
});
|
|
|
|
new cdk.CfnOutput(this, 'DbSecretArn', {
|
|
value: dbSecret.secretArn,
|
|
description: 'ARN of the DB credentials secret in Secrets Manager',
|
|
});
|
|
|
|
new cdk.CfnOutput(this, 'SupplierDocumentCloudFrontDomain', {
|
|
value: distribution.distributionDomainName,
|
|
description: 'CloudFront Distribution Domain Name for Supplier Documents',
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|