Skip to main content
Service Catalog Version 0.110.5Last updated in version 0.104.4

Bastion Host

View SourceRelease Notes

Overview

This service creates a single EC2 instance that is meant to serve as a bastion host.

Bastion architectureBastion architecture

A bastion host is a security practice where it is the only server exposed to the public. You must connect to it before you can connect to any of the other servers on your network. This way, you minimize the surface area you expose to attackers, and can focus all your efforts on locking down just a single server.

Features

  • Build an AMI to run on the bastion host
  • Create EC2 instance for the host
  • Allocate an Elastic IP Address (EIP) and an associated DNS record
  • Create an IAM Role and IAM instance profile
  • Create a security group allowing access to the host
  • Harden the OS by installing fail2ban, ntp, auto-update, ip-lockdown, and more
  • Send all logs and metrics to CloudWatch
  • Configure alerts in CloudWatch for CPU, memory, and disk space usage
  • Manage SSH access with IAM groups using ssh-grunt

Learn

note

This repo is a part of the Gruntwork Service Catalog, a collection of reusable, battle-tested, production ready infrastructure code. If you’ve never used the Service Catalog before, make sure to read How to use the Gruntwork Service Catalog!

Core concepts

To understand core concepts like why you should use a bastion host, how to connect to the bastion host, how to use the bastion host as a "jump host" to connect to other instances, port forwarding, and more, see the bastion-host documentation documentation in the terraform-aws-server repo.

The bastion host AMI

The bastion host AMI is defined using the Packer templates bastion-host-ubuntu.json (Packer < v1.7.0) and bastion-host-ubuntu.pkr.hcl (Packer >= v1.7.0). The template configures the AMI to:

  • Run the ssh-grunt module so that developers can upload their public SSH keys to IAM and use those SSH keys, along with their IAM user names, to SSH to the bastion host.

  • Run the auto-update module so that the bastion host installs security updates automatically.

  • Optionally run the syslog module to automatically rotate and rate limit syslog so that the bastion host doesn’t run out of disk space from large volumes of logs.

Deploy

Non-production deployment (quick start for learning)

If you just want to try this repo out for experimenting and learning, check out the following resources:

  • examples/for-learning-and-testing folder: The examples/for-learning-and-testing folder contains standalone sample code optimized for learning, experimenting, and testing (but not direct production usage).

Production deployment

If you want to deploy this repo in production, check out the following resources:

  • examples/for-production folder: The examples/for-production folder contains sample code optimized for direct usage in production. This is code from the Gruntwork Reference Architecture, and it shows you how we build an end-to-end, integrated tech stack on top of the Gruntwork Service Catalog, configure CI / CD for your apps and infrastructure.

Sample Usage

main.tf

# ------------------------------------------------------------------------------------------------------
# DEPLOY GRUNTWORK'S BASTION-HOST MODULE
# ------------------------------------------------------------------------------------------------------

module "bastion_host" {

source = "git::git@github.com:gruntwork-io/terraform-aws-service-catalog.git//modules/mgmt/bastion-host?ref=v0.110.5"

# ----------------------------------------------------------------------------------------------------
# REQUIRED VARIABLES
# ----------------------------------------------------------------------------------------------------

# A list of IP address ranges in CIDR format from which SSH access will be
# permitted. Attempts to access the bastion host from all other IP addresses
# will be blocked. This is only used if var.allow_ssh_from_cidr is true.
allow_ssh_from_cidr_list = <list(string)>

# The AMI to run on the bastion host. This should be built from the Packer
# template under bastion-host.json. One of var.ami or var.ami_filters is
# required. Set to null if looking up the ami with filters.
ami = <string>

# Properties on the AMI that can be used to lookup a prebuilt AMI for use with
# the Bastion Host. You can build the AMI using the Packer template
# bastion-host.json. Only used if var.ami is null. One of var.ami or
# var.ami_filters is required. Set to null if passing the ami ID directly.
ami_filters = <object(
owners = list(string)
filters = list(object(
name = string
values = list(string)
))
)>

# The ID of the subnet in which to deploy the bastion. Must be a subnet in
# var.vpc_id.
subnet_id = <string>

# The ID of the VPC in which to deploy the bastion.
vpc_id = <string>

# ----------------------------------------------------------------------------------------------------
# OPTIONAL VARIABLES
# ----------------------------------------------------------------------------------------------------

# A list of optional additional security group ids to assign to the bastion
# server.
additional_security_group_ids = []

# The ARNs of SNS topics where CloudWatch alarms (e.g., for CPU, memory, and
# disk space usage) should send notifications.
alarms_sns_topic_arn = []

# Tags to use to filter the Route 53 Hosted Zones that might match the hosted
# zone's name (use if you have multiple public hosted zones with the same
# name)
base_domain_name_tags = {}

# Cloud init scripts to run on the bastion host while it boots. See the part
# blocks in
# https://www.terraform.io/docs/providers/template/d/cloudinit_config.html for
# syntax.
cloud_init_parts = {}

# The ID (ARN, alias ARN, AWS ID) of a customer managed KMS Key to use for
# encrypting log data.
cloudwatch_log_group_kms_key_id = null

# The number of days to retain log events in the log group. Refer to
# https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group#retention_in_days
# for all the valid values. When null, the log events are retained forever.
cloudwatch_log_group_retention_in_days = null

# Tags to apply on the CloudWatch Log Group, encoded as a map where the keys
# are tag keys and values are tag values.
cloudwatch_log_group_tags = null

# Set to true to create a DNS record in Route53 pointing to the bastion. If
# true, be sure to set var.domain_name.
create_dns_record = true

# The default OS user for the Bastion Host AMI. For AWS Ubuntu AMIs, which is
# what the Packer template in bastion-host.json uses, the default OS user is
# 'ubuntu'.
default_user = "ubuntu"

# The apex domain of the hostname for the bastion server (e.g., example.com).
# The complete hostname for the bastion server will be
# var.name.var.domain_name (e.g., bastion.example.com). Only used if
# create_dns_record is true.
domain_name = ""

# If true, the launched EC2 Instance will be EBS-optimized.
ebs_optimized = true

# Set to true to enable several basic CloudWatch alarms around CPU usage,
# memory usage, and disk space usage. If set to true, make sure to specify SNS
# topics to send notifications to using var.alarms_sns_topic_arn.
enable_cloudwatch_alarms = true

# Set to true to send logs to CloudWatch. This is useful in combination with
# https://github.com/gruntwork-io/terraform-aws-monitoring/tree/master/modules/logs/cloudwatch-log-aggregation-scripts
# to do log aggregation in CloudWatch.
enable_cloudwatch_log_aggregation = true

# Set to true to add IAM permissions to send custom metrics to CloudWatch.
# This is useful in combination with
# https://github.com/gruntwork-io/terraform-aws-monitoring/tree/master/modules/agents/cloudwatch-agent
# to get memory and disk metrics in CloudWatch for your Bastion host.
enable_cloudwatch_metrics = true

# Enable fail2ban to block brute force log in attempts. Defaults to true.
enable_fail2ban = true

# Enable ip-lockdown to block access to the instance metadata. Defaults to
# true.
enable_ip_lockdown = true

# Set to true to add IAM permissions for ssh-grunt
# (https://github.com/gruntwork-io/terraform-aws-security/tree/master/modules/ssh-grunt),
# which will allow you to manage SSH access via IAM groups.
enable_ssh_grunt = true

# If you are using ssh-grunt and your IAM users / groups are defined in a
# separate AWS account, you can use this variable to specify the ARN of an IAM
# role that ssh-grunt can assume to retrieve IAM group and public SSH key info
# from that account. To omit this variable, set it to an empty string (do NOT
# use null, or Terraform will complain).
external_account_ssh_grunt_role_arn = ""

# The period, in seconds, over which to measure the CPU utilization percentage
# for the instance.
high_instance_cpu_utilization_period = 60

# Trigger an alarm if the EC2 instance has a CPU utilization percentage above
# this threshold.
high_instance_cpu_utilization_threshold = 90

# Sets how this alarm should handle entering the INSUFFICIENT_DATA state.
# Based on
# https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/AlarmThatSendsEmail.html#alarms-and-missing-data.
# Must be one of: 'missing', 'ignore', 'breaching' or 'notBreaching'.
high_instance_cpu_utilization_treat_missing_data = "missing"

# The period, in seconds, over which to measure the root disk utilization
# percentage for the instance.
high_instance_disk_utilization_period = 60

# Trigger an alarm if the EC2 instance has a root disk utilization percentage
# above this threshold.
high_instance_disk_utilization_threshold = 90

# Sets how this alarm should handle entering the INSUFFICIENT_DATA state.
# Based on
# https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/AlarmThatSendsEmail.html#alarms-and-missing-data.
# Must be one of: 'missing', 'ignore', 'breaching' or 'notBreaching'.
high_instance_disk_utilization_treat_missing_data = "missing"

# The period, in seconds, over which to measure the Memory utilization
# percentage for the instance.
high_instance_memory_utilization_period = 60

# Trigger an alarm if the EC2 instance has a Memory utilization percentage
# above this threshold.
high_instance_memory_utilization_threshold = 90

# Sets how this alarm should handle entering the INSUFFICIENT_DATA state.
# Based on
# https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/AlarmThatSendsEmail.html#alarms-and-missing-data.
# Must be one of: 'missing', 'ignore', 'breaching' or 'notBreaching'.
high_instance_memory_utilization_treat_missing_data = "missing"

# The type of instance to run for the bastion host
instance_type = "t3.micro"

# The name of a Key Pair that can be used to SSH to this instance.
keypair_name = null

# The name of the bastion host and the other resources created by these
# templates
name = "bastion-host"

# If set to true, the root volume will be deleted when the Instance is
# terminated.
root_volume_delete_on_termination = true

# The size of the root volume, in gigabytes.
root_volume_size = 8

# Tags to set on the root volume.
root_volume_tags = {}

# The root volume type. Must be one of: standard, gp2, io1.
root_volume_type = "standard"

# When true, precreate the CloudWatch Log Group to use for log aggregation
# from the EC2 instances. This is useful if you wish to customize the
# CloudWatch Log Group with various settings such as retention periods and KMS
# encryption. When false, the CloudWatch agent will automatically create a
# basic log group to use.
should_create_cloudwatch_log_group = true

# If you are using ssh-grunt, this is the name of the IAM group from which
# users will be allowed to SSH to this Bastion Host. This value is only used
# if enable_ssh_grunt=true.
ssh_grunt_iam_group = "ssh-grunt-users"

# If you are using ssh-grunt, this is the name of the IAM group from which
# users will be allowed to SSH to this Bastion Host with sudo permissions.
# This value is only used if enable_ssh_grunt=true.
ssh_grunt_iam_group_sudo = "ssh-grunt-sudo-users"

# The tenancy of this server. Must be one of: default, dedicated, or host.
tenancy = "default"

# When true, all IAM policies will be managed as dedicated policies rather
# than inline policies attached to the IAM roles. Dedicated managed policies
# are friendlier to automated policy checkers, which may scan a single
# resource for findings. As such, it is important to avoid inline policies
# when targeting compliance with various security standards.
use_managed_iam_policies = true

}


Reference

Required

allow_ssh_from_cidr_listlist(string)required

A list of IP address ranges in CIDR format from which SSH access will be permitted. Attempts to access the bastion host from all other IP addresses will be blocked. This is only used if allow_ssh_from_cidr is true.

amistringrequired

The AMI to run on the bastion host. This should be built from the Packer template under bastion-host.json. One of ami or ami_filters is required. Set to null if looking up the ami with filters.

ami_filtersobject(…)required

Properties on the AMI that can be used to lookup a prebuilt AMI for use with the Bastion Host. You can build the AMI using the Packer template bastion-host.json. Only used if ami is null. One of ami or ami_filters is required. Set to null if passing the ami ID directly.

object({
# List of owners to limit the search. Set to null if you do not wish to limit the search by AMI owners.
owners = list(string)

# Name/Value pairs to filter the AMI off of. There are several valid keys, for a full reference, check out the
# documentation for describe-images in the AWS CLI reference
# (https://docs.aws.amazon.com/cli/latest/reference/ec2/describe-images.html).
filters = list(object({
name = string
values = list(string)
}))
})
Details

Name/Value pairs to filter the AMI off of. There are several valid keys, for a full reference, check out the
documentation for describe-images in the AWS CLI reference
(https://docs.aws.amazon.com/cli/latest/reference/ec2/describe-images.html).

subnet_idstringrequired

The ID of the subnet in which to deploy the bastion. Must be a subnet in vpc_id.

vpc_idstringrequired

The ID of the VPC in which to deploy the bastion.

Optional

additional_security_group_idslist(string)optional

A list of optional additional security group ids to assign to the bastion server.

[]
alarms_sns_topic_arnlist(string)optional

The ARNs of SNS topics where CloudWatch alarms (e.g., for CPU, memory, and disk space usage) should send notifications.

[]
base_domain_name_tagsmap(string)optional

Tags to use to filter the Route 53 Hosted Zones that might match the hosted zone's name (use if you have multiple public hosted zones with the same name)

{}
cloud_init_partsmap(object(…))optional

Cloud init scripts to run on the bastion host while it boots. See the part blocks in https://www.terraform.io/docs/providers/template/d/cloudinit_config.html for syntax.

map(object({
filename = string
content_type = string
content = string
}))
{}

The ID (ARN, alias ARN, AWS ID) of a customer managed KMS Key to use for encrypting log data.

null

The number of days to retain log events in the log group. Refer to https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group#retention_in_days for all the valid values. When null, the log events are retained forever.

null
cloudwatch_log_group_tagsmap(string)optional

Tags to apply on the CloudWatch Log Group, encoded as a map where the keys are tag keys and values are tag values.

null
create_dns_recordbooloptional

Set to true to create a DNS record in Route53 pointing to the bastion. If true, be sure to set domain_name.

true
default_userstringoptional

The default OS user for the Bastion Host AMI. For AWS Ubuntu AMIs, which is what the Packer template in bastion-host.json uses, the default OS user is 'ubuntu'.

"ubuntu"
domain_namestringoptional

The apex domain of the hostname for the bastion server (e.g., example.com). The complete hostname for the bastion server will be name.domain_name (e.g., bastion.example.com). Only used if create_dns_record is true.

""
ebs_optimizedbooloptional

If true, the launched EC2 Instance will be EBS-optimized.

true

Set to true to enable several basic CloudWatch alarms around CPU usage, memory usage, and disk space usage. If set to true, make sure to specify SNS topics to send notifications to using alarms_sns_topic_arn.

true

Set to true to send logs to CloudWatch. This is useful in combination with https://github.com/gruntwork-io/terraform-aws-monitoring/tree/master/modules/logs/cloudwatch-log-aggregation-scripts to do log aggregation in CloudWatch.

true

Set to true to add IAM permissions to send custom metrics to CloudWatch. This is useful in combination with https://github.com/gruntwork-io/terraform-aws-monitoring/tree/master/modules/agents/cloudwatch-agent to get memory and disk metrics in CloudWatch for your Bastion host.

true
enable_fail2banbooloptional

Enable fail2ban to block brute force log in attempts. Defaults to true.

true
enable_ip_lockdownbooloptional

Enable ip-lockdown to block access to the instance metadata. Defaults to true.

true
enable_ssh_gruntbooloptional

Set to true to add IAM permissions for ssh-grunt (https://github.com/gruntwork-io/terraform-aws-security/tree/master/modules/ssh-grunt), which will allow you to manage SSH access via IAM groups.

true

If you are using ssh-grunt and your IAM users / groups are defined in a separate AWS account, you can use this variable to specify the ARN of an IAM role that ssh-grunt can assume to retrieve IAM group and public SSH key info from that account. To omit this variable, set it to an empty string (do NOT use null, or Terraform will complain).

""

The period, in seconds, over which to measure the CPU utilization percentage for the instance.

60

Trigger an alarm if the EC2 instance has a CPU utilization percentage above this threshold.

90

Sets how this alarm should handle entering the INSUFFICIENT_DATA state. Based on https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/AlarmThatSendsEmail.html#alarms-and-missing-data. Must be one of: 'missing', 'ignore', 'breaching' or 'notBreaching'.

"missing"

The period, in seconds, over which to measure the root disk utilization percentage for the instance.

60

Trigger an alarm if the EC2 instance has a root disk utilization percentage above this threshold.

90

Sets how this alarm should handle entering the INSUFFICIENT_DATA state. Based on https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/AlarmThatSendsEmail.html#alarms-and-missing-data. Must be one of: 'missing', 'ignore', 'breaching' or 'notBreaching'.

"missing"

The period, in seconds, over which to measure the Memory utilization percentage for the instance.

60

Trigger an alarm if the EC2 instance has a Memory utilization percentage above this threshold.

90

Sets how this alarm should handle entering the INSUFFICIENT_DATA state. Based on https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/AlarmThatSendsEmail.html#alarms-and-missing-data. Must be one of: 'missing', 'ignore', 'breaching' or 'notBreaching'.

"missing"
instance_typestringoptional

The type of instance to run for the bastion host

"t3.micro"
keypair_namestringoptional

The name of a Key Pair that can be used to SSH to this instance.

null
namestringoptional

The name of the bastion host and the other resources created by these templates

"bastion-host"

If set to true, the root volume will be deleted when the Instance is terminated.

true
root_volume_sizenumberoptional

The size of the root volume, in gigabytes.

8
root_volume_tagsmap(string)optional

Tags to set on the root volume.

{}
root_volume_typestringoptional

The root volume type. Must be one of: standard, gp2, io1.

"standard"

When true, precreate the CloudWatch Log Group to use for log aggregation from the EC2 instances. This is useful if you wish to customize the CloudWatch Log Group with various settings such as retention periods and KMS encryption. When false, the CloudWatch agent will automatically create a basic log group to use.

true
ssh_grunt_iam_groupstringoptional

If you are using ssh-grunt, this is the name of the IAM group from which users will be allowed to SSH to this Bastion Host. This value is only used if enable_ssh_grunt=true.

"ssh-grunt-users"

If you are using ssh-grunt, this is the name of the IAM group from which users will be allowed to SSH to this Bastion Host with sudo permissions. This value is only used if enable_ssh_grunt=true.

"ssh-grunt-sudo-users"
tenancystringoptional

The tenancy of this server. Must be one of: default, dedicated, or host.

"default"

When true, all IAM policies will be managed as dedicated policies rather than inline policies attached to the IAM roles. Dedicated managed policies are friendlier to automated policy checkers, which may scan a single resource for findings. As such, it is important to avoid inline policies when targeting compliance with various security standards.

true