How to log in to an EC2 instance without opening up ports?

EC2 instances are widely used in various AWS setups as jumpboxes or servers. It's inevitable to regularly log in to the instance to reach infrastructure elements in private subnets or maintain the server. In this post, I'll write about logging in to EC2 instances through Session Manager.

1. Why Session Manager?

It’s widely known (for hackers as well) that port 22 is used to SSH into Linux EC2 instances (and 3389 for Windows RDP). Although changing the default port to something else and taking additional steps increase the level of protection against port attacks, what could be a better protection than not opening any port at all?

This is what Session Manager allows developers and system administrators to do.

Session Manager is part of a service called Systems Manager, which allows developers to manage operational tasks across their AWS infrastructure.

Below I’ll describe how to log in to an EC2 instance without having port 22 (or any other port) open.

2. Conditions

If you decide to follow along, you’ll need to have an EC2 instance running (free-tier t2.micro is fine), have a key pair downloaded on your machine and have the AWS CLI installed. So if you don’t have any running instances, go and quickly launch one using a Linux2 AMI.

3. Let’s do it

First, logging into the instance through Session Manager only works if the EC2 instance has the Systems Manager Agent (SSM) installed.

3.1. SSM

The current AWS AMIs have the SSM installed on them by default, so you don’t have to worry about that now. If you want to use Session Manager on older instances, it’s possible that SSM is not installed. In this case, it needs to be manually sorted out, so follow this tutorial before moving on.

3.2. Give permissions to Session Manager

In AWS, everything is built around permissions, which is a good thing because it increases security. As such, Session Manager has to have permissions to access the EC2 instance - otherwise how could we log in?

So we need to create a role, add it to an instance profile and then attach the instance profile to the instance.

Create the instance profile. An instance profile is sort of a container that holds roles for EC2 instances. Let’s create one called SessionManagerInstanceProfile first with the following CLI command:

aws iam create-instance-profile --instance-profile-name SessionManagerInstanceProfile

Instance profiles (along with roles and policies) are part of IAM, which is one of the most important services in AWS.

Create the role. It’s best practice to create a role with the necessary permissions and attach it to the EC2 instance instead of adding user credentials to it. Roles will use temporary credentials to access the relevant services.

We need to create a trust policy defining which service is allowed to assume the role (it’s EC2), and a permission policy, which contains the permissions (both are in JSON format) for the service.

If the CLI is used, it’s easier to create these policies in the editor, then save them in the project folder and refer to them in the terminal.

The trust policy in a file called trust-policy.json can look like this:

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

The ec2.amazonaws.com in the Service array refers to the service assuming the role, in this case this service is EC2.

The permissions can be placed in a file called session-manager-policy.json:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ssm:UpdateInstanceInformation",
        "ssmmessages:CreateControlChannel",
        "ssmmessages:CreateDataChannel",
        "ssmmessages:OpenControlChannel",
        "ssmmessages:OpenDataChannel"
      ],
      "Resource": "*"
    }
  ]
}

The Action property contains all allowed operations, which I won’t go into detail about, the names tell what they do.

Now the role can be created:

aws iam create-role --role-name SessionManagerAccessRole \\
--assume-role-policy-document file://trust-policy.json

It’s straightforward; we simply pair up the role and the trust policy here.

Let’s add the policy to the role:

aws iam put-role-policy --role-name SessionManagerAccessRole --policy-name SessionManagerPolicy \\
--policy-document file://session-manager-policy.json

There’s also not much to explain here.

Add the role to the instance profile. Now that the role is created, let’s add it to the instance profile:

aws iam add-role-to-instance-profile --role-name SessionManagerAccessRole \\
--instance-profile-name SessionManagerInstanceProfile

Add the instance profile to the instance. Finally, the instance profile needs to be added to the EC2 instance:

aws ec2 associate-iam-instance-profile --instance-id i-0a62992c8638f8cc5 \\
--iam-instance-profile Name=SessionManagerInstanceProfile

The instance-id will of course be different for everyone. It can be fetched using the aws ec2 describe-instances command.

Restart the ssm-agent (optional). Once everything is done, it might be necessary to restart the ssm-agent inside the instance so that Session Manager can see it.

If this is the case, you need to SSH into the instance (so port 22 needs to be open in the security group), and check if the ssm-agent is running (for instances created from the Linux2 AMI):

sudo systemctl status amazon-ssm-agent

Then, you need to restart the service:

sudo systemctl restart amazon-ssm-agent

To SSH into the EC2 instance, go to the EC2 section of the Console, select the running instance, and click Connect, and run that command in the terminal.

Alternatively, grab the public IP-address for the instance using the aws ec2 describe-instances command, and run the following instruction:

ssh -i ~/.ssh/NAME_OF_THE_PEM_FILE.pem ec2-user@10.20.30.40

where it’s assumed that the .pem file is located in the .ssh folder, it has 400 permissions and the public IP-address of the instance is 10.20.30.40. If the .pem file has permissions other than 400, it needs to be changed:

chmod 400 NAME_OF_THE_PEM_FILE.pem

3.3. Log in to the instance

Now it’s safe to remove port 22 from the security group, and the Session Manager can be used to log in to the instance:

aws ec2 revoke-security-group-ingress --group-id sg-084e99399003bfa44 --protocol tcp \\
--port 22 --cidr 10.20.30.40/32

Logging in the instance can be done via the Console or CLI.

If you decide to do it from the Console, go to Systems Manager, then select Session Manager, and Start session. The instance should show in the Console by now.

Connecting to the instance through the CLI is more fun though! We can use the CLI with the session manager plugin. Follow the instructions to get the plugin if the AWS CLI has already been installed on your machine.

We can now log in to the instance! Type the following command in the terminal and then be amazed:

aws ssm start-session --target i-0a62992c8638f8cc5

Let’s quickly discuss what target means before the wow! feeling spreads in your body. You need to enter the id of the EC2 instance here which you want to log in to. This id starts with i, and it can be found by looking at the response to the aws ec2 describe-instances command.

You can wow! now and do anything you would normally do when you SSH in the instance.

4. Clean-up

To avoid any unnecessary costs, it’s a good idea to clean up and delete the resources that have been created.

First, let’s terminate the EC2 instance:

aws ec2 terminate-instances --instance-ids i-0a62992c8638f8cc5

Next, delete all IAM-related resources. They are dependent on each other, so the order matters, otherwise you won’t be able to delete them (AWS doesn’t let account users delete anything that has a dependency).

Remove the role from the instance profile:

aws iam remove-role-from-instance-profile --instance-profile-name SessionManagerInstanceProfile \\
--role-name SessionManagerAccessRole

Then, delete the instance profile:

aws iam delete-instance-profile --instance-profile-name SessionManagerInstanceProfile

Remove the inline policy from the role:

aws iam delete-role-policy --role-name SessionManagerAccessRole --policy-name SessionManagerPolicy

Finally, delete the role itself:

aws iam delete-role --role-name SessionManagerAccessRole

All done!

5. Summary

Securing the port which developers and administrators log in to EC2 instances on is an important and still often overlooked task. It’s even better if no ports are open because this completely eliminates the chance of any port attacks.

Session Manager makes it possible to log in to the instance without opening port 22 (or 3389). To use the Session Manager, the Systems Manager Agent needs to be installed on the instance and the relevant permissions should be assigned to it.

Thanks for reading and see you next time.