Skip to main content

Introduction

A bastion host serves as a secure entry point to your private Poolside subnets, enabling you to perform deployment tasks that require network access to resources not directly accessible from your local environment. The steps provided here focus on setting up a headless Linux bastion host. However, these instructions are equally applicable if you choose to deploy a GUI-based bastion host.

Prerequisites

This document presumes that you have already deployed the AWS VPC and subnets for Poolside as documented in the AWS Networking Setup guide. Before you begin, ensure you have the following:
  • AWS Account Credentials: Access to an AWS account with permissions to create EC2 instances, configure networking, and manage IAM roles.
  • VPC Information:
    • vpc_id where Poolside will be deployed
    • private_subnet_ids for the bastion host
    • private_subnet_ids for Poolside
  • Network Access:
    • Ability to access the bastion host via SSH from your local machine.
    • A GUI-based system with a web browser and network access to the Poolside API endpoint for the Splash CLI steps.
  • Splash CLI Package: Provided by the Poolside team.
  • Required Tools: List of tools to be installed on the bastion host (detailed in Step 3).

Deploy an EC2 Instance in the Public Subnet

A sample Terraform script is provided in the Appendix below. However, if you would like to deploy manually, begin by logging into the AWS Management Console and navigating to the EC2 service. Select the option to launch a new instance, the specifications for which should look similar to the following:
  • Name: poolside-bastion (or similar)
  • AMI: Amazon Linux 2023 (or preferred AMI)
  • Instance Type: t3.small
  • Key Pair: Select or create a new one for SSH access
  • Network:
    • VPC: Choose the VPC where Poolside will be deployed
    • Subnet: Choose a public subnet within the VPC
    • Auto-assign Public IP: Enable
  • Security Group:
  • Create a new security group with the following inbound rule:
    • Type: SSH
    • Protocol: TCP
    • Port Range: 22
    • Source: Your trusted IP address or CIDR block
Review the instance details and launch the instance. Save and note down the location of your EC2 instance key pair for SSH access.

Configure Security Groups and Network Access

In order for your new bastion host to connect to Poolside’s network, you will need to ensure all network paths and ports are correctly configured.

Modify Bastion Host Security Group

Ensure the security group allows outbound access to the private subnets where Poolside resides. This setting is generally enabled by default.

Configure Private Subnets’ Security Groups

  • EKS Cluster Security Group: Add an inbound rule to allow traffic from the bastion host’s security group on necessary ports, such as:
  • Type: HTTPS
    • Protocol: TCP
    • Port Range: 443
    • Source: Bastion Host security group ID
  • Node Security Groups: If needed, allow inbound SSH and other necessary ports from the bastion host.

Network Access Control Lists (NACLs)

Ensure NACLs associated with your subnets allow the necessary inbound and outbound traffic between the bastion host and the private subnets.
  • Inbound Rules: Allow traffic from the bastion host’s subnet CIDR to the private subnets.
  • Outbound Rules: Allow traffic from the private subnets to the bastion host’s subnet CIDR.

Install Required Tools on the Bastion Host

Start by connecting to the bastion host via SSH
ssh -i /path/to/your/key.pem ec2-user@<Bastion_Public_IP>
Install Curl (in not already pre-installed):
sudo yum install -y curl
Install the AWS CLI:
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
Install Docker:
sudo amazon-linux-extras install docker -y
sudo service docker start
sudo usermod -a -G docker ec2-user
Install JQ:
sudo yum install -y jq
Install Kubectl:
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x ./kubectl
sudo mv ./kubectl /usr/local/bin/
Install Helm:
curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 > get_helm.sh
chmod 700 get_helm.sh
./get_helm.sh
Install Helmfile:
curl -L https://github.com/helmfile/helmfile/releases/download/v0.169.1/helmfile_0.169.1_linux_amd64.tar.gz | \
tar xz && \
sudo mv helmfile /usr/local/bin/helmfile && \
sudo chmod +x /usr/local/bin/helmfile && \
helmfile --version
Install Terraform:
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/AmazonLinux/hashicorp.repo
sudo yum -y install terraform
And finally, verify the installations:
aws --version
docker --version
jq --version
kubectl version --client
helm version
helmfile --version
terraform version
curl --version
If you are deploying on a GUI-based system, then you can install the Splash CLI provided to you by Poolside here as well. If not, save this package for later.

Configure AWS Credentials

To deploy the required Poolside’s resources, the bastion host needs appropriate AWS permissions. You have two options:
  1. Create and attach a dedicated IAM Role (recommended)
    • Navigate to Instance → ‘Actions’ → ‘Security’ → ‘Modify IAM role’
  2. Specify a set of (new or existing) AWS Access Keys for the Bastion’s AWS CLI
    • Run aws configure on the bastion host and input your key configs and default region

Required AWS Permissions

For either method, the following AWS permissions are required:
  1. Amazon EKS: Full access to EKS services (eks:*)
  2. Amazon EC2:
    • Describe actions for instances, VPCs, subnets, security groups, route tables, and volumes
    • Create and manage security groups
    • Create and manage EC2 instances for node groups
  3. Amazon ECR:
    • Pull images
    • Get authorization token
  4. Amazon S3: Full access to create, read, update, and delete S3 buckets and objects related to the Poolside deployment
  5. Amazon RDS: Full access to create, read, update, and delete RDS instances related to the Poolside deployment
  6. IAM: Create and manage roles and policies (limited to roles/policies related to Poolside)
  7. VPC: Describe and modify VPC components (subnets, route tables, NACLs)
  8. AWS Certificate Manager (ACM): Describe and list certificates
  9. AWS Key Management Service (KMS): Describe keys and create grants
Note: The specific permissions for your deployment may vary depending on your choices of container registry, certificate manager, etc. Always follow the principle of least privilege when assigning permissions.

Proceed to Poolside VPC Deployment

When the above steps have been completed, you can proceed to the Poolside VPC Deployment guide to complete the setup. The Poolside team will provide you with deployment scripts that will need to be transferred over to the bastion to complete the setup.

Appendix

(Sample) Terraform Bastion Host Deployment Script

The following Terraform script is provided as a reference implementation. Before using or adapting this script for your environment, please consider the following:
  1. Review thoroughly: Ensure the script aligns with your organization’s security policies, compliance requirements, and best practices.
  2. Customization: Modify all sections marked with ”# Replace” comments to reflect your specific AWS environment and requirements.
  3. IAM Policies: Pay special attention to the included IAM role policy. The provided resource scope is permissive and should be adjusted to adhere to your organization’s security standards.
By using or adapting this script, you acknowledge that you understand its implications and take responsibility for its implementation in your environment. Poolside does not guarantee the script’s suitability for your specific use case and is not liable for any issues arising from its use.
main.tf

provider "aws" {
  region = "us-west-2"                # Replace with your desired AWS region
}

# Variables
variable "vpc_id" {
  description = "The ID of the VPC where the bastion host will be deployed"
  type        = string
  default     = "vpc-...."            # Replace with your VPC ID
}

variable "public_subnet_id" {
  description = "The ID of the public subnet for the bastion host"
  type        = string
  default     = "subnet-...."         # Replace with your public subnet ID
}

variable "trusted_ip" {
  description = "Your trusted IP address for SSH access"
  type        = string
  default     = "1.2.3.4/32"          # Replace with your IP address or CIDR block
}

variable "key_name" {
  description = "The name of the key pair for SSH access"
  type        = string
  default     = "your-key-pair-name"  # Replace with your key pair name
}

# Bastion Host EC2 Instance
resource "aws_instance" "bastion" {
  ami                    = "ami-..."  # Replace with your desired AMI ID (e.g., Amazon Linux 2)
  instance_type          = "t3.medium"
  subnet_id              = var.public_subnet_id
  vpc_security_group_ids = [aws_security_group.bastion_sg.id]
  key_name               = var.key_name
  iam_instance_profile   = aws_iam_instance_profile.bastion_profile.name

  associate_public_ip_address = true

  user_data = <<-EOF
    #!/bin/bash
    yum update -y
    yum install -y curl jq unzip yum-utils

    # Install AWS CLI
    curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
    unzip awscliv2.zip
    ./aws/install

    # Install Docker
    amazon-linux-extras install docker -y
    systemctl start docker
    systemctl enable docker
    usermod -a -G docker ec2-user

    # Install kubectl
    curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
    chmod +x kubectl
    mv kubectl /usr/local/bin/

    # Install Helm
    curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash

    # Install Helmfile
    curl -L https://github.com/helmfile/helmfile/releases/download/v0.144.0/helmfile_linux_amd64 -o helmfile
    chmod +x helmfile
    mv helmfile /usr/local/bin/

    # Install Terraform
    yum-config-manager --add-repo https://rpm.releases.hashicorp.com/AmazonLinux/hashicorp.repo
    yum -y install terraform
  EOF

  tags = {
    Name = "poolside-bastion"
  }
}

# Security Group for Bastion Host
resource "aws_security_group" "bastion_sg" {
  name        = "poolside-bastion-sg"
  description = "Security group for Poolside bastion host"
  vpc_id      = var.vpc_id

  ingress {
    description = "SSH from trusted IP"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = [var.trusted_ip]
  }

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

  tags = {
    Name = "poolside-bastion-sg"
  }
}

# IAM Role for Bastion Host
resource "aws_iam_role" "bastion_role" {
  name = "poolside-bastion-role"

  assume_role_policy = jsonencode({
    Version   = "2012-10-17",
    Statement = [
      {
        Effect    = "Allow",
        Principal = { Service = "ec2.amazonaws.com" },
        Action    = "sts:AssumeRole"
      }
    ]
  })
}

# IAM Instance Profile for Bastion Host
resource "aws_iam_instance_profile" "bastion_profile" {
  name = "poolside-bastion-profile"
  role = aws_iam_role.bastion_role.name
}

# IAM Policy for Bastion Host
resource "aws_iam_role_policy" "bastion_policy" {
  name = "poolside-bastion-policy"
  role = aws_iam_role.bastion_role.id

  policy = jsonencode({
    Version   = "2012-10-17",
    Statement = [
      {
        Effect   = "Allow",
        Action   = [
          "eks:*",
          "ec2:Describe*",
          "ec2:CreateSecurityGroup",
          "ec2:DeleteSecurityGroup",
          "ec2:AuthorizeSecurityGroupIngress",
          "ec2:RevokeSecurityGroupIngress",
          "ec2:CreateTags",
          "ec2:RunInstances",
          "ec2:TerminateInstances",
          "ec2:ModifyInstanceAttribute",
          "ec2:CreateVolume",
          "ec2:DeleteVolume",
          "ec2:AttachVolume",
          "ec2:DetachVolume",
          "ecr:GetAuthorizationToken",
          "ecr:BatchCheckLayerAvailability",
          "ecr:GetDownloadUrlForLayer",
          "ecr:BatchGetImage",
          "rds:CreateDBInstance",
          "rds:DeleteDBInstance",
          "rds:DescribeDBInstances",
          "rds:ModifyDBInstance",
          "rds:CreateDBParameterGroup",
          "rds:DeleteDBParameterGroup",
          "rds:ModifyDBParameterGroup",
          "rds:DescribeDBParameterGroups",
          "rds:CreateDBSubnetGroup",
          "rds:DeleteDBSubnetGroup",
          "rds:DescribeDBSubnetGroups",
          "kms:CreateGrant",
          "kms:DescribeKey",
          "acm:DescribeCertificate",
          "acm:ListCertificates",
          "iam:GetRole",
          "iam:ListAttachedRolePolicies",
          "iam:CreateRole",
          "iam:DeleteRole",
          "iam:AttachRolePolicy",
          "iam:DetachRolePolicy",
          "iam:PutRolePolicy",
          "iam:DeleteRolePolicy",
          "s3:CreateBucket",
          "s3:DeleteBucket",
          "s3:ListBucket",
          "s3:GetBucketLocation",
          "s3:PutBucketPolicy",
          "s3:PutEncryptionConfiguration",
          "s3:PutBucketTagging",
          "s3:PutBucketLogging",
          "s3:PutBucketPublicAccessBlock",
          "s3:GetObject",
          "s3:PutObject",
          "s3:DeleteObject"
        ],
        Resource = "*"
      }
    ]
  })
}

# Output the public IP of the bastion host
output "bastion_public_ip" {
  value       = aws_instance.bastion.public_ip
  description = "The public IP address of the bastion host"
}