Building a Secure Private Cloud Network on AWS with Transit Gateway and SSM
In 2025, enterprise cloud architecture demands more sophisticated networking solutions that prioritize security, scalability, and operational efficiency. This comprehensive guide explores how to build a fully private, secure cloud network on AWS using Transit Gateway and Systems Manager (SSM). We'll dive deep into creating isolated VPC architectures, implementing zero-trust networking principles, and enabling secure administrative access without exposing resources to the public internet. Whether you're building a new cloud foundation or modernizing existing infrastructure, this architecture represents the gold standard for enterprise-grade AWS networking.
🚀 Why Private Cloud Networks Matter in 2025
The evolution of cloud security has shifted from perimeter-based defenses to zero-trust architectures where private networking is fundamental. In today's threat landscape, minimizing internet exposure isn't just best practice—it's essential for compliance, data protection, and risk management. Here's why this architecture is crucial:
- Enhanced Security Posture: Eliminate public attack surfaces by keeping resources in private subnets Regulatory Compliance: Meet stringent requirements like GDPR, HIPAA, and SOC 2 with controlled data flows
🔧 Core Components: Transit Gateway & SSM Session Manager
AWS Transit Gateway acts as a regional hub that simplifies network connectivity between VPCs, on-premises networks, and other AWS services. When combined with SSM Session Manager for secure bastion-free access, you create a powerful foundation for enterprise networking.
Let's examine the key components of this architecture:
- AWS Transit Gateway: Centralized network transit hub with route tables and cross-region peering
- VPC Endpoints: Private connectivity to AWS services without internet gateways
- SSM Session Manager: Secure CLI and SSH access without bastion hosts or public IPs
- Private Subnets: Isolated network segments with no internet ingress
- Security Groups & NACLs: Micro-segmentation and network-level security controls
💻 Infrastructure as Code: Terraform Configuration
Let's start with the foundational Terraform code to provision our secure private network. This configuration sets up Transit Gateway, VPCs with only private subnets, and the necessary VPC endpoints for SSM.
# main.tf - Core Transit Gateway and VPC Configuration
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
# Transit Gateway for centralized routing
resource "aws_ec2_transit_gateway" "main" {
description = "Central Transit Gateway for private cloud"
amazon_side_asn = 64512
auto_accept_shared_attachments = "enable"
default_route_table_association = "disable"
default_route_table_propagation = "disable"
tags = {
Name = "main-tgw"
}
}
# Application VPC with only private subnets
resource "aws_vpc" "app_vpc" {
cidr_block = "10.1.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = {
Name = "app-vpc-private"
}
}
# Private subnets across multiple AZs
resource "aws_subnet" "app_private" {
count = 3
vpc_id = aws_vpc.app_vpc.id
cidr_block = cidrsubnet(aws_vpc.app_vpc.cidr_block, 8, count.index)
availability_zone = data.aws_availability_zones.available.names[count.index]
tags = {
Name = "app-private-${count.index + 1}"
}
}
# Transit Gateway VPC attachment
resource "aws_ec2_transit_gateway_vpc_attachment" "app_vpc" {
subnet_ids = aws_subnet.app_private[*].id
transit_gateway_id = aws_ec2_transit_gateway.main.id
vpc_id = aws_vpc.app_vpc.id
tags = {
Name = "app-vpc-attachment"
}
}
🛡️ Implementing VPC Endpoints for Private Service Access
VPC endpoints are crucial for maintaining private network isolation while allowing necessary AWS service connectivity. Here's how to implement the essential endpoints for SSM and other critical services.
# vpc-endpoints.tf - PrivateLink Configuration for AWS Services
# SSM VPC Endpoint for Session Manager
resource "aws_vpc_endpoint" "ssm" {
vpc_id = aws_vpc.app_vpc.id
service_name = "com.amazonaws.${var.region}.ssm"
vpc_endpoint_type = "Interface"
private_dns_enabled = true
subnet_ids = aws_subnet.app_private[*].id
security_group_ids = [
aws_security_group.vpc_endpoints.id
]
tags = {
Name = "ssm-endpoint"
}
}
# Additional SSM endpoints for full functionality
resource "aws_vpc_endpoint" "ssm_messages" {
vpc_id = aws_vpc.app_vpc.id
service_name = "com.amazonaws.${var.region}.ssmmessages"
vpc_endpoint_type = "Interface"
private_dns_enabled = true
subnet_ids = aws_subnet.app_private[*].id
security_group_ids = [
aws_security_group.vpc_endpoints.id
]
tags = {
Name = "ssm-messages-endpoint"
}
}
resource "aws_vpc_endpoint" "ec2_messages" {
vpc_id = aws_vpc.app_vpc.id
service_name = "com.amazonaws.${var.region}.ec2messages"
vpc_endpoint_type = "Interface"
private_dns_enabled = true
subnet_ids = aws_subnet.app_private[*].id
security_group_ids = [
aws_security_group.vpc_endpoints.id
]
tags = {
Name = "ec2-messages-endpoint"
}
}
# S3 Gateway Endpoint for package downloads and logs
resource "aws_vpc_endpoint" "s3" {
vpc_id = aws_vpc.app_vpc.id
service_name = "com.amazonaws.${var.region}.s3"
vpc_endpoint_type = "Gateway"
route_table_ids = aws_route_table.private[*].id
tags = {
Name = "s3-gateway-endpoint"
}
}
# ECR endpoints for Docker image pulls
resource "aws_vpc_endpoint" "ecr_api" {
vpc_id = aws_vpc.app_vpc.id
service_name = "com.amazonaws.${var.region}.ecr.api"
vpc_endpoint_type = "Interface"
private_dns_enabled = true
subnet_ids = aws_subnet.app_private[*].id
security_group_ids = [
aws_security_group.vpc_endpoints.id
]
tags = {
Name = "ecr-api-endpoint"
}
}
🔐 Advanced Security Groups for Micro-Segmentation
Security groups provide essential micro-segmentation within your private network. Here's how to implement zero-trust security group rules that enforce least privilege access.
# security-groups.tf - Zero-Trust Security Configuration
# VPC Endpoints Security Group
resource "aws_security_group" "vpc_endpoints" {
name_prefix = "vpc-endpoints-"
description = "Security group for VPC endpoints"
vpc_id = aws_vpc.app_vpc.id
ingress {
description = "HTTPS from private subnets"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = [aws_vpc.app_vpc.cidr_block]
}
ingress {
description = "SSM from private subnets"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = [aws_vpc.app_vpc.cidr_block]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "vpc-endpoints-sg"
}
}
# Application instances security group
resource "aws_security_group" "app_instances" {
name_prefix = "app-instances-"
description = "Security group for application instances"
vpc_id = aws_vpc.app_vpc.id
ingress {
description = "SSH via Session Manager"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = [aws_vpc.app_vpc.cidr_block]
}
ingress {
description = "Application traffic from internal"
from_port = 8080
to_port = 8080
protocol = "tcp"
cidr_blocks = [aws_vpc.app_vpc.cidr_block]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "app-instances-sg"
}
}
# Database security group with strict rules
resource "aws_security_group" "database" {
name_prefix = "database-"
description = "Security group for database instances"
vpc_id = aws_vpc.app_vpc.id
ingress {
description = "PostgreSQL from app instances"
from_port = 5432
to_port = 5432
protocol = "tcp"
security_groups = [aws_security_group.app_instances.id]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "database-sg"
}
}
🚀 Configuring SSM Session Manager for Secure Access
SSM Session Manager eliminates the need for bastion hosts and provides secure, auditable access to EC2 instances. Here's the complete IAM and SSM configuration.
# iam-ssm.tf - IAM Roles and SSM Configuration
# SSM Instance Role
resource "aws_iam_role" "ssm_instance_role" {
name_prefix = "SSMInstanceRole-"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ec2.amazonaws.com"
}
}
]
})
tags = {
Name = "ssm-instance-role"
}
}
# AmazonSSMManagedInstanceCore policy attachment
resource "aws_iam_role_policy_attachment" "ssm_core" {
role = aws_iam_role.ssm_instance_role.name
policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}
# Custom SSM policy for additional permissions
resource "aws_iam_role_policy" "ssm_custom" {
name_prefix = "SSMCustomPolicy-"
role = aws_iam_role.ssm_instance_role.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"s3:GetObject",
"s3:PutObject",
"s3:ListBucket"
]
Resource = [
"arn:aws:s3:::my-ssm-logs-bucket/*",
"arn:aws:s3:::my-ssm-logs-bucket"
]
},
{
Effect = "Allow"
Action = [
"logs:CreateLogStream",
"logs:PutLogEvents",
"logs:DescribeLogGroups",
"logs:DescribeLogStreams"
]
Resource = "*"
}
]
})
}
# Instance Profile for EC2 instances
resource "aws_iam_instance_profile" "ssm_instance" {
name_prefix = "SSMInstanceProfile-"
role = aws_iam_role.ssm_instance_role.name
}
# SSM Document for session preferences
resource "aws_ssm_document" "session_preferences" {
name = "SSM-SessionManagerRunShell"
document_type = "Session"
content = jsonencode({
schemaVersion = "1.0"
description = "Document to hold regional session settings"
sessionType = "Standard_Stream"
inputs = {
s3BucketName = "my-ssm-logs-bucket"
s3KeyPrefix = "ssm-sessions"
s3EncryptionEnabled = true
cloudWatchLogGroupName = "/aws/ssm/sessions"
cloudWatchEncryptionEnabled = true
cloudWatchStreamingEnabled = true
idleSessionTimeout = "20"
maxSessionDuration = "60"
shellProfile = {
linux = "echo 'Welcome to Secure Session Manager'"
}
}
})
tags = {
Name = "session-preferences"
}
}
🔄 Advanced Transit Gateway Routing
Transit Gateway route tables enable sophisticated routing patterns for multi-VPC architectures. Here's how to implement advanced routing with segregation and security controls.
# tgw-routing.tf - Advanced Transit Gateway Configuration
# Segregated route tables for different environments
resource "aws_ec2_transit_gateway_route_table" "production" {
transit_gateway_id = aws_ec2_transit_gateway.main.id
tags = {
Name = "production-rt"
}
}
resource "aws_ec2_transit_gateway_route_table" "development" {
transit_gateway_id = aws_ec2_transit_gateway.main.id
tags = {
Name = "development-rt"
}
}
resource "aws_ec2_transit_gateway_route_table" "shared_services" {
transit_gway_id = aws_ec2_transit_gateway.main.id
tags = {
Name = "shared-services-rt"
}
}
# Route table associations
resource "aws_ec2_transit_gateway_route_table_association" "app_vpc_prod" {
transit_gateway_attachment_id = aws_ec2_transit_gateway_vpc_attachment.app_vpc.id
transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.production.id
}
# Static routes for specific traffic patterns
resource "aws_ec2_transit_gateway_route" "to_inspection_vpc" {
destination_cidr_block = "0.0.0.0/0"
transit_gateway_attachment_id = aws_ec2_transit_gateway_vpc_attachment.inspection.id
transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.production.id
}
# Route table propagations
resource "aws_ec2_transit_gateway_route_table_propagation" "prod_to_shared" {
transit_gateway_attachment_id = aws_ec2_transit_gateway_vpc_attachment.shared_services.id
transit_gateway_route_table_id = aws_ec2_transit_gateway_route_table.production.id
}
📊 Monitoring and Logging Configuration
Comprehensive monitoring is essential for maintaining security and performance in private cloud networks. Implement these CloudWatch and VPC Flow Log configurations.
# monitoring.tf - Comprehensive Observability Setup
# VPC Flow Logs for network traffic monitoring
resource "aws_cloudwatch_log_group" "vpc_flow_logs" {
name = "/aws/vpc/flow-logs"
retention_in_days = 365
tags = {
Name = "vpc-flow-logs"
}
}
resource "aws_iam_role" "vpc_flow_log_role" {
name_prefix = "VPCFlowLogRole-"
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" "vpc_flow_log_policy" {
name_prefix = "VPCFlowLogPolicy-"
role = aws_iam_role.vpc_flow_log_role.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents",
"logs:DescribeLogGroups",
"logs:DescribeLogStreams"
]
Resource = "*"
}
]
})
}
resource "aws_flow_log" "app_vpc" {
iam_role_arn = aws_iam_role.vpc_flow_log_role.arn
log_destination = aws_cloudwatch_log_group.vpc_flow_logs.arn
traffic_type = "ALL"
vpc_id = aws_vpc.app_vpc.id
tags = {
Name = "app-vpc-flow-logs"
}
}
# Transit Gateway Flow Logs
resource "aws_ec2_transit_gateway_flow_log" "main" {
transit_gateway_id = aws_ec2_transit_gateway.main.id
transit_gateway_attachment_id = aws_ec2_transit_gateway_vpc_attachment.app_vpc.id
log_destination = aws_cloudwatch_log_group.vpc_flow_logs.arn
iam_role_arn = aws_iam_role.vpc_flow_log_role.arn
traffic_type = "ALL"
tags = {
Name = "tgw-flow-logs"
}
}
# CloudWatch Alarms for security monitoring
resource "aws_cloudwatch_log_metric_filter" "unauthorized_access" {
name = "UnauthorizedAccessAttempts"
pattern = "[version, account, eni, source, destination, srcport, destport, protocol, packets, bytes, windowstart, windowend, action = \"REJECT\"]"
log_group_name = aws_cloudwatch_log_group.vpc_flow_logs.name
metric_transformation {
name = "UnauthorizedAccessCount"
namespace = "VPC/Security"
value = "1"
}
}
resource "aws_cloudwatch_metric_alarm" "high_unauthorized_access" {
alarm_name = "HighUnauthorizedAccessAttempts"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = "2"
metric_name = "UnauthorizedAccessCount"
namespace = "VPC/Security"
period = "300"
statistic = "Sum"
threshold = "10"
alarm_description = "This metric monitors for high unauthorized access attempts"
alarm_actions = [aws_sns_topic.security_alerts.arn]
tags = {
Name = "unauthorized-access-alarm"
}
}
🔒 Advanced Security: Network Firewall & Security Hub
For enterprise-grade security, integrate AWS Network Firewall and Security Hub to provide comprehensive threat protection and compliance monitoring.
# advanced-security.tf - Enterprise Security Controls
# AWS Network Firewall for deep packet inspection
resource "aws_networkfirewall_firewall" "inspection" {
name = "inspection-firewall"
firewall_policy_arn = aws_networkfirewall_firewall_policy.inspection.arn
vpc_id = aws_vpc.inspection.id
subnet_mapping {
subnet_id = aws_subnet.firewall.id
}
tags = {
Name = "inspection-firewall"
}
}
resource "aws_networkfirewall_firewall_policy" "inspection" {
name = "inspection-policy"
firewall_policy {
stateless_default_actions = ["aws:forward_to_sfe"]
stateless_fragment_default_actions = ["aws:forward_to_sfe"]
stateful_rule_group_reference {
resource_arn = aws_networkfirewall_rule_group.threat_prevention.arn
}
stateful_engine_options {
rule_order = "STRICT_ORDER"
}
}
tags = {
Name = "inspection-firewall-policy"
}
}
# Security Hub integration for compliance monitoring
resource "aws_securityhub_account" "main" {}
resource "aws_securityhub_standards_subscription" "cis" {
depends_on = [aws_securityhub_account.main]
standards_arn = "arn:aws:securityhub:::ruleset/cis-aws-foundations-benchmark/v/1.2.0"
}
resource "aws_securityhub_standards_subscription" "pci" {
depends_on = [aws_securityhub_account.main]
standards_arn = "arn:aws:securityhub:::ruleset/pci-dss/v/3.2.1"
}
# GuardDuty for threat detection
resource "aws_guardduty_detector" "main" {
enable = true
datasources {
s3_logs {
enable = true
}
kubernetes {
audit_logs {
enable = false
}
}
malware_protection {
scan_ec2_instance_with_findings {
ebs_volumes {
enable = true
}
}
}
}
}
⚡ Key Takeaways
- Zero-Trust Architecture: Implement private subnets exclusively and use VPC endpoints for AWS service access
- Centralized Networking: Leverage Transit Gateway for simplified multi-VPC management and routing
- Secure Access Patterns: Replace bastion hosts with SSM Session Manager for improved security and auditability
- Comprehensive Monitoring: Implement VPC Flow Logs, Transit Gateway Flow Logs, and Security Hub for full visibility
- Infrastructure as Code: Use Terraform to ensure consistent, repeatable deployments across environments
- Advanced Security: Integrate Network Firewall and GuardDuty for enterprise-grade threat protection
- Cost Optimization: Reduce data transfer costs and eliminate NAT gateway expenses through proper architecture
❓ Frequently Asked Questions
- How does this architecture compare to traditional VPN/bastion host setups?
- This architecture eliminates public attack surfaces entirely. Instead of VPNs and bastion hosts with public IPs, we use AWS PrivateLink and SSM Session Manager, which provide more secure, auditable access without internet exposure. The attack surface is significantly reduced while maintaining full functionality.
- What are the cost implications of using Transit Gateway and multiple VPC endpoints?
- While there are hourly costs for Transit Gateway and VPC endpoints, these are often offset by eliminating NAT gateway costs and reducing data transfer charges. The architecture typically results in better cost predictability and can be more economical for enterprise-scale deployments compared to maintaining multiple NAT gateways and VPN connections.
- Can I use this architecture for HIPAA or PCI DSS compliant workloads?
- Yes, this architecture is well-suited for compliant workloads. The private network design, comprehensive logging, and advanced security controls align with HIPAA and PCI DSS requirements. However, you should conduct proper validation and implement additional controls specific to your compliance framework.
- How do I handle internet access for instances that need to download updates?
- For controlled internet access, implement a dedicated egress VPC with NAT gateways or AWS Network Firewall. Route specific traffic through this inspection VPC rather than providing direct internet access. Alternatively, use VPC endpoints for AWS services and maintain internal repositories for software updates.
- What's the performance impact of using VPC endpoints versus public service endpoints?
- VPC endpoints typically provide equal or better performance since traffic stays within the AWS network. They eliminate internet latency and provide more consistent throughput. For most workloads, you'll see improved performance and reliability compared to public endpoints.
- How do I monitor and troubleshoot network issues in this private architecture?
- Implement VPC Flow Logs, Transit Gateway Flow Logs, and CloudWatch metrics extensively. Use SSM Session Manager for instance access and AWS X-Ray for application-level tracing. Centralize logs in CloudWatch Logs or S3 for analysis and set up alerts for unusual patterns or connectivity issues.
💬 Have you implemented a similar private cloud architecture? Share your experiences, challenges, or questions in the comments below! If you found this guide helpful, please share it with your team or on social media to help others build more secure AWS environments.
About LK-TECH Academy — Practical tutorials & explainers on software engineering, AI, and infrastructure. Follow for concise, hands-on guides.

No comments:
Post a Comment