Terraforming Bastion Architecture: Secure AWS Access for Private Subnets
Introduction
In cloud-native environments, private subnets are essential for securely hosting backend services like databases and internal APIs. However, connecting to instances in these private subnets requires a secure and controlled access method. This is where a bastion host (a jump server) becomes essential. In this guide, we'll explore how to deploy a bastion host architecture using Terraform on AWS to enable secure SSH access to private EC2 instances, without compromising security.
What is a Bastion Host?
A bastion host is a secure EC2 instance configured as the only entry point to your private subnets. It resides in a public subnet and provides SSH access to resources in private subnets.
Key Benefits:
Centralized access control
Improved audit logging and compliance
Minimized attack surface
Enhanced security when combined with SSH key rotation or Session Manager
Terraform Architecture Overview
Using Terraform, we’ll define the following key components:
VPC and Subnets
One public subnet (for bastion host)
One or more private subnets (for application/data servers)
Security Groups
Bastion host SG allows inbound SSH access (restricted to your IP)
Private instances SG allows SSH only from the bastion's SG
EC2 Instances
Bastion EC2 instance with a public IP
Private EC2 instances without public IPs
IAM Roles (Optional)
For integrating AWS Systems Manager (SSM) as an alternative access method
Key Pair Management
Define SSH key pair for secure instance access.
Terraform Configuration Snippets
Here’s a high-level look at the main components in Terraform:
1. VPC and Subnet
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
}
resource "aws_subnet" "public" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.1.0/24"
map_public_ip_on_launch = true
}
resource "aws_subnet" "private" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.2.0/24"
}
2. Security Groups
resource "aws_security_group" "bastion_sg" {
name = "bastion-sg"
description = "Allow SSH from known IP"
vpc_id = aws_vpc.main.id
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["<Your-IP>/32"]
}
}
resource "aws_security_group" "private_sg" {
name = "private-sg"
description = "Allow SSH from Bastion"
vpc_id = aws_vpc.main.id
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
security_groups = [aws_security_group.bastion_sg.id]
}
}
3. Bastion EC2 Instance
resource "aws_instance" "bastion" {
ami = "<Amazon-Linux-AMI>"
instance_type = "t3.micro"
subnet_id = aws_subnet.public.id
key_name = "your-key"
security_groups = [aws_security_group.bastion_sg.name]
associate_public_ip_address = true
}
Best Practices for Bastion Hosts
Use SSH agent forwarding or AWS Session Manager to avoid direct key storage.
Enable CloudTrail and VPC Flow Logs for auditing.
Auto-terminate or schedule shutdown for idle bastions
Limit user permissions via IAM.
Consider placing bastions behind a NAT Gateway if outbound internet access is required for private instances.
Advanced: Replacing SSH with AWS SSM
To avoid managing SSH keys altogether, integrate AWS Systems Manager (SSM) for secure and auditable access to private instances. Attach the appropriate IAM role and SSM agent, and connect using:
aws ssm start-session --target <instance-id>
Conclusion
Terraforming your bastion architecture empowers you with automated, secure, and repeatable deployment of access patterns for AWS private subnets. It ensures your private resources remain isolated while being reachable under controlled, auditable conditions.
By combining Infrastructure as Code (IaC) with security best practices, you create a scalable cloud environment that aligns with DevSecOps principles.
Comments
Post a Comment