Skip to main content

AWS EC2 Session Manager: Access EC2 Shell without SSH via AWS Session Manager

1352 words·
AWS AWS CLI Terraform EC2 Session Manager
Table of Contents

Prerequisites
#

Install AWS CLI
#

# Update packages
sudo apt update

# Unstall zip tool
sudo apt install unzip -y

# Download AWS CLI zip file
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"

# Unzip
unzip awscliv2.zip

# Install
sudo ./aws/install
# Verify installation / check version
/usr/local/bin/aws --version

Configure AWS CLI
#

# Start AWS CLI configuration
aws configure

Install Terraform
#

# Install the HashiCorp GPG key
wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor | sudo tee /usr/share/keyrings/hashicorp-archive-keyring.gpg > /dev/null

# Verify the GPG key fingerprint
gpg --no-default-keyring --keyring /usr/share/keyrings/hashicorp-archive-keyring.gpg --fingerprint

# Add the official HashiCorp repository 
echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/hashicorp.list

# Install Terraform
sudo apt update && sudo apt-get install terraform
# Verify installation / check version
terraform version



Overview
#

Requirements
#

  • Systems Manager Agent (SSM Agent) must be installed and running on EC2 instances (Enabled on AWS Linux and Ubuntu per default)

  • IAM Role & Permissions

  • GUI Terminal or AWS CLI to open a session

  • The EC2 instance must be able to reach AWS Systems Manager endpoints, either through public internet (Internet or NAT Gateway) or a VPC endpoint for Systems Manager

  • Open SSH port (22) is not required in the security group



Terraform Network & EC2 Stack
#

File and Folder Structure
#

The file and folder structure of the Terraform project looks like this:

aws-vpc-ec2
├── ec2_sessionmanager.tf
├── ec2.tf
├── terraform.tf
├── variables.tf
└── vpc.tf

Terraform Configuration Files
#

Project Folder & Terraform Provider
#

# Create Terraform project folder
TF_PROJECT_NAME=aws-vpc-ec2
mkdir $TF_PROJECT_NAME && cd $TF_PROJECT_NAME

  • terraform.tf
# Terraform Provider
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

# Provider AWS Region
provider "aws" {
  alias  = "aws_region"
  region = var.aws_region
}

Variables
#

  • variables.tf
## AWS Region & Availability Zones

variable "aws_region" {
  description = "AWS Region"
  type        = string
  default     = "us-east-1"
}

variable "availability_zone_1" {
  description = "The availability zone for the subnet"
  type        = string
  default     = "us-east-1a"
}


## VPC: CIDR Blocks

variable "vpc1_cidr" {
  description = "VPC CIDR block"
  type        = string
  default     = "10.10.0.0/16"
}

variable "vpc1_subnet_cidr_1" {
  description = "Subnet CIDR block"
  type        = string
  default     = "10.10.0.0/24"
}

variable "vpc1_subnet_cidr_2" {
  description = "Subnet CIDR block"
  type        = string
  default     = "10.10.1.0/24"
}


## EC2 SSH Key and EC2 Image

# SSH key pair name
variable "key_name" {
  default = "us-east-1-pc-le" # Define key pair name
}

# EC2 Image ID
variable "ami_id" {
  default = "ami-0e2c8caa4b6378d8c" # Define EC2 AMI ID
}

VPC Network
#

  • vpc.tf
## VPC & Subnets

# VPC
resource "aws_vpc" "vpc1" {
  provider = aws.aws_region
  cidr_block           = var.vpc1_cidr
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = {
    Name       = "VPC1"
    Env        = "Production"
  }
}


# Public Subnet "10.10.0.0/24"
resource "aws_subnet" "vpc1_subnet_public1" {
  provider = aws.aws_region
  vpc_id                  = aws_vpc.vpc1.id
  cidr_block              = var.vpc1_subnet_cidr_1
  availability_zone       = var.availability_zone_1
  map_public_ip_on_launch = true

  tags = {
    Name      = "VPC1 Subnet-Public-1"
    Env       = "Production"
  }
}

# Private Subnet "10.10.1.0/24"
resource "aws_subnet" "vpc1_subnet_private1" {
  provider = aws.aws_region
  vpc_id            = aws_vpc.vpc1.id
  cidr_block        = var.vpc1_subnet_cidr_2
  availability_zone = var.availability_zone_1

  tags = {
    Name      = "VPC1 Subnet-Private-1"
    Env       = "Production"
  }
}


## Gateways

# Internet Gateway
resource "aws_internet_gateway" "vpc1_igw" {
  provider = aws.aws_region
  vpc_id = aws_vpc.vpc1.id

  tags = {
    Name        = "VPC1 IGW"
    Env         = "Production"
  }
}

# Elastic IP (EIP) for NAT Gateway
resource "aws_eip" "vpc1_nat_gw_eip" {
  provider = aws.aws_region
  domain = "vpc"

  tags = {
    Name        = "VPC1 NAT-GW EIP"
    Env         = "Production"
  }
}

# NAT Gateway
resource "aws_nat_gateway" "vpc1_nat_gw" {
  provider = aws.aws_region
  allocation_id = aws_eip.vpc1_nat_gw_eip.id
  subnet_id     = aws_subnet.vpc1_subnet_public1.id

  tags = {
    Name        = "VPC1 NAT-GW"
    Env         = "Production"
  }

  depends_on = [aws_internet_gateway.vpc1_igw]
}


## Routing

# Private Routing Table
resource "aws_route_table" "vpc1_private_routetable" {
  provider = aws.aws_region
  vpc_id = aws_vpc.vpc1.id

  route {
    cidr_block     = "0.0.0.0/0"
    nat_gateway_id = aws_nat_gateway.vpc1_nat_gw.id
  }

  tags = {
    Name        = "VPC1 Private Route Table"
    Env         = "Production"
  }
}

# Public Routing Table
resource "aws_route_table" "vpc1_public_routetable" {
  provider = aws.aws_region
  vpc_id = aws_vpc.vpc1.id

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

  tags = {
    Name        = "VPC1 Public Route Table"
    Env         = "Production"
  }
}


# Associate Routes with Subnets
resource "aws_route_table_association" "vpc1_subnet_public1_ra" {
  provider = aws.aws_region
  subnet_id      = aws_subnet.vpc1_subnet_public1.id
  route_table_id = aws_route_table.vpc1_public_routetable.id
}

resource "aws_route_table_association" "vpc1_subnet_private1_ra" {
  provider = aws.aws_region
  subnet_id      = aws_subnet.vpc1_subnet_private1.id
  route_table_id = aws_route_table.vpc1_private_routetable.id
}

Session Manager
#

  • ec2_sessionmanager.tf
# IAM Role
resource "aws_iam_role" "ssm_role" {
  name = "ssm-role"

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

# IAM Policy Attachment
resource "aws_iam_role_policy_attachment" "ssm_role_policy" {
  role       = aws_iam_role.ssm_role.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}

# Instance Profile
resource "aws_iam_instance_profile" "ssm_instance_profile" {
  name = "ssm-instance-profile"
  role = aws_iam_role.ssm_role.name
}

EC2 & Security Group
#

  • ec2.tf
#  EC2 Instance in Public Subnet
resource "aws_instance" "ec2_vpc1_public_subnet" {
  provider = aws.aws_region
  ami                    = var.ami_id
  instance_type          = "t2.micro"
  subnet_id              = aws_subnet.vpc1_subnet_public1.id
  key_name               = var.key_name
  vpc_security_group_ids = [aws_security_group.vpc1_sg.id]
  iam_instance_profile = aws_iam_instance_profile.ssm_instance_profile.name  # Enable Session Manager

  tags = {
    Name = "Public VM"
    Env  = "Production"
  }
}

# EC2 Instance in Private Subnet
resource "aws_instance" "ec2_vpc1_private_subnet1" {
  provider = aws.aws_region
  ami                    = var.ami_id
  instance_type          = "t2.micro"
  subnet_id              = aws_subnet.vpc1_subnet_private1.id
  key_name               = var.key_name
  vpc_security_group_ids = [aws_security_group.vpc1_sg.id]
  iam_instance_profile = aws_iam_instance_profile.ssm_instance_profile.name  # Enable Session Manager

  tags = {
    Name = "Private VM"
    Env  = "Production"
  }
}


# VPC1: Security Group for SSH Access and Ping
resource "aws_security_group" "vpc1_sg" {
  provider = aws.aws_region
  name        = "VPC1-SG"
  description = "Security group for SSH access and ping"
  vpc_id      = aws_vpc.vpc1.id

  ingress {
    description = "Allow ping"
    from_port   = 8
    to_port     = -1
    protocol    = "icmp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    description = "Allow all outbound traffic"
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags = {
    Name = "VPC1-SG"
    Env  = "Production"
  }
}


## Outputs

# Public VM Public IP
output "Public-VM-Public-IP" {
  description = "Public IP address of the EC2 instance in the public subnet"
  value       = aws_instance.ec2_vpc1_public_subnet.public_ip
}

# Public VM Private IP
output "Public-VM-Private-IP" {
  description = "Private IP address of the EC2 instance in the public subnet"
  value       = aws_instance.ec2_vpc1_public_subnet.private_ip
}

# Private VM Private IP
output "Private-VM-Private-IP" {
  description = "Private IP address of the EC2 instance in private subnet 1"
  value       = aws_instance.ec2_vpc1_private_subnet1.private_ip
}

Note: The Security Group for the EC2 instances does not allow SSH.



Configuration Deployment
#

Initialize Terraform Project
#

This will download and install the AWS Terraform provider defined in the terraform.tf file with “hashicorp/aws”, as well as setting up the configuration files in the project directory.

# Initialize the Terraform project
terraform init

Validate Configuration Files
#

# Validates the syntax and structure of Terraform configuration files
terraform validate

# Shell output:
Success! The configuration is valid.

Plan the Deployment
#

# Dry run / preview changes before applying them
terraform plan

Apply the Configuration
#

# Create network stack
terraform apply -auto-approve

# Shell output:
Apply complete! Resources: 16 added, 0 changed, 0 destroyed.

Outputs:

Private-VM-Private-IP = "10.10.1.29"
Public-VM-Private-IP = "10.10.0.51"
Public-VM-Public-IP = "184.73.65.54"



Session Manager
#

AWS Management Console
#

Links:

# Systems Manager
https://console.aws.amazon.com/systems-manager/home?region=us-east-1

# Session Manager
https://console.aws.amazon.com/systems-manager/session-manager?region=us-east-1

  • Open the AWS Systems Manager Session Manger

  • Click “Start Session”

  • Select on of the EC2 instances

  • Click “Start Session”

  • Click “Terminate” to end the session
  • Verify the Session Manager user and internet connectivity
# Open home directory
cd

# List current directory / very SSM user
pwd

# Shell output:
/home/ssm-user

AWS CLI
#

Install Session Manager Plugin
#

# Download the "Deb" package
cd /tmp &&
curl "https://s3.amazonaws.com/session-manager-downloads/plugin/latest/ubuntu_64bit/session-manager-plugin.deb" -o "session-manager-plugin.deb"

# Install the Session Manager plugin
sudo dpkg -i session-manager-plugin.deb
# Verify the installation / list version
session-manager-plugin --version

# Shell output:
1.2.694.0

Start a Session
#

#  start a Session Manager session to a EC2 instance: Syntax
aws ssm start-session --target <INSTANCE_ID>
#  start a Session Manager session to a EC2 instance: Example
aws ssm start-session --target i-0120a081183a4585d

# Shell output:
Starting session with SessionId: terraform-rustf3npgnixqf2beck82glbeq
# Exit the session
exit

# Shell output:
Exiting session with sessionId: terraform-rustf3npgnixqf2beck82glbeq.



Links #

# Session Manager Plugin
https://docs.aws.amazon.com/systems-manager/latest/userguide/install-plugin-debian-and-ubuntu.html