cfn-forge
Version:
CloudFormation deployment automation tool with git-based workflows
592 lines (513 loc) • 17.9 kB
YAML
name: vpc-network
description: Complete VPC setup with public/private subnets, NAT Gateway, and security groups
version: 1.0.0
author: CFN-Forge
cfnForgeVersion: 1.0.0
variables:
projectName:
prompt: "Project name"
default: "my-vpc-network"
description:
prompt: "Project description"
default: "VPC network infrastructure for AWS applications"
vpcCidr:
prompt: "VPC CIDR block"
default: "10.0.0.0/16"
enableNatGateway:
prompt: "Enable NAT Gateway for private subnets?"
default: "true"
type: "boolean"
multiAz:
prompt: "Deploy across multiple Availability Zones?"
default: "true"
type: "boolean"
files:
include:
- "**/*"
exclude:
- "template.yaml"
- ".git/**"
structure:
cloudformation.yaml: |
AWSTemplateFormatVersion: '2010-09-09'
Description: {{description}}
Parameters:
Environment:
Type: String
Default: dev
AllowedValues: [dev, staging, prod]
Description: Environment name
Conditions:
EnableNatGateway: !Equals ["{{enableNatGateway}}", "true"]
MultiAZ: !Equals ["{{multiAz}}", "true"]
CreateSecondAZ: !And [!Condition MultiAZ, !Not [!Equals [!Select [1, !GetAZs ""], ""]]]
Resources:
# VPC
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: {{vpcCidr}}
EnableDnsHostnames: true
EnableDnsSupport: true
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-vpc"
- Key: Environment
Value: !Ref Environment
# Internet Gateway
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-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 [{{vpcCidr}}, 6, 8]]
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-public-subnet-1"
- Key: Type
Value: Public
PublicSubnet2:
Type: AWS::EC2::Subnet
Condition: CreateSecondAZ
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select [1, !GetAZs ""]
CidrBlock: !Select [1, !Cidr [{{vpcCidr}}, 6, 8]]
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-public-subnet-2"
- Key: Type
Value: Public
# Private Subnets
PrivateSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select [0, !GetAZs ""]
CidrBlock: !Select [2, !Cidr [{{vpcCidr}}, 6, 8]]
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-private-subnet-1"
- Key: Type
Value: Private
PrivateSubnet2:
Type: AWS::EC2::Subnet
Condition: CreateSecondAZ
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select [1, !GetAZs ""]
CidrBlock: !Select [3, !Cidr [{{vpcCidr}}, 6, 8]]
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-private-subnet-2"
- Key: Type
Value: Private
# Database Subnets
DatabaseSubnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select [0, !GetAZs ""]
CidrBlock: !Select [4, !Cidr [{{vpcCidr}}, 6, 8]]
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-database-subnet-1"
- Key: Type
Value: Database
DatabaseSubnet2:
Type: AWS::EC2::Subnet
Condition: CreateSecondAZ
Properties:
VpcId: !Ref VPC
AvailabilityZone: !Select [1, !GetAZs ""]
CidrBlock: !Select [5, !Cidr [{{vpcCidr}}, 6, 8]]
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-database-subnet-2"
- Key: Type
Value: Database
# NAT Gateway
NatGateway1EIP:
Type: AWS::EC2::EIP
Condition: EnableNatGateway
DependsOn: InternetGatewayAttachment
Properties:
Domain: vpc
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-nat-eip-1"
NatGateway2EIP:
Type: AWS::EC2::EIP
Condition: !And [EnableNatGateway, CreateSecondAZ]
DependsOn: InternetGatewayAttachment
Properties:
Domain: vpc
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-nat-eip-2"
NatGateway1:
Type: AWS::EC2::NatGateway
Condition: EnableNatGateway
Properties:
AllocationId: !GetAtt NatGateway1EIP.AllocationId
SubnetId: !Ref PublicSubnet1
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-nat-1"
NatGateway2:
Type: AWS::EC2::NatGateway
Condition: !And [EnableNatGateway, CreateSecondAZ]
Properties:
AllocationId: !GetAtt NatGateway2EIP.AllocationId
SubnetId: !Ref PublicSubnet2
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-nat-2"
# Route Tables
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-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
Condition: CreateSecondAZ
Properties:
RouteTableId: !Ref PublicRouteTable
SubnetId: !Ref PublicSubnet2
PrivateRouteTable1:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-private-routes-1"
DefaultPrivateRoute1:
Type: AWS::EC2::Route
Condition: EnableNatGateway
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
Condition: CreateSecondAZ
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-private-routes-2"
DefaultPrivateRoute2:
Type: AWS::EC2::Route
Condition: !And [EnableNatGateway, CreateSecondAZ]
Properties:
RouteTableId: !Ref PrivateRouteTable2
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NatGateway2
PrivateSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Condition: CreateSecondAZ
Properties:
RouteTableId: !Ref PrivateRouteTable2
SubnetId: !Ref PrivateSubnet2
# Database Subnet Group
DatabaseSubnetGroup:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupDescription: Subnet group for RDS database
SubnetIds:
- !Ref DatabaseSubnet1
- !If [CreateSecondAZ, !Ref DatabaseSubnet2, !Ref DatabaseSubnet1]
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-db-subnet-group"
# Security Groups
WebSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub "${AWS::StackName}-web-sg"
GroupDescription: Security group for web servers
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
Description: HTTP
- IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: 0.0.0.0/0
Description: HTTPS
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-web-sg"
ApplicationSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub "${AWS::StackName}-app-sg"
GroupDescription: Security group for application servers
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 8080
ToPort: 8080
SourceSecurityGroupId: !Ref WebSecurityGroup
Description: Application port from web tier
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-app-sg"
DatabaseSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub "${AWS::StackName}-db-sg"
GroupDescription: Security group for database servers
VpcId: !Ref VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 3306
ToPort: 3306
SourceSecurityGroupId: !Ref ApplicationSecurityGroup
Description: MySQL/Aurora from application tier
- IpProtocol: tcp
FromPort: 5432
ToPort: 5432
SourceSecurityGroupId: !Ref ApplicationSecurityGroup
Description: PostgreSQL from application tier
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-db-sg"
Outputs:
VPC:
Description: VPC ID
Value: !Ref VPC
Export:
Name: !Sub "${AWS::StackName}-VPC"
PublicSubnets:
Description: Public subnet IDs
Value: !If
- CreateSecondAZ
- !Sub "${PublicSubnet1},${PublicSubnet2}"
- !Ref PublicSubnet1
Export:
Name: !Sub "${AWS::StackName}-PublicSubnets"
PrivateSubnets:
Description: Private subnet IDs
Value: !If
- CreateSecondAZ
- !Sub "${PrivateSubnet1},${PrivateSubnet2}"
- !Ref PrivateSubnet1
Export:
Name: !Sub "${AWS::StackName}-PrivateSubnets"
DatabaseSubnets:
Description: Database subnet IDs
Value: !If
- CreateSecondAZ
- !Sub "${DatabaseSubnet1},${DatabaseSubnet2}"
- !Ref DatabaseSubnet1
Export:
Name: !Sub "${AWS::StackName}-DatabaseSubnets"
DatabaseSubnetGroup:
Description: Database subnet group name
Value: !Ref DatabaseSubnetGroup
Export:
Name: !Sub "${AWS::StackName}-DatabaseSubnetGroup"
WebSecurityGroup:
Description: Web security group ID
Value: !Ref WebSecurityGroup
Export:
Name: !Sub "${AWS::StackName}-WebSecurityGroup"
ApplicationSecurityGroup:
Description: Application security group ID
Value: !Ref ApplicationSecurityGroup
Export:
Name: !Sub "${AWS::StackName}-ApplicationSecurityGroup"
DatabaseSecurityGroup:
Description: Database security group ID
Value: !Ref DatabaseSecurityGroup
Export:
Name: !Sub "${AWS::StackName}-DatabaseSecurityGroup"
.cfn-forge.yaml: |
project:
name: {{projectName}}
description: {{description}}
type: vpc-network
# Global AWS settings (optional - can be overridden per environment)
aws:
profile: default # Default AWS profile for all environments
region: us-east-1 # Default region
environments:
dev:
stackName: "{{projectName}}-dev"
template: cloudformation.yaml
region: us-east-1
# aws:
# profile: dev-account # Override profile for dev environment
# region: us-east-1 # Override region for dev environment
parameters:
Environment: dev
staging:
stackName: "{{projectName}}-staging"
template: cloudformation.yaml
region: us-east-1
# aws:
# profile: staging-account # Override profile for staging
# region: us-west-2 # Override region for staging
parameters:
Environment: staging
prod:
stackName: "{{projectName}}-prod"
template: cloudformation.yaml
region: us-east-1
# aws:
# profile: prod-account # Override profile for production
# region: us-west-2 # Override region for production
# accountId: "123456789012" # Verify account ID for safety
parameters:
Environment: prod
deployment:
packagingBucket: "{{projectName}}-deployment-artifacts"
createBucket: true
git:
watchBranches: ["main", "develop"]
deployOnPush: true
README.md: |
# {{projectName}}
{{description}}
## Architecture
This template creates a complete VPC infrastructure with:
- **VPC**: {{vpcCidr}} CIDR block
- **Public Subnets**: For load balancers, NAT gateways
- **Private Subnets**: For application servers, Lambda functions
- **Database Subnets**: Isolated subnets for RDS databases
- **Internet Gateway**: Internet access for public subnets
- **NAT Gateway**: Outbound internet access for private subnets {{#if enableNatGateway}}✅{{else}}❌{{/if}}
- **Multi-AZ**: High availability across multiple zones {{#if multiAz}}✅{{else}}❌{{/if}}
## Quick Start
1. Deploy the VPC infrastructure:
```bash
cfn-forge deploy
```
2. Verify the deployment:
```bash
cfn-forge validate
```
## Network Layout
```
{{projectName}} VPC ({{vpcCidr}})
├── Public Subnets (Internet Gateway)
│ ├── Public Subnet 1 (AZ-a)
│ └── Public Subnet 2 (AZ-b) {{#if multiAz}}✅{{else}}❌{{/if}}
├── Private Subnets (NAT Gateway)
│ ├── Private Subnet 1 (AZ-a)
│ └── Private Subnet 2 (AZ-b) {{#if multiAz}}✅{{else}}❌{{/if}}
└── Database Subnets (No Internet)
├── Database Subnet 1 (AZ-a)
└── Database Subnet 2 (AZ-b) {{#if multiAz}}✅{{else}}❌{{/if}}
```
## Security Groups
### Web Security Group
- **HTTP (80)**: Open to internet (0.0.0.0/0)
- **HTTPS (443)**: Open to internet (0.0.0.0/0)
### Application Security Group
- **Port 8080**: Access from Web Security Group only
### Database Security Group
- **MySQL (3306)**: Access from Application Security Group only
- **PostgreSQL (5432)**: Access from Application Security Group only
## Deployment
### Environments
- **dev**: Development environment
- **staging**: Staging environment
- **prod**: Production environment
### Commands
- `cfn-forge deploy` - Deploy VPC infrastructure
- `cfn-forge deploy --env prod` - Deploy to production
- `cfn-forge validate` - Validate CloudFormation template
## Usage Examples
### Launch EC2 Instances
```bash
# Web servers in public subnets
aws ec2 run-instances \
--subnet-id $(cfn-forge output PublicSubnets | cut -d',' -f1) \
--security-group-ids $(cfn-forge output WebSecurityGroup)
# App servers in private subnets
aws ec2 run-instances \
--subnet-id $(cfn-forge output PrivateSubnets | cut -d',' -f1) \
--security-group-ids $(cfn-forge output ApplicationSecurityGroup)
```
### Create RDS Database
```bash
aws rds create-db-instance \
--db-subnet-group-name $(cfn-forge output DatabaseSubnetGroup) \
--vpc-security-group-ids $(cfn-forge output DatabaseSecurityGroup) \
--db-instance-identifier myapp-db \
--engine mysql
```
### Deploy Lambda Functions
```yaml
# In your Lambda CloudFormation template
VpcConfig:
SubnetIds:
- !ImportValue {{projectName}}-dev-PrivateSubnets
SecurityGroupIds:
- !ImportValue {{projectName}}-dev-ApplicationSecurityGroup
```
## Costs
### Fixed Costs (24/7)
{{#if enableNatGateway}}
- **NAT Gateway**: ~$32-45/month per gateway
{{#if multiAz}}
- **Total NAT Gateways**: 2 (~$64-90/month)
{{else}}
- **Total NAT Gateways**: 1 (~$32-45/month)
{{/if}}
{{else}}
- **NAT Gateway**: Disabled (no fixed costs)
{{/if}}
### Variable Costs
- **Data Transfer**: $0.045/GB through NAT Gateway
- **VPC, Subnets, Route Tables**: Free
- **Security Groups, NACLs**: Free
## Best Practices
- ✅ Separate tiers into different subnets
- ✅ Use security groups for fine-grained access control
- ✅ Deploy across multiple AZs for high availability
- ✅ Use private subnets for application/database servers
- ✅ Monitor NAT Gateway costs and data transfer
hooks:
postInstall:
- type: message
content: "VPC network template created! This provides the foundation for your AWS infrastructure."