cdkinfracode/aws_cdk/lib/aws_cdk-stack.ts

346 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');
// 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',
});
}
}