Beyond SSH: The Best Ways to Securely Access Your AWS EC2 Instances
Manish Kumar
Cloud & IT Infrastructure Consultant | Architecting Secure, Scalable Solutions for Digital Transformation
Introduction: Secure and Cost-Effective Ways to Access EC2 Instances
Amazon EC2 instances power a wide range of cloud workloads, from web applications to enterprise databases. However, securely accessing these instances is often an overlooked aspect of cloud security and cost optimization.
Traditionally, SSH access with key pairs has been the go-to method for developers, but modern AWS services like Session Manager, VPNs, and Bastion Hosts provide more secure and scalable alternatives. The choice of access method significantly impacts security risks, operational complexity, and costs—making it crucial to select the right approach for your environment.
In this blog, we'll dive deep into:
? The different methods of accessing EC2 instances (SSH, Session Manager, Bastion Hosts, VPNs, and Direct Connect).
? A detailed step-by-step Terraform implementation for each method.
? A comparative analysis of cost, security risks, and best practices.
By the end of this guide, you'll be equipped to choose the best access method that balances security, ease of use, and cost-effectiveness for your AWS environment. Let’s get started!
Accessing EC2 instances can be done using various methods, each with different cost implications and security risks.
Below is a comparison of the most common methods:?
1. SSH (Secure Shell) via Key Pair
Risk Mitigation:
? Use key-based authentication only.
? Restrict SSH access to specific IPs in security groups.
? Rotate SSH keys periodically.?
Best for: Traditional remote access with Linux/macOS terminal or PuTTY on Windows.
Step-by-Step Implementation
Step 1: Create an SSH Key Pair
Step 2: Launch EC2 with Key Pair
Step 3: Connect to the EC2 Instance
On macOS/Linux
chmod 400 my-key.pem
ssh -i my-key.pem ec2-user@<Public-IP>
(Use Ubuntu as the username if using Ubuntu AMI.)
On Windows (Using PuTTY)
Step 4: Security Best Practices
? Disable password authentication in /etc/ssh/sshd_config by setting:
PasswordAuthentication no
? Use Security Groups to allow SSH only from trusted IPs.
? Use AWS Systems Manager (SSM) instead of SSH where possible.
We can also implement it through an IAC tool like Terraform, a sample solution will look like the following:
Creates an EC2 instance with SSH access using a key pair.
Terraform Configuration
provider "aws" {
region = "us-east-1"
}
resource "aws_key_pair" "my_key" {
key_name = "my-key"
public_key = file("~/.ssh/id_rsa.pub") # Replace with your public key path
}
resource "aws_security_group" "ssh_sg" {
name = "allow_ssh"
description = "Allow SSH inbound traffic"
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["YOUR_IP/32"] # Replace with your public IP
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_instance" "my_ec2" {
ami = "ami-0c55b159cbfafe1f0" # Amazon Linux 2 AMI ID (update as needed)
instance_type = "t2.micro"
key_name = aws_key_pair.my_key.key_name
security_groups = [aws_security_group.ssh_sg.name]
tags = {
Name = "SSH-Access-EC2"
}
}
Deployment Steps
2. EC2 Instance Connect (for Amazon Linux & Ubuntu)
Risk Mitigation:
? Use IAM policies with the least privilege.
? Enable MFA for AWS Console.
Best for: Temporary access via AWS Console (no need for SSH key).
Step-by-Step Implementation
Step 1: Enable EC2 Instance Connect
Step 2: Connect Using AWS Console
Step 3: Security Best Practices
? Use IAM roles to restrict who can access EC2 Instance Connect.
? Disable direct SSH if using this method exclusively.?
Steps to implement through the IAC tool Terraform:?
Terraform Configuration
provider "aws" {
region = "us-east-1"
}
resource "aws_iam_role" "ssm_role" {
name = "ssm-instance-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ec2.amazonaws.com"
}
}]
})
}
resource "aws_iam_policy_attachment" "ssm_attach" {
name = "ssm-attach"
roles = [aws_iam_role.ssm_role.name]
policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}
resource "aws_iam_instance_profile" "ssm_instance_profile" {
name = "ssm-instance-profile"
role = aws_iam_role.ssm_role.name
}
resource "aws_instance" "ssm_ec2" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
iam_instance_profile = aws_iam_instance_profile.ssm_instance_profile.name
tags = {
Name = "SSM-Access-EC2"
}
}
Deployment Steps
3. Systems Manager (SSM) Session Manager
Risk Mitigation:
? Restrict IAM roles to allow only authorized users.
? Use AWS Organizations SCPs to prevent misconfigurations.?
Best for: Secure access without SSH, with full AWS IAM control.
Step-by-Step Implementation
Step 1: Attach IAM Role to EC2
Step 2: Install & Start SSM Agent
For Amazon Linux & Ubuntu, the SSM agent is pre-installed. If using another OS:
sudo yum install -y amazon-ssm-agent # Amazon Linux
sudo apt install -y amazon-ssm-agent # Ubuntu
#Start the agent:
sudo systemctl enable amazon-ssm-agent
sudo systemctl start amazon-ssm-agent
领英推荐
Step 3: Connect Using Session Manager
Step 4: Security Best Practices
? Remove SSH access (port 22) completely if using this method.
? Restrict IAM permissions to avoid misuse.
? Enable AWS CloudTrail to monitor session activity.
4. Bastion Host (Jump Server)
Risk Mitigation:
? Use key-based authentication and restrict access by IP.
? Enable logging and monitoring with CloudTrail.
? Consider auto-shutdown policies for unused bastion hosts.
Best for: Private subnet instances needing indirect SSH access.
Step-by-Step Implementation
Step 1: Launch a Bastion Host
Step 2: Launch Private EC2 Instance
Step 3: SSH via Bastion Host
ssh -i my-key.pem ec2-user@<Bastion-Public-IP>
ssh -i my-key.pem ec2-user@<Private-EC2-Private-IP>
Step 4: Security Best Practices
? Configure IAM Session Manager instead of SSH if possible.
? Auto-stop bastion host when not in use.
? Use fail2ban to block brute-force attacks.
Using Terraform to implement the above steps:?
Creates a bastion host in a public subnet and a private EC2 instance.
Terraform Configuration
provider "aws" {
region = "us-east-1"
}
resource "aws_vpc" "my_vpc" {
cidr_block = "10.0.0.0/16"
}
resource "aws_subnet" "public_subnet" {
vpc_id = aws_vpc.my_vpc.id
cidr_block = "10.0.1.0/24"
map_public_ip_on_launch = true
}
resource "aws_subnet" "private_subnet" {
vpc_id = aws_vpc.my_vpc.id
cidr_block = "10.0.2.0/24"
}
resource "aws_security_group" "bastion_sg" {
vpc_id = aws_vpc.my_vpc.id
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["YOUR_IP/32"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_security_group" "private_sg" {
vpc_id = aws_vpc.my_vpc.id
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
security_groups = [aws_security_group.bastion_sg.id]
}
}
resource "aws_instance" "bastion_host" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
subnet_id = aws_subnet.public_subnet.id
security_groups = [aws_security_group.bastion_sg.name]
key_name = aws_key_pair.my_key.key_name
}
resource "aws_instance" "private_instance" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
subnet_id = aws_subnet.private_subnet.id
security_groups = [aws_security_group.private_sg.name]
key_name = aws_key_pair.my_key.key_name
}
Deployment Steps
5. VPN (Virtual Private Network)
Risk Mitigation:
? Use strong authentication (e.g., MFA).
? Regularly rotate VPN credentials.?
Best for: Secure private network access without exposing instances to the internet.
Step-by-Step Implementation
Step 1: Create a Client VPN Endpoint
Step 2: Connect to the VPN
Step 3: Security Best Practices
? Enable MFA for VPN authentication.
? Restrict access using IAM policies.
? Monitor connections with AWS CloudWatch.
?
Using Terraform to create the required setup:
Creates a VPN endpoint for secure access.
Terraform Configuration
resource "aws_ec2_client_vpn_endpoint" "vpn" {
server_certificate_arn = "arn:aws:acm:region:account:certificate/your-cert-id"
client_cidr_block = "10.10.0.0/22"
authentication_options {
type = "certificate-authentication"
root_certificate_chain_arn = "arn:aws:acm:region:account:certificate/your-root-cert-id"
}
connection_log_options {
enabled = false
}
}
resource "aws_ec2_client_vpn_network_association" "vpn_assoc" {
client_vpn_endpoint_id = aws_ec2_client_vpn_endpoint.vpn.id
subnet_id = "subnet-xxxxxxx" # Replace with a private subnet ID
}
Deployment Steps
6. Direct Connect
Risk Mitigation:
? Use NACLs and firewalls to restrict traffic.
? Monitor logs with AWS CloudTrail.?
Best for: Enterprise-level secure private AWS access.
Step-by-Step Implementation
Step 1: Order a Direct Connect Circuit
Step 2: Configure Virtual Interface
Step 3: Secure the Connection
? Implement Network ACLs to restrict unwanted traffic.
? Monitor traffic using AWS CloudWatch.?
?
Best Approach?
Final Comparison summary: