Skip to content

Accessing your EC2 instances securely

Cloud-native applications are certainly the way to go to maximize your cloud investment, however, for many organizations, redeveloping their in-house applications to be cloud-native can be a daunting (and expensive) exercise. So in many cases, it makes sense to lift-and-shift the on-prem servers to the cloud to utilize at least some of the benefits of cloud infrastructure.

Having those servers in the cloud is one thing, how do you connect securely to them to perform your support tasks?

Architecture

When you first spin up an EC2 instance, you will most probably be faced with plenty of default options. They're designed in such a way to get you up and running very quickly. Every AWS account comes with a default VPC, with default subnets that will allow internet access. This is not necessarily a bad thing, but if security is your main driver, you would want to create an architecture that hides the backend infrastructure from the front end. Designing a proper architecture is outside the scope of today's blog post, except to say that when you do place an EC2 instance somewhere in your infrastructure, it makes more security sense to have a tiered design where your backend and frontend systems are separated from each other.

Your architecture will also dictate how you can connect to the server remotely. By following the default options, AWS will inadvertently spin up a launch-wizard-x security group that will allow the basic ports to be exposed on the internet (typically 80, 22, 3389). While this is probably ok in a lab setting, the problem is that as soon as this server is created, the admin ports (22,3389) will automatically be exposed to the internet. Keep an eye on your event log. Within a few hours of the server becoming available on the internet, someone will try to connect to it, over, and over again. By simply opening the port on your EC2 instance, you are increasing your attack surface. Now you could argue that you have all sorts of other controls in place (like account lockout, using certificates, etc). Be it as it may – relying only on Identity Access to protect your server does not make good security sense. We have to employ further network controls to reduce the attack surface.

Jump servers

A jump server (or a bastion host) is a specific server that is placed in the environment, typically with a public IP address that will allow your staff to connect to it, and then from there, they can “jump” to any other server in the environment. This design has several key advantages like reducing the number of exposed systems on the internet to just one. It also reduces the number of public IP addresses you need to use since every public IP will also cost you money.

This is also not a perfect design. Like in the previous topic, your jump server will now again be exposed to the internet. Should the jump server be compromised, it's game over. Anyone with even basic access to the jump server will be able to connect and query any other system within the VPC. It also creates a single point of failure, meaning that if the jump server crashes, no one will be able to connect to the environment.

Use a custom Security Group

For the EC2 instances with public IP addresses, how do you reduce the attack surface, limit access to the ports, and only allow your own IP address to connect? One way, is to use a security group. Here's how this works.

  • Create a new security group with no ingress or egress rules
  • Attach this rule to the EC2 instances you'd like to connect to
  • Make sure this policy is attached to the user (you can add this to the role, or group as well) and of course, you need to update the resource ID to the same ID from the security group you created.
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ec2:DescribeSecurityGroups"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:RevokeSecurityGroupIngress",
                "ec2:AuthorizeSecurityGroupIngress"
            ],
            "Resource": [
                "arn:aws:ec2:<region>:<account>:security-group/<security group id>"
            ]
        }
    ]
}

This policy should be relatively straightforward. It will allow you to view all security groups since this is necessary for the script to see what it's dealing with, and then it also needs specific access to the security group to add and remove rules.

With the policy in place, and with the necessary user accounts setup on your AWS CLI, you should be able to execute this Python script. The script will find your external IP address, connect to your AWS account, and add a new rule to the security group, allowing your IP address to the security group. It will also remove any other ingress rules that may exist.

Note that while this reduces the attack surface significantly, it does increase the administration. This solution requires a security group for every user. When you start sharing the same security group with multiple users and attaching the same security group to multiple EC2 instances, you're bound to run into issues when one user removes all rules while another is trying to connect to it.

AWS Session Manager

Provided you have the SSM agent installed on all your EC2 instances, Session Manager allows you to remotely connect to your EC2 instances. There are a number of advantages to doing this, like:

  • There is no need to open port 22 or 3389
  • Access to EC2 is managed through AWS IAM – you can define in the IAM policy who can and who can't access the EC2 instance.

SSM works great for Linux – you click open session, and the web console opens up straight to the Linux console. For Windows, unfortunately, it is not that straightforward. In order to connect to Windows via RDP, you'll need to utilize port forwarding.