Loading....

Introduction

Protecting your AWS infrastructure is essential in the constantly changing world of cloud security. A Bastion Host, a crucial gateway that serves as your fortress’s initial line of defense, is one crucial part of its defense. We’ll discuss the importance of a Bastion Host in this blog article and show you how to build one using Terraform on AWS. As we set out on a trip to reinforce your cloud infrastructure and improve your security posture, buckle up.

What is a Bastion Host?

A Bastion host, also known as a jump server, acts as a key intermediate between the broad and possibly hostile expanse of the internet and a network’s private, sensitive servers. This intermediate position gives it the ability to add an extra layer of protection to a network’s inner workings, increasing its resilience against external assaults.

The Bastion host guards the secret jewels hidden within the private architecture of a Virtual Private Cloud (VPC), which is positioned strategically within a public subnet. We can create a safe bridge that goes outside the boundaries of this VPC by using the Bastion host. SSH (Secure Shell) provides this bridge, enabling authorized users to connect to and communicate with the private EC2 instances housed inside the same VPC.

Understanding the intricate details of how the Bastion host, keeping watch in the public subnet, orchestrates the secure passage to the VPC’s inner sanctum and makes sure that only those with the necessary credentials are granted access, is essential to comprehending how this implementation works.

Practical Guide

We are going to create a Terraform Module to deploy a Bastion Host and handle the SSH key.

Prerequisites

  1. An AWS Account.
  2. A terminal
  3. Terraform CLI

Architecture diagram

We will have a VPC, a public subnet, a private subnet, a public instance, a private instance, and an internet gateway required for the public instance to connect to the internet, as shown in the above diagram.

Security group configuration

The configuration of the security group is crucial information that we need to understand for Bastion hosts. By inheriting the security group from the bastion host, the private security group enables SSH access from the bastion to the private EC2 instance. Take a look at it in Terraform code.

# Bastion Host Security Group
resource "aws_security_group" "bastion-host-sg" {
  name_prefix = var.security_group_name
  description = "Bastion Host instance security group"
  vpc_id      = aws_vpc.vpc.id

  ingress {
    description = "Allow SSH from my Public IP"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"] #replace with your system ip
  }

  ingress {
    description = "Allows HTTP Access to the Bastion Host"
    from_port   = 8080
    to_port     = 8080
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
  tags = {
    Name = "bastion-host-sg"
  }
}

# Private EC2 Security Group
resource "aws_security_group" "private_sg" {
  name_prefix = var.security_group_name
  description = "Bastion Host instance security group"
  vpc_id      = aws_vpc.vpc.id

  ingress {
    description = "Allow SSH from my Public IP"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    security_groups = [aws_security_group.bastion-host-sg.id]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
  tags = {
    Name = "private-sg"
  }
}

SSH key generation

To produce SSH keys for remote access to EC2 instances on the AWS cloud platform, use the Terraform code listed below. SSH keys are a safe way to log in to and access distant servers.

Let’s step-by-step through the code:A TLS private key is created in the first resource block. SSL (Secure Sockets Layer) was replaced by TLS (Transport Layer Security), which is frequently used for secure internet communication. A popular encryption strategy for safeguarding data transfer, the RSA algorithm is utilized in this instance to produce the private key. Other algorithms include ED25519, P334, and others. Refer to the Terraform registry for further information.

resource "tls_private_key" "rsa_key_generated" {
 algorithm = "RSA"
}

The private key is stored in a local file by the second resource block. The “private_key_pem” attribute of the “tls_private_key.generated” resource is shown as the file’s content. “$var.ssh_key_name” is a placeholder for the name of the SSH key that is supplied in the input variables. The “filename” property specifies the name of the file as “$var.ssh_key_name.pem”. File permission is set to 0400 via the “file_permission” attribute, which denotes that only the owner has read access.

resource "local_file" "private_key_pem" {
 content = tls_private_key.generated.private_key_pem
 filename = "${var.ssh_key_name}.pem"
 file_permission = "0400"
}

The “aws_key_pair” resource is used in the third resource block to construct an AWS key pair. Using the input variable once more, the “key_name” property defines the name of the key pair as “$var.ssh_key_name”. “tls_private_key.generated.public_key_openssh” is the value for the “public_key” property, and it fetches the public key in OpenSSH format from the “tls_private_key.generated” resource.

resource "aws_key_pair" "generated" {
 key_name = var.ssh_key_name
 public_key = tls_private_key.generated.public_key_openssh
}

You can generate an RSA private key using this sample Terraform code, save it to a local file with the required permissions, then construct an AWS key pair with the corresponding public key. AWS’s EC2 instances may then be accessed and authenticated using the generated key pair via SSH.

Bastion Host and EC2 configurations

The security group makes an important difference in this situation. The private ec2 instance receives the private ec2 security group, whereas the Bastion ec2 receives the public security group.

# Public EC2 Instance - BastionHost -Ubuntu, 22.04 LTS
resource "aws_instance" "BastionHost" {
  ami             = "ami-053b0d53c279acc90" 
  instance_type   = "t2.micro"
  key_name        = var.common_ssh_key
  security_groups = [aws_security_group.bastion-host-sg.id]
  subnet_id       = aws_subnet.subnet_public_bastion.id
  
  root_block_device {
    volume_type           = "gp3"
    volume_size           = 10
    throughput            = 500
    delete_on_termination = true
  }

  tags = {
    Name = "Bastion-Host"

  }
}

# Private EC2 Instance - Ubuntu, 22.04 LTS
resource "aws_instance" "ubuntu-instance" {
  ami             = "ami-053b0d53c279acc90"  
  instance_type   = "t2.micro"
  key_name        = var.common_ssh_key
  security_groups = [aws_security_group.private_sg.id]
  subnet_id = aws_subnet.subnet_private.id


  root_block_device {
    volume_type           = "gp3" #Faster then regular gp3 and cost less
    volume_size           = 10
    throughput            = 300
    delete_on_termination = true
  }

  tags = {
    Name = "ubuntu-private-instance"
  }
}

In this code, the new code is the “root_block_device ” where I started to add volume type to “gp3” to increase efficiency and to reduce cost.

Networking configuration

As explained above, we need some configuration regarding to networking services on AWS. The code is below.

#Create VPC and its subnets
# Custom VPC
resource "aws_vpc" "vpc" {
  cidr_block = var.vpc_cidr

  tags = {
    Name = var.vpc_custom
  }
}

# Public Subnet for bastion host
resource "aws_subnet" "subnet_public_bastion" {
  vpc_id                  = aws_vpc.vpc.id
  cidr_block              = var.subnet_cidr_bhec2
  availability_zone = "us-east-1a"
  map_public_ip_on_launch = true

  tags = {
    Name = "subnet_public_bastionhost"
  }

}

# Private Subnet
resource "aws_subnet" "subnet_private" {
  vpc_id                  = aws_vpc.vpc.id
  cidr_block              = var.subnet_cidr_pec2
  availability_zone = "us-east-1b"

  tags = {
    Name = var.subnet_custom
  }
}


# Internet Gateway
resource "aws_internet_gateway" "internet_gateway" {
  vpc_id = aws_vpc.vpc.id

  tags = {
    Name = var.internet_gateway_tag
  }
}

# Route Table
resource "aws_default_route_table" "default_route" {
  default_route_table_id = aws_vpc.vpc.default_route_table_id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.internet_gateway.id
  }
}

With this code, we can deploy a single VPC with two subnets, one private and one public, with a Internet Gateway attached to gain access to the infrastructure through internet. This code can be adapted in current architectures, only pointing to a existing VPC and subnets.

Outputs

Regarding to output items, we need this two basically:

output "remote_access_key" {
  description = "EC2 Remote Access"
  value       = "ssh -i ${local_file.private_key_pem.filename} ubuntu@${aws_instance.BastionHost.public_ip}"
}

output "instance_public_ip" {
  description = "Public IP address of the Jenkins EC2 instance"
  value       = "Bastion Host Public IP: ${aws_instance.BastionHost.public_ip}"
}

Deployment Guide

Before we run terraform Apply using the Terraform CLI, we must see the ssh file in the same directory as our terraform files

Interfaz de usuario gráfica, Texto, Aplicación

Descripción generada automáticamente

Then, we can look the new created instances:

Let’s try to connect to our Bastion Host

Texto

Descripción generada automáticamente

We can see that we connect successfully, we can get the same result as if we try to connect from the private instance and have access to internet in a secure way through the Internet Gateway.

References

https://registry.terraform.io/providers/hashicorp/tls/latest/docs/resources/private_key?source=post_page—–9464a3c1df64——————————–

Leave a Reply

Your email address will not be published.