Securely Access Your EC2 Instances with AWS Systems Manager SSM and VPC Endpoints
Tags: aws, ec2, terraform, awscommunitybuilders
Published: 2023-03-29 15:00:19

- [Overview](#overview)
- [Background Knowledge](#background-knowledge)
  - [_What is SSH-Less Login?_](#what-is-ssh-less-login)
  - [_What is AWS Systems Manager (SSM)?_](#what-is-aws-systems-manager-ssm)
  - [_How to Use SSM for SSH-Less Login?_](#how-to-use-ssm-for-ssh-less-login)
- [Terraform code](#terraform-code)
- [Documentation Links](#documentation-links)
- [Conclusion](#conclusion)

## Overview

As more and more organizations adopt cloud computing, managing resources on cloud platforms like Amazon Web Services (AWS) becomes increasingly important. The need to manage multiple instances of Amazon Elastic Compute Cloud (EC2) instances effectively has led to the development of various tools to simplify the process. One such tool is the **AWS Systems Manager (SSM)**, which enables users to manage EC2 instances, as well as other AWS resources, using a single interface. One of the most powerful features of SSM is the ability to perform **SSH-less login** to EC2 machines, which we will explore in this blog.

## Background Knowledge 

</br>

### _What is SSH-Less Login?_

Traditionally, logging into an EC2 instance involves connecting via SSH with a username and password or a key pair. However, managing SSH keys can be challenging, particularly when dealing with multiple EC2 instances. SSH-Less login, on the other hand, is a secure and more efficient method of accessing EC2 instances without requiring SSH keys.

### _What is AWS Systems Manager (SSM)?_

AWS Systems Manager (SSM) is a management service that enables users to automate the management of their EC2 instances and other AWS resources. SSM enables users to perform various tasks, including software installation, patching, and maintenance across a fleet of EC2 instances. It also provides a single interface to manage EC2 instances running in different regions and accounts.

### _How to Use SSM for SSH-Less Login?_

To use SSM for SSH-less login, follow the steps below:

**Security Group for EC2 Instance**: The minimum traffic you need to allow for SSM access to work is to add an Outbound HTTPS (port 443) in the security group for EC2 instance.

**Create an IAM Role**: To use SSM to log in to EC2 instances, you must first create an IAM role with the required permissions. The role must have the AmazonEC2RoleforSSM policy attached to it, which allows SSM to access the EC2 instances.

**Install SSM Agent**: After creating the IAM role, you need to install the SSM agent on each EC2 instance you want to access using SSM. The SSM agent is pre-installed on Amazon Linux 2 and Amazon Linux AMIs, but you must install it manually on other instances.

**Configure EC2 Instances**: Once the SSM agent is installed, you need to configure your EC2 instances to allow SSM access. You can do this by creating a VPC endpoint for SSM. VPC endpoints which are required when using Private Subnets are below:

1. _com.amazonaws.region.ec2messages_
2. _com.amazonaws.region.ssmmessages_
3. _com.amazonaws.region.ssm_
4. _com.amazonaws.region.kms_ (This is needed if you want to use AWS KMS encryption for Session Manager.)

**Note:**The security group for VPC Endpoints must allow inbound HTTPS (port 443) traffic from the resources in your VPC that communicate with the service.

## Terraform code

- _Let's first start with creating VPC, Public Subnet, Private Subnet, Internet Gateway, Nat Gateway and Route tables_.

Prerequisite - Create provider configuration.

```hcl
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 4.60.0"
    }
  }
}

provider "aws" {
  region = var.region
}
```
- Variable definition can be done like this:

```hcl
# Please set variable region as per your needs.
variable "region" {
  type        = string
  description = "Region for the resource deployment"
  default     = "eu-central-1"
}
```

```hcl
# Create a VPC
resource "aws_vpc" "vpc" {
  cidr_block = "10.0.0.0/16"
  tags = {
    Name = "vpc-${var.region}"
  }
}

# Create an internet gateway
resource "aws_internet_gateway" "gw" {
  vpc_id = aws_vpc.vpc.id
  tags = {
    Name = "igw-${var.region}"
  }
}

# Create a public subnet
resource "aws_subnet" "public_subnet" {
  vpc_id            = aws_vpc.vpc.id
  cidr_block        = "10.0.1.0/24"
  availability_zone = "${var.region}a"
  tags = {
    Name = "Public Subnet"
  }
}

# Create a private subnet
resource "aws_subnet" "private_subnet" {
  vpc_id            = aws_vpc.vpc.id
  cidr_block        = "10.0.2.0/24"
  availability_zone = "${var.region}a"
  tags = {
    Name = "Private Subnet"
  }
}

# Create a NAT gateway
resource "aws_nat_gateway" "nat_gateway" {
  allocation_id = aws_eip.nat_eip.id
  subnet_id     = aws_subnet.public_subnet.id
  tags = {
    Name = "ngw-${var.region}"
  }
}

# Create an EIP for the NAT gateway
resource "aws_eip" "nat_eip" {
  vpc = true
}

# Create a public route table and associate it with the public subnet
resource "aws_route_table" "public_route_table" {
  vpc_id = aws_vpc.vpc.id
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.gw.id
  }
  tags = {
    Name = "Public route table"
  }
}

resource "aws_route_table_association" "public_route_table_association" {
  subnet_id      = aws_subnet.public_subnet.id
  route_table_id = aws_route_table.public_route_table.id
}

# Create a private route table and associate it with the private subnet
resource "aws_route_table" "private_route_table" {
  vpc_id = aws_vpc.vpc.id
  route {
    cidr_block     = "0.0.0.0/0"
    nat_gateway_id = aws_nat_gateway.nat_gateway.id
  }
  tags = {
    Name = "Private route table"
  }
}

resource "aws_route_table_association" "private_route_table_association" {
  subnet_id      = aws_subnet.private_subnet.id
  route_table_id = aws_route_table.private_route_table.id
}
```

- _Let's now create EC2 and Endpoint Security Group_

```hcl
# Create a security group for the EC2 instance
resource "aws_security_group" "instance_security_group" {
  name_prefix = "instance-sg"
  vpc_id      = aws_vpc.vpc.id
  description = "security group for the EC2 instance"

  # Allow outbound HTTPS traffic
  egress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
    description = "Allow HTTPS outbound traffic"
  }

  tags = {
    Name = "EC2 Instance security group"
  }
}

# Security group for VPC Endpoints
resource "aws_security_group" "vpc_endpoint_security_group" {
  name_prefix = "vpc-endpoint-sg"
  vpc_id      = aws_vpc.vpc.id
  description = "security group for VPC Endpoints"

  # Allow inbound HTTPS traffic
  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = [aws_vpc.vpc.cidr_block]
    description = "Allow HTTPS traffic from VPC"
  }

  tags = {
    Name = "VPC Endpoint security group"
  }
}
```

- _Now we can create VPC Endpoints_

```hcl
locals {
  endpoints = {
    "endpoint-ssm" = {
      name = "ssm"
    },
    "endpoint-ssmm-essages" = {
      name = "ssmmessages"
    },
    "endpoint-ec2-messages" = {
      name = "ec2messages"
    }
  }
}

resource "aws_vpc_endpoint" "endpoints" {
  vpc_id            = aws_vpc.vpc.id
  for_each          = local.endpoints
  vpc_endpoint_type = "Interface"
  service_name      = "com.amazonaws.${var.region}.${each.value.name}"
  # Add a security group to the VPC endpoint
  security_group_ids = [aws_security_group.vpc_endpoint_security_group.id]
}

```

- _After creating endpoints, the final components are Instance profile and EC2 instance._

```hcl
# Create IAM role for EC2 instance
resource "aws_iam_role" "ec2_role" {
  name = "EC2_SSM_Role"

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

# Attach AmazonSSMManagedInstanceCore policy to the IAM role
resource "aws_iam_role_policy_attachment" "ec2_role_policy" {
  policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
  role       = aws_iam_role.ec2_role.name
}

# Create an instance profile for the EC2 instance and associate the IAM role
resource "aws_iam_instance_profile" "ec2_instance_profile" {
  name = "EC2_SSM_Instance_Profile"

  roles = [aws_iam_role.ec2_role.name]
}

data "aws_ami" "amazon_linux_2_ssm" {
  most_recent = true

  filter {
    name   = "owner-alias"
    values = ["amazon"]
  }

  filter {
    name   = "name"
    values = ["amzn2-ami-hvm-*-x86_64-ebs"]
  }
}

# Create EC2 instance
resource "aws_instance" "ec2_instance" {
  ami           = data.aws_ami.amazon_linux_2_ssm.id
  instance_type = "t2.micro"
  subnet_id     = aws_subnet.private_subnet.id
  vpc_security_group_ids = [
    aws_security_group.instance_security_group.id,
  ]
  iam_instance_profile = aws_iam_instance_profile.ec2_instance_profile.name
}
```

**Access EC2 Instance using SSM**: After completing the above steps, you can access your EC2 instances using SSM without requiring an SSH key. To do this, navigate to the EC2 console and select the instance you want to access. Then, click on the "Connect" button and select "Session Manager" from the dropdown menu. This will open a web-based shell that allows you to interact with the instances.


## Documentation Links

- [Systems Manager to manage private EC2 instances](https://repost.aws/knowledge-center/ec2-systems-manager-vpc-endpoints)
- [AWS System Managers](https://aws.amazon.com/systems-manager/)

## Conclusion

Using SSM for SSH-less login provides a secure and efficient way to manage multiple EC2 instances without the need for managing SSH keys. SSM makes it easy to perform tasks like software installation, patching, and maintenance across a fleet of EC2 instances using a single interface. With the steps outlined above, you can easily set up SSH-less login for your EC2 instances and enjoy the benefits of streamlined instance management.