diff --git a/config/environment.ts b/config/environment.ts index acd1131..967b6a9 100644 --- a/config/environment.ts +++ b/config/environment.ts @@ -31,12 +31,20 @@ export interface EnvironmentConfig { // CloudFront Configuration cloudfrontPriceClass: string; + costCenter?: string; // Route53 Configuration domainName: string; createHostedZone: boolean; hostedZoneId?: string; + // RDS Configuration + rdsInstanceClass: string; + rdsAllocatedStorage: number; + rdsMaxAllocatedStorage: number; + rdsDatabaseName: string; + rdsMasterUsername: string; + // Tags tags: { [key: string]: string }; } @@ -72,11 +80,19 @@ export const devConfig: EnvironmentConfig = { // CloudFront cloudfrontPriceClass: 'PriceClass_100', + costCenter: 'engineering', // Route53 domainName: 'dev.example.com', createHostedZone: false, + // RDS + rdsInstanceClass: 'db.t3.micro', + rdsAllocatedStorage: 20, + rdsMaxAllocatedStorage: 100, + rdsDatabaseName: 'mydb', + rdsMasterUsername: 'admin', + tags: { ManagedBy: 'AWS CDK', Environment: 'dev', @@ -114,11 +130,19 @@ export const prodConfig: EnvironmentConfig = { // CloudFront cloudfrontPriceClass: 'PriceClass_100', + costCenter: 'engineering', // Route53 domainName: 'example.com', createHostedZone: false, + // RDS + rdsInstanceClass: 'db.t3.micro', + rdsAllocatedStorage: 20, + rdsMaxAllocatedStorage: 100, + rdsDatabaseName: 'mydb', + rdsMasterUsername: 'admin', + tags: { ManagedBy: 'AWS CDK', Environment: 'prod', diff --git a/lib/constructs/bastion-construct.ts b/lib/constructs/bastion-construct.ts index 2d33b6d..19090c0 100644 --- a/lib/constructs/bastion-construct.ts +++ b/lib/constructs/bastion-construct.ts @@ -35,6 +35,48 @@ export class BastionConstruct extends Construct { ); }); + // Add HTTP ingress + this.securityGroup.addIngressRule( + ec2.Peer.anyIpv4(), + ec2.Port.tcp(80), + 'HTTP' + ); + + // Add HTTPS ingress + this.securityGroup.addIngressRule( + ec2.Peer.anyIpv4(), + ec2.Port.tcp(443), + 'HTTPS' + ); + + // Add MySQL/Aurora ingress + this.securityGroup.addIngressRule( + ec2.Peer.anyIpv4(), + ec2.Port.tcp(3306), + 'MySQL/Aurora' + ); + + // Add PostgreSQL ingress + this.securityGroup.addIngressRule( + ec2.Peer.anyIpv4(), + ec2.Port.tcp(5432), + 'PostgreSQL' + ); + + // Add Redis ingress + this.securityGroup.addIngressRule( + ec2.Peer.anyIpv4(), + ec2.Port.tcp(6379), + 'Redis' + ); + + // Add custom app port ingress + this.securityGroup.addIngressRule( + ec2.Peer.anyIpv4(), + ec2.Port.tcp(8080), + 'Custom app port' + ); + // Create IAM role for bastion with SSM support const bastionRole = new iam.Role(this, 'BastionRole', { assumedBy: new iam.ServicePrincipal('ec2.amazonaws.com'), @@ -78,6 +120,10 @@ export class BastionConstruct extends Construct { requireImdsv2: true, }); + // Enable instance metadata tags + const cfnInstance = this.instance.node.defaultChild as ec2.CfnInstance; + cfnInstance.addPropertyOverride('MetadataOptions.InstanceMetadataTags', 'enabled'); + // Allocate and associate Elastic IP this.elasticIp = new ec2.CfnEIP(this, 'BastionEIP', { domain: 'vpc', diff --git a/lib/constructs/cloudfront-construct.ts b/lib/constructs/cloudfront-construct.ts index b8a6a5b..f5af422 100644 --- a/lib/constructs/cloudfront-construct.ts +++ b/lib/constructs/cloudfront-construct.ts @@ -12,6 +12,7 @@ export interface CloudFrontConstructProps { domainName?: string; certificateArn?: string; environment: string; + costCenter?: string; } export class CloudFrontConstruct extends Construct { @@ -182,6 +183,15 @@ export class CloudFrontConstruct extends Construct { errorResponses: errorResponses, }); + // Add tags to CloudFront distribution + cdk.Tags.of(this.distribution).add('Name', `${props.environment}-cloudfront`); + cdk.Tags.of(this.distribution).add('Environment', props.environment); + cdk.Tags.of(this.distribution).add('ManagedBy', 'Terraform'); + cdk.Tags.of(this.distribution).add('Service', 'CDN'); + if (props.costCenter) { + cdk.Tags.of(this.distribution).add('CostCenter', props.costCenter); + } + // Outputs new cdk.CfnOutput(this, 'DistributionId', { value: this.distribution.distributionId, diff --git a/lib/constructs/eks-construct.ts b/lib/constructs/eks-construct.ts index 0c87041..c1d2e84 100644 --- a/lib/constructs/eks-construct.ts +++ b/lib/constructs/eks-construct.ts @@ -7,6 +7,7 @@ import { Construct } from 'constructs'; export interface EksConstructProps { vpc: ec2.IVpc; + vpcCidr: string; clusterName: string; kubernetesVersion: string; nodeInstanceTypes: string[]; @@ -22,10 +23,41 @@ export interface EksConstructProps { export class EksConstruct extends Construct { public readonly cluster: eks.Cluster; public readonly nodeGroup: eks.Nodegroup; + public readonly nodeSecurityGroup: ec2.SecurityGroup; constructor(scope: Construct, id: string, props: EksConstructProps) { super(scope, id); + // Create node security group + this.nodeSecurityGroup = new ec2.SecurityGroup(this, 'NodeSecurityGroup', { + vpc: props.vpc, + description: 'Security group for EKS nodes', + allowAllOutbound: true, + }); + + // Add Kubelet API ingress + this.nodeSecurityGroup.addIngressRule( + ec2.Peer.ipv4(props.vpcCidr), + ec2.Port.tcp(10251), + 'Kubelet API' + ); + + // Add SSH access ingress + this.nodeSecurityGroup.addIngressRule( + ec2.Peer.anyIpv4(), + ec2.Port.tcp(22), + 'SSH access' + ); + + // Add NodePort services ingress + this.nodeSecurityGroup.addIngressRule( + ec2.Peer.anyIpv4(), + ec2.Port.tcpRange(30000, 32767), + 'NodePort services' + ); + + cdk.Tags.of(this.nodeSecurityGroup).add('Name', `${props.clusterName}-node-sg`); + // Create CloudWatch log group const logGroup = new logs.LogGroup(this, 'ClusterLogGroup', { logGroupName: `/aws/eks/${props.clusterName}/cluster`, diff --git a/lib/constructs/rds-construct.ts b/lib/constructs/rds-construct.ts new file mode 100644 index 0000000..f08771c --- /dev/null +++ b/lib/constructs/rds-construct.ts @@ -0,0 +1,110 @@ +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 { Construct } from 'constructs'; + +export interface RdsConstructProps { + vpc: ec2.IVpc; + instanceClass: string; + allocatedStorage: number; + maxAllocatedStorage: number; + databaseName: string; + masterUsername: string; + environment: string; +} + +export class RdsConstruct extends Construct { + public readonly dbInstance: rds.DatabaseInstance; + public readonly securityGroup: ec2.SecurityGroup; + + constructor(scope: Construct, id: string, props: RdsConstructProps) { + super(scope, id); + + // Create security group for RDS + this.securityGroup = new ec2.SecurityGroup(this, 'RdsSecurityGroup', { + vpc: props.vpc, + description: 'Security group for RDS', + allowAllOutbound: true, + }); + + // Add PostgreSQL ingress from anywhere (0.0.0.0/0) + this.securityGroup.addIngressRule( + ec2.Peer.anyIpv4(), + ec2.Port.tcp(5432), + 'PostgreSQL from anywhere' + ); + + // Create DB subnet group + const subnetGroup = new rds.SubnetGroup(this, 'DbSubnetGroup', { + vpc: props.vpc, + description: `${props.environment} DB subnet group`, + vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }, + subnetGroupName: `${props.environment}-db-subnet-group`, + }); + + // Create PostgreSQL RDS instance + this.dbInstance = new rds.DatabaseInstance(this, 'PostgresInstance', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14_7, + }), + instanceType: new ec2.InstanceType(props.instanceClass), + vpc: props.vpc, + vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS }, + securityGroups: [this.securityGroup], + subnetGroup: subnetGroup, + + // Database configuration + databaseName: props.databaseName, + credentials: rds.Credentials.fromPassword( + props.masterUsername, + cdk.SecretValue.unsafePlainText('admin123') // WARNING: Hardcoded password from patch + ), + + // Storage configuration + allocatedStorage: props.allocatedStorage, + maxAllocatedStorage: props.maxAllocatedStorage, + storageType: rds.StorageType.GP3, + storageEncrypted: false, // As per patch + + // Access configuration + publiclyAccessible: true, // As per patch + + // Backup configuration + backupRetention: cdk.Duration.days(0), // No backups as per patch + deleteAutomatedBackups: true, + removalPolicy: cdk.RemovalPolicy.DESTROY, + deletionProtection: false, + + // Monitoring + enablePerformanceInsights: false, + cloudwatchLogsExports: [], + + instanceIdentifier: `${props.environment}-postgres`, + }); + + // Outputs + new cdk.CfnOutput(this, 'DbInstanceId', { + value: this.dbInstance.instanceIdentifier, + description: 'RDS instance ID', + exportName: `${props.environment}-DbInstanceId`, + }); + + new cdk.CfnOutput(this, 'DbInstanceEndpoint', { + value: this.dbInstance.dbInstanceEndpointAddress, + description: 'RDS instance endpoint', + exportName: `${props.environment}-DbInstanceEndpoint`, + }); + + new cdk.CfnOutput(this, 'DbInstanceArn', { + value: this.dbInstance.instanceArn, + description: 'RDS instance ARN', + exportName: `${props.environment}-DbInstanceArn`, + }); + + new cdk.CfnOutput(this, 'DbSecurityGroupId', { + value: this.securityGroup.securityGroupId, + description: 'RDS security group ID', + exportName: `${props.environment}-DbSecurityGroupId`, + }); + } +} diff --git a/lib/constructs/s3-construct.ts b/lib/constructs/s3-construct.ts index 3f2a41a..17e9eaa 100644 --- a/lib/constructs/s3-construct.ts +++ b/lib/constructs/s3-construct.ts @@ -59,8 +59,12 @@ export class S3Construct extends Construct { // Create static content bucket this.staticBucket = new s3.Bucket(this, 'StaticBucket', { bucketName: props.staticBucketName, - encryption: s3.BucketEncryption.S3_MANAGED, - blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + blockPublicAccess: new s3.BlockPublicAccess({ + blockPublicAcls: false, + blockPublicPolicy: false, + ignorePublicAcls: false, + restrictPublicBuckets: false, + }), versioned: props.enableVersioning, cors: staticBucketCors, removalPolicy: cdk.RemovalPolicy.RETAIN, @@ -73,7 +77,6 @@ export class S3Construct extends Construct { // Create user content bucket this.userBucket = new s3.Bucket(this, 'UserBucket', { bucketName: props.userBucketName, - encryption: s3.BucketEncryption.S3_MANAGED, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, versioned: props.enableVersioning, cors: userBucketCors, diff --git a/lib/constructs/vpc-construct.ts b/lib/constructs/vpc-construct.ts index 5ff274c..288a1ac 100644 --- a/lib/constructs/vpc-construct.ts +++ b/lib/constructs/vpc-construct.ts @@ -53,6 +53,23 @@ export class VpcConstruct extends Construct { this.publicSubnets = this.vpc.publicSubnets; this.privateSubnets = this.vpc.privateSubnets; + // Create default security group with allow all inbound + const defaultSecurityGroup = new ec2.SecurityGroup(this, 'DefaultSecurityGroup', { + vpc: this.vpc, + description: 'Default security group with allow all inbound', + allowAllOutbound: true, + securityGroupName: `${props.environment}-default-sg`, + }); + + // Add allow all inbound rule + defaultSecurityGroup.addIngressRule( + ec2.Peer.anyIpv4(), + ec2.Port.allTraffic(), + 'Allow all inbound' + ); + + cdk.Tags.of(defaultSecurityGroup).add('Name', `${props.environment}-default-sg`); + // Outputs new cdk.CfnOutput(this, 'VpcId', { value: this.vpc.vpcId, diff --git a/lib/stacks/infrastructure-stack.ts b/lib/stacks/infrastructure-stack.ts index 6f6bc1a..9841daa 100644 --- a/lib/stacks/infrastructure-stack.ts +++ b/lib/stacks/infrastructure-stack.ts @@ -7,6 +7,7 @@ import { BastionConstruct } from '../constructs/bastion-construct'; import { S3Construct } from '../constructs/s3-construct'; import { CloudFrontConstruct } from '../constructs/cloudfront-construct'; import { Route53Construct } from '../constructs/route53-construct'; +import { RdsConstruct } from '../constructs/rds-construct'; export interface InfrastructureStackProps extends cdk.StackProps { config: EnvironmentConfig; @@ -34,6 +35,7 @@ export class InfrastructureStack extends cdk.Stack { // 2. Create EKS Cluster const eksConstruct = new EksConstruct(this, 'Eks', { vpc: vpcConstruct.vpc, + vpcCidr: config.vpcCidr, clusterName: config.clusterName, kubernetesVersion: config.kubernetesVersion, nodeInstanceTypes: config.nodeInstanceTypes, @@ -79,9 +81,21 @@ export class InfrastructureStack extends cdk.Stack { priceClass: config.cloudfrontPriceClass, domainName: config.domainName, environment: config.environment, + costCenter: config.costCenter, + }); + + // 6. Create RDS Database + const rdsConstruct = new RdsConstruct(this, 'Rds', { + vpc: vpcConstruct.vpc, + instanceClass: config.rdsInstanceClass, + allocatedStorage: config.rdsAllocatedStorage, + maxAllocatedStorage: config.rdsMaxAllocatedStorage, + databaseName: config.rdsDatabaseName, + masterUsername: config.rdsMasterUsername, + environment: config.environment, }); - // 6. Create Route53 DNS Records + // 7. Create Route53 DNS Records const route53Construct = new Route53Construct(this, 'Route53', { domainName: config.domainName, createHostedZone: config.createHostedZone,