ℹ️ What is Infrastructure as Code (IaC)?
Infrastructure as Code is the practice of managing and provisioning computing infrastructure through machine-readable definition files, rather than physical hardware configuration or interactive configuration tools.
🚀 Operational Excellence:
💰 Cost Optimization:
🔒 Security & Compliance:
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Production-ready VPC with multi-AZ NAT Gateways, Flow Logs, and Security Groups'
Parameters:
VpcCidr:
Type: String
Default: '10.10.0.0/16'
Description: 'CIDR block for the VPC'
Environment:
Type: String
Default: 'prod'
AllowedValues: ['dev', 'staging', 'prod']
Description: 'Environment name'
Resources:
# VPC
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VpcCidr
EnableDnsHostnames: true
EnableDnsSupport: true
Tags:
- Key: Name
Value: !Sub '${Environment}-VPC'
- Key: Environment
Value: !Ref Environment
# Internet Gateway
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub '${Environment}-IGW'
InternetGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: !Ref InternetGateway
VpcId: !Ref VPC
# Public Subnets
PublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select [0, !GetAZs '']
CidrBlock: !Select [0, !Cidr [!Ref VpcCidr, 4, 8]]
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub '${Environment}-Public-Subnet-AZ1'
PublicSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select [1, !GetAZs '']
CidrBlock: !Select [1, !Cidr [!Ref VpcCidr, 4, 8]]
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub '${Environment}-Public-Subnet-AZ2'
# Private Subnets
PrivateSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select [0, !GetAZs '']
CidrBlock: !Select [2, !Cidr [!Ref VpcCidr, 4, 8]]
Tags:
- Key: Name
Value: !Sub '${Environment}-Private-Subnet-AZ1'
PrivateSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select [1, !GetAZs '']
CidrBlock: !Select [3, !Cidr [!Ref VpcCidr, 4, 8]]
Tags:
- Key: Name
Value: !Sub '${Environment}-Private-Subnet-AZ2'
# NAT Gateways
NatGateway1EIP:
Type: AWS::EC2::EIP
DependsOn: InternetGatewayAttachment
Properties:
Domain: vpc
Tags:
- Key: Name
Value: !Sub '${Environment}-NAT-EIP-AZ1'
NatGateway2EIP:
Type: AWS::EC2::EIP
DependsOn: InternetGatewayAttachment
Properties:
Domain: vpc
Tags:
- Key: Name
Value: !Sub '${Environment}-NAT-EIP-AZ2'
NatGateway1:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt NatGateway1EIP.AllocationId
SubnetId: !Ref PublicSubnet1
Tags:
- Key: Name
Value: !Sub '${Environment}-NAT-Gateway-AZ1'
NatGateway2:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt NatGateway2EIP.AllocationId
SubnetId: !Ref PublicSubnet2
Tags:
- Key: Name
Value: !Sub '${Environment}-NAT-Gateway-AZ2'
# Route Tables
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub '${Environment}-Public-Routes'
DefaultPublicRoute:
Type: AWS::EC2::Route
DependsOn: InternetGatewayAttachment
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTable
SubnetId: !Ref PublicSubnet1
PublicSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PublicRouteTable
SubnetId: !Ref PublicSubnet2
PrivateRouteTable1:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub '${Environment}-Private-Routes-AZ1'
DefaultPrivateRoute1:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PrivateRouteTable1
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NatGateway1
PrivateSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PrivateRouteTable1
SubnetId: !Ref PrivateSubnet1
PrivateRouteTable2:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub '${Environment}-Private-Routes-AZ2'
DefaultPrivateRoute2:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PrivateRouteTable2
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NatGateway2
PrivateSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId: !Ref PrivateRouteTable2
SubnetId: !Ref PrivateSubnet2
# Security Groups
PublicSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub '${Environment}-Public-SG'
GroupDescription: 'Security group for public subnet resources'
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0 # Restrict this in production
Description: 'SSH access'
- IpProtocol: icmp
FromPort: -1
ToPort: -1
CidrIp: !Ref VpcCidr
Description: 'ICMP within VPC'
Tags:
- Key: Name
Value: !Sub '${Environment}-Public-SG'
PrivateSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub '${Environment}-Private-SG'
GroupDescription: 'Security group for private subnet resources'
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
SourceSecurityGroupId: !Ref PublicSecurityGroup
Description: 'SSH from public subnet'
- IpProtocol: icmp
FromPort: -1
ToPort: -1
CidrIp: !Ref VpcCidr
Description: 'ICMP within VPC'
SecurityGroupEgress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
Description: 'HTTPS to AWS services and VPC endpoints'
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
Description: 'HTTP for package updates'
Tags:
- Key: Name
Value: !Sub '${Environment}-Private-SG'
VPCEndpointsSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub '${Environment}-VPCEndpoints-SG'
GroupDescription: 'Security group for VPC endpoints'
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: !Ref VpcCidr
Description: 'HTTPS access from VPC resources'
Tags:
- Key: Name
Value: !Sub '${Environment}-VPCEndpoints-SG'
# VPC Endpoints for Session Manager
SSMEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VPC
ServiceName: !Sub 'com.amazonaws.${AWS::Region}.ssm'
VpcEndpointType: Interface
SubnetIds:
- !Ref PrivateSubnet1
- !Ref PrivateSubnet2
SecurityGroupIds:
- !Ref VPCEndpointsSecurityGroup
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal: '*'
Action:
- ssm:*
Resource: '*'
SSMMessagesEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VPC
ServiceName: !Sub 'com.amazonaws.${AWS::Region}.ssmmessages'
VpcEndpointType: Interface
SubnetIds:
- !Ref PrivateSubnet1
- !Ref PrivateSubnet2
SecurityGroupIds:
- !Ref VPCEndpointsSecurityGroup
EC2MessagesEndpoint:
Type: AWS::EC2::VPCEndpoint
Properties:
VpcId: !Ref VPC
ServiceName: !Sub 'com.amazonaws.${AWS::Region}.ec2messages'
VpcEndpointType: Interface
SubnetIds:
- !Ref PrivateSubnet1
- !Ref PrivateSubnet2
SecurityGroupIds:
- !Ref VPCEndpointsSecurityGroup
# VPC Flow Logs
VPCFlowLogRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: vpc-flow-logs.amazonaws.com
Action: sts:AssumeRole
Policies:
- PolicyName: flowlogsDeliveryRolePolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
- logs:DescribeLogGroups
- logs:DescribeLogStreams
Resource: '*'
VPCFlowLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub '/aws/vpc/flowlogs/${Environment}'
RetentionInDays: 30
VPCFlowLog:
Type: AWS::EC2::FlowLog
Properties:
ResourceType: VPC
ResourceId: !Ref VPC
TrafficType: ALL
LogDestinationType: cloud-watch-logs
LogGroupName: !Ref VPCFlowLogGroup
DeliverLogsPermissionArn: !GetAtt VPCFlowLogRole.Arn
Tags:
- Key: Name
Value: !Sub '${Environment}-VPC-FlowLogs'
Outputs:
VPC:
Description: 'VPC ID'
Value: !Ref VPC
Export:
Name: !Sub '${Environment}-VPC-ID'
PublicSubnets:
Description: 'Public subnet IDs'
Value: !Join [',', [!Ref PublicSubnet1, !Ref PublicSubnet2]]
Export:
Name: !Sub '${Environment}-Public-Subnets'
PrivateSubnets:
Description: 'Private subnet IDs'
Value: !Join [',', [!Ref PrivateSubnet1, !Ref PrivateSubnet2]]
Export:
Name: !Sub '${Environment}-Private-Subnets'
PublicSecurityGroup:
Description: 'Public security group ID'
Value: !Ref PublicSecurityGroup
Export:
Name: !Sub '${Environment}-Public-SG'
PrivateSecurityGroup:
Description: 'Private security group ID'
Value: !Ref PrivateSecurityGroup
Export:
Name: !Sub '${Environment}-Private-SG'
VPCEndpointsSecurityGroup:
Description: 'VPC Endpoints security group ID'
Value: !Ref VPCEndpointsSecurityGroup
Export:
Name: !Sub '${Environment}-VPCEndpoints-SG'
NATGateway1:
Description: 'NAT Gateway 1 ID'
Value: !Ref NatGateway1
Export:
Name: !Sub '${Environment}-NAT-Gateway-1'
NATGateway2:
Description: 'NAT Gateway 2 ID'
Value: !Ref NatGateway2
Export:
Name: !Sub '${Environment}-NAT-Gateway-2'
import * as cdk from 'aws-cdk-lib';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as logs from 'aws-cdk-lib/aws-logs';
import * as iam from 'aws-cdk-lib/aws-iam';
import { Construct } from 'constructs';
export class ProductionVpcStack extends cdk.Stack {
public readonly vpc: ec2.Vpc;
public readonly publicSecurityGroup: ec2.SecurityGroup;
public readonly privateSecurityGroup: ec2.SecurityGroup;
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Create VPC with public and private subnets
this.vpc = new ec2.Vpc(this, 'ProductionVPC', {
ipAddresses: ec2.IpAddresses.cidr('10.10.0.0/16'),
maxAzs: 2,
subnetConfiguration: [
{
cidrMask: 24,
name: 'Public',
subnetType: ec2.SubnetType.PUBLIC,
},
{
cidrMask: 24,
name: 'Private',
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
},
],
natGateways: 2, // One per AZ for high availability
enableDnsHostnames: true,
enableDnsSupport: true,
});
// Create security groups
this.publicSecurityGroup = new ec2.SecurityGroup(this, 'PublicSG', {
vpc: this.vpc,
description: 'Security group for public subnet resources',
allowAllOutbound: true,
});
this.publicSecurityGroup.addIngressRule(
ec2.Peer.anyIpv4(),
ec2.Port.tcp(22),
'SSH access'
);
this.publicSecurityGroup.addIngressRule(
ec2.Peer.ipv4(this.vpc.vpcCidrBlock),
ec2.Port.allIcmp(),
'ICMP within VPC'
);
this.privateSecurityGroup = new ec2.SecurityGroup(this, 'PrivateSG', {
vpc: this.vpc,
description: 'Security group for private subnet resources',
allowAllOutbound: false, // We'll define specific outbound rules
});
this.privateSecurityGroup.addIngressRule(
this.publicSecurityGroup,
ec2.Port.tcp(22),
'SSH from public subnet'
);
this.privateSecurityGroup.addIngressRule(
ec2.Peer.ipv4(this.vpc.vpcCidrBlock),
ec2.Port.allIcmp(),
'ICMP within VPC'
);
// Add specific outbound rules for private security group
this.privateSecurityGroup.addEgressRule(
ec2.Peer.anyIpv4(),
ec2.Port.tcp(443),
'HTTPS to AWS services and VPC endpoints'
);
this.privateSecurityGroup.addEgressRule(
ec2.Peer.anyIpv4(),
ec2.Port.tcp(80),
'HTTP for package updates'
);
// Create VPC Endpoints Security Group
const vpcEndpointsSecurityGroup = new ec2.SecurityGroup(this, 'VPCEndpointsSG', {
vpc: this.vpc,
description: 'Security group for VPC endpoints',
allowAllOutbound: true,
});
vpcEndpointsSecurityGroup.addIngressRule(
ec2.Peer.ipv4(this.vpc.vpcCidrBlock),
ec2.Port.tcp(443),
'HTTPS access from VPC resources'
);
// Create VPC Endpoints for Session Manager
const ssmEndpoint = new ec2.InterfaceVpcEndpoint(this, 'SSMEndpoint', {
vpc: this.vpc,
service: ec2.InterfaceVpcEndpointAwsService.SSM,
subnets: {
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
},
securityGroups: [vpcEndpointsSecurityGroup],
});
const ssmMessagesEndpoint = new ec2.InterfaceVpcEndpoint(this, 'SSMMessagesEndpoint', {
vpc: this.vpc,
service: ec2.InterfaceVpcEndpointAwsService.SSM_MESSAGES,
subnets: {
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
},
securityGroups: [vpcEndpointsSecurityGroup],
});
const ec2MessagesEndpoint = new ec2.InterfaceVpcEndpoint(this, 'EC2MessagesEndpoint', {
vpc: this.vpc,
service: ec2.InterfaceVpcEndpointAwsService.EC2_MESSAGES,
subnets: {
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
},
securityGroups: [vpcEndpointsSecurityGroup],
});
// Enable VPC Flow Logs
const flowLogRole = new iam.Role(this, 'FlowLogRole', {
assumedBy: new iam.ServicePrincipal('vpc-flow-logs.amazonaws.com'),
inlinePolicies: {
FlowLogDeliveryRolePolicy: new iam.PolicyDocument({
statements: [
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: [
'logs:CreateLogGroup',
'logs:CreateLogStream',
'logs:PutLogEvents',
'logs:DescribeLogGroups',
'logs:DescribeLogStreams',
],
resources: ['*'],
}),
],
}),
},
});
const logGroup = new logs.LogGroup(this, 'VPCFlowLogGroup', {
logGroupName: '/aws/vpc/flowlogs/production',
retention: logs.RetentionDays.ONE_MONTH,
});
new ec2.FlowLog(this, 'VPCFlowLog', {
resourceType: ec2.FlowLogResourceType.fromVpc(this.vpc),
destination: ec2.FlowLogDestination.toCloudWatchLogs(logGroup, flowLogRole),
trafficType: ec2.FlowLogTrafficType.ALL,
});
// Output important values
new cdk.CfnOutput(this, 'VpcId', {
value: this.vpc.vpcId,
exportName: 'ProductionVpcId',
});
new cdk.CfnOutput(this, 'PublicSubnetIds', {
value: this.vpc.publicSubnets.map(subnet => subnet.subnetId).join(','),
exportName: 'ProductionPublicSubnets',
});
new cdk.CfnOutput(this, 'PrivateSubnetIds', {
value: this.vpc.privateSubnets.map(subnet => subnet.subnetId).join(','),
exportName: 'ProductionPrivateSubnets',
});
}
}
# terraform/main.tf
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = var.aws_region
}
# Variables
variable "aws_region" {
description = "AWS region"
type = string
default = "ap-southeast-1"
}
variable "environment" {
description = "Environment name"
type = string
default = "production"
}
variable "vpc_cidr" {
description = "CIDR block for VPC"
type = string
default = "10.10.0.0/16"
}
# Data sources
data "aws_availability_zones" "available" {
state = "available"
}
# VPC
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "${var.environment}-vpc"
Environment = var.environment
}
}
# Internet Gateway
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.environment}-igw"
Environment = var.environment
}
}
# Public Subnets
resource "aws_subnet" "public" {
count = 2
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index)
availability_zone = data.aws_availability_zones.available.names[count.index]
map_public_ip_on_launch = true
tags = {
Name = "${var.environment}-public-subnet-${count.index + 1}"
Environment = var.environment
Type = "Public"
}
}
# Private Subnets
resource "aws_subnet" "private" {
count = 2
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index + 2)
availability_zone = data.aws_availability_zones.available.names[count.index]
tags = {
Name = "${var.environment}-private-subnet-${count.index + 1}"
Environment = var.environment
Type = "Private"
}
}
# Elastic IPs for NAT Gateways
resource "aws_eip" "nat" {
count = 2
domain = "vpc"
depends_on = [aws_internet_gateway.main]
tags = {
Name = "${var.environment}-nat-eip-${count.index + 1}"
Environment = var.environment
}
}
# NAT Gateways
resource "aws_nat_gateway" "main" {
count = 2
allocation_id = aws_eip.nat[count.index].id
subnet_id = aws_subnet.public[count.index].id
tags = {
Name = "${var.environment}-nat-gateway-${count.index + 1}"
Environment = var.environment
}
depends_on = [aws_internet_gateway.main]
}
# Route Tables
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = {
Name = "${var.environment}-public-rt"
Environment = var.environment
}
}
resource "aws_route_table" "private" {
count = 2
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.main[count.index].id
}
tags = {
Name = "${var.environment}-private-rt-${count.index + 1}"
Environment = var.environment
}
}
# Route Table Associations
resource "aws_route_table_association" "public" {
count = 2
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
resource "aws_route_table_association" "private" {
count = 2
subnet_id = aws_subnet.private[count.index].id
route_table_id = aws_route_table.private[count.index].id
}
# Security Groups
resource "aws_security_group" "public" {
name_prefix = "${var.environment}-public-sg"
vpc_id = aws_vpc.main.id
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"] # Restrict in production
description = "SSH access"
}
ingress {
from_port = -1
to_port = -1
protocol = "icmp"
cidr_blocks = [var.vpc_cidr]
description = "ICMP within VPC"
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.environment}-public-sg"
Environment = var.environment
}
}
resource "aws_security_group" "private" {
name_prefix = "${var.environment}-private-sg"
vpc_id = aws_vpc.main.id
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
security_groups = [aws_security_group.public.id]
description = "SSH from public subnet"
}
ingress {
from_port = -1
to_port = -1
protocol = "icmp"
cidr_blocks = [var.vpc_cidr]
description = "ICMP within VPC"
}
egress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
description = "HTTPS to AWS services and VPC endpoints"
}
egress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
description = "HTTP for package updates"
}
tags = {
Name = "${var.environment}-private-sg"
Environment = var.environment
}
}
resource "aws_security_group" "vpc_endpoints" {
name_prefix = "${var.environment}-vpc-endpoints-sg"
vpc_id = aws_vpc.main.id
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = [var.vpc_cidr]
description = "HTTPS access from VPC resources"
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "${var.environment}-vpc-endpoints-sg"
Environment = var.environment
}
}
# VPC Endpoints for Session Manager
resource "aws_vpc_endpoint" "ssm" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${var.aws_region}.ssm"
vpc_endpoint_type = "Interface"
subnet_ids = aws_subnet.private[*].id
security_group_ids = [aws_security_group.vpc_endpoints.id]
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "ssm:*"
Effect = "Allow"
Principal = "*"
Resource = "*"
}
]
})
tags = {
Name = "${var.environment}-ssm-endpoint"
Environment = var.environment
}
}
resource "aws_vpc_endpoint" "ssm_messages" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${var.aws_region}.ssmmessages"
vpc_endpoint_type = "Interface"
subnet_ids = aws_subnet.private[*].id
security_group_ids = [aws_security_group.vpc_endpoints.id]
tags = {
Name = "${var.environment}-ssm-messages-endpoint"
Environment = var.environment
}
}
resource "aws_vpc_endpoint" "ec2_messages" {
vpc_id = aws_vpc.main.id
service_name = "com.amazonaws.${var.aws_region}.ec2messages"
vpc_endpoint_type = "Interface"
subnet_ids = aws_subnet.private[*].id
security_group_ids = [aws_security_group.vpc_endpoints.id]
tags = {
Name = "${var.environment}-ec2-messages-endpoint"
Environment = var.environment
}
}
# VPC Flow Logs
resource "aws_flow_log" "vpc" {
iam_role_arn = aws_iam_role.flow_log.arn
log_destination = aws_cloudwatch_log_group.vpc_flow_log.arn
traffic_type = "ALL"
vpc_id = aws_vpc.main.id
}
resource "aws_cloudwatch_log_group" "vpc_flow_log" {
name = "/aws/vpc/flowlogs/${var.environment}"
retention_in_days = 30
}
resource "aws_iam_role" "flow_log" {
name = "${var.environment}-flow-log-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "vpc-flow-logs.amazonaws.com"
}
}
]
})
}
resource "aws_iam_role_policy" "flow_log" {
name = "${var.environment}-flow-log-policy"
role = aws_iam_role.flow_log.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"logs:DescribeLogGroups",
"logs:DescribeLogStreams"
]
Effect = "Allow"
Resource = "*"
}
]
})
}
# Outputs
output "vpc_id" {
description = "ID of the VPC"
value = aws_vpc.main.id
}
output "public_subnet_ids" {
description = "IDs of the public subnets"
value = aws_subnet.public[*].id
}
output "private_subnet_ids" {
description = "IDs of the private subnets"
value = aws_subnet.private[*].id
}
output "public_security_group_id" {
description = "ID of the public security group"
value = aws_security_group.public.id
}
output "private_security_group_id" {
description = "ID of the private security group"
value = aws_security_group.private.id
}
output "vpc_endpoints_security_group_id" {
description = "ID of the VPC endpoints security group"
value = aws_security_group.vpc_endpoints.id
}
output "nat_gateway_ids" {
description = "IDs of the NAT Gateways"
value = aws_nat_gateway.main[*].id
}
output "vpc_endpoint_ssm_id" {
description = "ID of the SSM VPC endpoint"
value = aws_vpc_endpoint.ssm.id
}
output "internet_gateway_id" {
description = "ID of the Internet Gateway"
value = aws_internet_gateway.main.id
}
# Deploy the stack
aws cloudformation create-stack \
--stack-name production-vpc \
--template-body file://vpc-template.yaml \
--parameters ParameterKey=Environment,ParameterValue=prod \
--capabilities CAPABILITY_IAM
# Monitor deployment
aws cloudformation describe-stacks \
--stack-name production-vpc \
--query 'Stacks[0].StackStatus'
# Install dependencies
npm install
# Bootstrap CDK (first time only)
cdk bootstrap
# Deploy the stack
cdk deploy ProductionVpcStack
# View outputs
cdk output ProductionVpcStack
# Initialize Terraform
terraform init
# Plan deployment
terraform plan
# Apply configuration
terraform apply
# View outputs
terraform output
🔒 Security:
💰 Cost Management:
⚡ Operational Excellence:
🔄 Reliability:
This Infrastructure as Code approach ensures your VPC deployments are consistent, repeatable, and follow AWS best practices while enabling rapid scaling and environment management.