Terraform AWS Bastion Host Module

Overview

This Terraform module creates an EC2 bastion host (jump server) for secure SSH access to private resources in your AWS VPC. The bastion host can be enabled or disabled as needed and includes features like Elastic IP, custom security groups, and configurable instance specifications.

Features

  • Conditional creation - Enable/disable bastion host via variable
  • Elastic IP assignment for consistent public IP
  • Dedicated network interface with security group
  • Automatic AWS optimized AMI selection
  • Custom user data support
  • EBS optimization option
  • Configurable CIDR-based access control
  • IMDSv2 enforcement for enhanced security
  • Flexible tagging support

Resources Created

  • EC2 Instance (Amazon Linux optimized AMI)
  • Elastic IP (EIP)
  • Network Interface
  • Security Group with SSH ingress rules
  • IAM instance profile (optional, via user configuration)

Usage

Basic Example

module "bastion" {
  source = "git@github.com:webuildyourcloud/terraform-aws-bastion.git?ref=tags/0.0.7"

  # Enable bastion creation
  enable_bastion = true

  # Naming
  environment = "prod"
  project     = "myapp"

  # Network Configuration
  aws_region = "us-east-1"
  vpc_id     = "vpc-12345678"
  subnet_id  = "subnet-12345678"

  # Instance Configuration
  instance_type = "t3.micro"
  key_name      = "my-ssh-key"

  # Security
  admin_cidr = "203.0.113.0/24"

  # Tags
  tags = {
    ManagedBy   = "terraform"
    Environment = "production"
  }
}

Advanced Example with Custom AMI

module "bastion" {
  source = "git@github.com:webuildyourcloud/terraform-aws-bastion.git?ref=tags/0.0.7"

  enable_bastion = true
  environment    = "dev"
  project        = "research"
  aws_region     = "eu-west-1"
  vpc_id         = module.vpc.vpc_id
  subnet_id      = module.vpc.public_subnets[0]

  # Custom instance configuration
  instance_type  = "t3.small"
  ebs_optimized  = true
  key_name       = "dev-bastion-key"

  # Custom AMI per region
  amazon_optimized_amis = {
    "eu-west-1" = "ami-0123456789abcdef0"
  }

  # Restrict SSH access
  admin_cidr = "10.0.0.0/8"

  # Custom user data
  user_data = file("${path.module}/bastion-userdata.sh")

  tags = {
    Backup      = "true"
    CostCenter  = "engineering"
  }
}

Variables

Name Description Type Default Required
aws_region The AWS region string n/a yes
project Name of the project string n/a yes
environment Logical name of the environment string n/a yes
key_name SSH key name for the environment string n/a yes
vpc_id The VPC ID to launch the instance in string n/a yes
subnet_id Subnet in which the bastion needs to be deployed string n/a yes
enable_bastion Enable bastion host creation bool false no
instance_type EC2 instance type string "t2.micro" no
ebs_optimized Enable EBS optimization bool true no
admin_cidr CIDR pattern to access the bastion host string "0.0.0.0/0" no
amazon_optimized_amis Map from region to AMI (uses latest Amazon Linux if not specified) map(string) {} no
user_data User data for bastion EC2 instance string "" no
tags Map of tags to apply on the resources map(string) {} no

Outputs

Name Description
instance_id ID of the created bastion instance
public_ip Public IP address of the bastion instance

Requirements

Name Version
terraform >= 0.13
aws Latest
template Latest

Security Features

IMDSv2 Enforcement

The bastion host is configured with Instance Metadata Service Version 2 (IMDSv2) enforcement:

metadata_options {
  http_endpoint = "enabled"
  http_tokens   = "required"
}

This provides enhanced security against SSRF attacks and unauthorized metadata access.

Security Group Configuration

The module creates a dedicated security group with:

  • SSH (port 22) ingress from specified CIDR blocks (configurable via admin_cidr)
  • All egress traffic allowed (for package updates and outbound connections)

Network Configuration

  • Dedicated network interface for better security isolation
  • Elastic IP for consistent public addressing
  • Deployed in public subnet for external access

Important Notes

  1. Enable Flag: By default, enable_bastion is set to false. You must explicitly set it to true to create the bastion host
  2. SSH Key: Ensure the SSH key specified in key_name exists in the target region before applying
  3. Cost Consideration: The bastion host and Elastic IP incur hourly charges even when idle
  4. AMI Selection: If amazon_optimized_amis is not specified, the module automatically selects the latest Amazon Linux AMI
  5. Access Control: Restrict admin_cidr to specific IP ranges for production use (avoid 0.0.0.0/0)
  6. User Data: Default user data template is included at template/user_data.sh

Example Use Cases

Development Environment

module "dev_bastion" {
  source         = "git@github.com:webuildyourcloud/terraform-aws-bastion.git"
  enable_bastion = true
  environment    = "dev"
  project        = "myapp"
  instance_type  = "t3.micro"
  admin_cidr     = "0.0.0.0/0"  # Open for development
}

Production Environment

module "prod_bastion" {
  source         = "git@github.com:webuildyourcloud/terraform-aws-bastion.git"
  enable_bastion = true
  environment    = "prod"
  project        = "myapp"
  instance_type  = "t3.small"
  ebs_optimized  = true
  admin_cidr     = "203.0.113.0/24"  # Restricted to office network
}

Accessing the Bastion Host

After creation, connect to the bastion using:

ssh -i /path/to/key.pem ec2-user@<bastion-public-ip>

To tunnel through the bastion to private resources:

ssh -i /path/to/key.pem -A -L 8080:private-server:80 ec2-user@<bastion-public-ip>

Best Practices

  1. Use restrictive CIDR blocks for admin_cidr in production
  2. Enable EBS optimization for better performance
  3. Use Session Manager as an alternative to SSH when possible
  4. Regularly update the bastion AMI for security patches
  5. Implement CloudWatch logging for SSH sessions
  6. Consider using AWS Systems Manager Session Manager instead of traditional SSH
  7. Tag resources appropriately for cost allocation
  8. Disable bastion when not in use to reduce costs

Troubleshooting

Cannot connect to bastion

  • Verify the security group allows your IP in admin_cidr
  • Check the Elastic IP is properly associated
  • Ensure the SSH key matches the one specified in key_name
  • Verify the subnet has an Internet Gateway attached

Instance not created

  • Confirm enable_bastion is set to true
  • Check AWS service quotas for EC2 instances
  • Verify subnet and VPC exist

License

This module is provided as-is for use within your organization.

Description
Terraform module for provisioning AWS bastion host with security groups and elastic IP
Readme 46 KiB
Languages
HCL 85%
Shell 15%