Bastion Hosts
Architecture
Deploy a bastion host (jump server) for secure access to private resources:
- Public subnet placement for internet accessibility
- Minimal security group allowing only SSH from approved IPs
- Session logging via CloudWatch or S3
- Auto-recovery for high availability
- Systems Manager as a more secure alternative
┌─────────────────────────────────────────────────────────────┐
│ Admin Workstation │
│ (Approved IP Address) │
└─────────────────────────────────────────────────────────────┘
│
│ SSH (port 22)
▼
┌─────────────────────────────────────────────────────────────┐
│ Public Subnet │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Bastion Host │ │
│ │ (EC2 Instance) │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
│ SSH (port 22)
▼
┌─────────────────────────────────────────────────────────────┐
│ Private Subnet │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ App Server │ │ Database │ │
│ │ (Private) │ │ (Private) │ │
│ └─────────────────┘ └─────────────────┘ │
└─────────────────────────────────────────────────────────────┘
When to Use
Bastion hosts are appropriate when:
- Accessing private EC2 instances that have no public IP
- Maintaining compliance requiring controlled access points
- Auditing SSH access through a single entry point
- Using legacy applications that require SSH access
- Temporary access is needed for troubleshooting
Example Configuration
module "bastion" {
source = "registry.patterneddesigns.ca/patterneddesigns/ec2-instance/aws"
version = "1.5.0"
instance_name = "bastion"
instance_type = "t3.micro"
ami_id = data.aws_ami.amazon_linux.id
subnet_id = module.vpc.public_subnets[0]
security_group_ids = [aws_security_group.bastion.id]
key_name = aws_key_pair.bastion.key_name
assign_public_ip = true
enable_auto_recovery = true
monitoring = true
iam_instance_profile = aws_iam_instance_profile.bastion.name
user_data = <<-EOF
#!/bin/bash
yum update -y
# Enable SSH session logging
echo 'ForceCommand /usr/bin/script -q -f /var/log/ssh-sessions/$USER-$(date +%Y%m%d%H%M%S).log' >> /etc/ssh/sshd_config
mkdir -p /var/log/ssh-sessions
chmod 733 /var/log/ssh-sessions
systemctl restart sshd
EOF
tags = {
Environment = "production"
Role = "bastion"
}
}
resource "aws_security_group" "bastion" {
name = "bastion-sg"
description = "Security group for bastion host"
vpc_id = module.vpc.vpc_id
ingress {
description = "SSH from approved IPs"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = var.allowed_ssh_cidrs # e.g., ["203.0.113.0/24"]
}
egress {
description = "SSH to private instances"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = [module.vpc.vpc_cidr]
}
tags = {
Name = "bastion-sg"
}
}
# Allow SSH from bastion to private instances
resource "aws_security_group" "private_ssh" {
name = "private-ssh-sg"
description = "Allow SSH from bastion"
vpc_id = module.vpc.vpc_id
ingress {
description = "SSH from bastion"
from_port = 22
to_port = 22
protocol = "tcp"
security_groups = [aws_security_group.bastion.id]
}
}
Considerations
| Aspect | Consideration |
|---|---|
| Access Control | Use IP whitelisting and strong key management |
| Logging | Enable SSH session logging and send to CloudWatch |
| Hardening | Disable root login, use fail2ban, keep updated |
| High Availability | Use auto-recovery or deploy in multiple AZs |
| Key Management | Rotate SSH keys regularly, consider EC2 Instance Connect |
Alternatives
Consider more modern alternatives:
- AWS Systems Manager Session Manager - No SSH keys, IAM-based access, full audit logging
- EC2 Instance Connect - Temporary SSH keys via IAM
- AWS Client VPN - Direct VPN access to VPC
- PrivateLink - Service-specific private connectivity