By blocking port 22 on your instance attackers can not brute force it or exploit eventual vulnerabilities. But then how do you access it? With some shell scripting, you can allow access specifically from your IP, thwarting attacks against the SSH server.
The usual process is to keep port 22 always open to the world, especially when you have no fixed-IP corporate network, just in case you need to SSH into it. While SSH is quite secure, closing the port prevents the bad guys from even trying to get into. And when you need to get into the instance, you can open port 22 to your specific IP address so that you can do so without friction.
Let’s see what scripting is required to implement this solution!
Here is the full script, you just need to input the
instance-id. Run this before you SSH in normally and it will make sure port 22 is opened for you.
jq to be installed, which you can do usually with a simple
sudo apt install jq.
INSTANCE_ID="..." SG=$(aws ec2 describe-instances --instance-ids $INSTANCE_ID --query "Reservations.Instances.SecurityGroups.GroupId" --no-paginate | jq -r '.') while : ; do MYIP=$(curl -s ifconfig.me) [ -z "$MYIP" ] || break done CIDRS=$(aws ec2 describe-security-groups --group-ids $SG | jq -r '.SecurityGroups.IpPermissions | select(.FromPort == 22 and .ToPort == 22) | .IpRanges.CidrIp') for ip in $CIDRS; do [ "$MYIP/32" != "$ip" ] && aws ec2 revoke-security-group-ingress --group-id $SG --protocol tcp --port 22 --cidr $ip done [ -z $(echo "$CIDRS" | grep "$MYIP/32") ] && aws ec2 authorize-security-group-ingress --group-id $SG --protocol tcp --port 22 --cidr "$MYIP/32"
After you’re finished, you can clean up:
aws ec2 revoke-security-group-ingress --group-id $SG --protocol tcp --port 22 --cidr "$MYIP/32"
Let’s see how each part is working together to keep port 22 secure!
Get the Security Group
The first step is to get the security group ID associated with the instance. This is the string with the format of
sg-xxxxxx. To do this, you need to query the instance, either by the
instance-id or some tags, such as the name of the instance.
To get the security group ID by name, use this:
SG=$(aws ec2 describe-instances --filter "Name=tag:Name,Values=<name>" \ --query "Reservations.Instances.SecurityGroups.GroupId" \ --no-paginate | jq -r '.')
And when you have the
instance-id, use this:
SG=$(aws ec2 describe-instances --instance-ids <instance-id> \ --query "Reservations.Instances.SecurityGroups.GroupId" \ --no-paginate | jq -r '.')
 part gets the first security group (from the first instance, if the query returns multiple). If you have more than one and want to use a different one than the first, you need to filter for that. Fortunately,
jq is more than capable to do so.
After this line is run
SG will hold the security group ID.
Get the current IP
The second task is to get the current external IP of the machine you are connecting from. This may be the IP your device reports or maybe your router supports returning it, but the most reliable solution is to use an external service. There are several such services each with its own level of reliability.
One service that you can use is
ifconfig.me, and I’ll use that in this solution. To get the IP, simply use
curl -s ifconfig.me.
But I’ve realized that in some cases it fails to return the IP, especially when the WiFi is connecting. So I needed to put a check that retries automatically.
while loop, it looks like this:
while : ; do MYIP=$(curl -s ifconfig.me) [ -z "$MYIP" ] || break done
MYIP will hold the current external IP.
Get the allowed IPs
The next step is to query the security group and see what IPs it currently allows:
CIDRS=$(aws ec2 describe-security-groups --group-ids $SG \ | jq -r '.SecurityGroups.IpPermissions | select(.FromPort == 22 and .ToPort == 22) | .IpRanges.CidrIp')
The allowed IPs are associated with port ranges. In this situation I only care about exact matches to port 22 (FromPort == 22 and ToPort == 22), but there can be rules that allow a wider range including port 22. You need to take care of those if you have such rules.
CIDRS will contain the allowed CIDR ranges.
What is a CIDR that is referenced here? It is an address range instead of being a specific one. In our case, the /32 suffix defines a single address.
Remove unnecessary ones
The next step is some housekeeping, as you should remove unneeded rules. These might be remnants of previous runs or rules allowed separately. The only address we need to connect to the instance is “$MYIP/32”, so let’s remove all the others:
for ip in $CIDRS; do [ "$MYIP/32" != "$ip" ] && aws ec2 revoke-security-group-ingress \ --group-id $SG --protocol tcp --port 22 --cidr $ip done
Allow the IP
And finally, make sure to authorize your own IP. In case if it is already present in the $CIDRS then this step is skipped. This can happen when you reconnect to the instance without changing your IP.
[ -z $(echo "$CIDRS" | grep "$MYIP/32") ] && aws ec2 authorize-security-group-ingress \ --group-id $SG --protocol tcp --port 22 --cidr "$MYIP/32"
By this time, everything is set up, and you can SSH into the instance without problems. As housekeeping, you can also remove your address after you’re done with this script:
aws ec2 revoke-security-group-ingress --group-id $SG \ --protocol tcp --port 22 --cidr "$MYIP/32"
While key-based SSH is considered to be secure even if it is open to the world, minimizing the attack surface is a good practice nevertheless. Fortunately, with a minimal amount of scripting you can lock down port 22 on your instance but still be able to SSH into it whenever needed.
Did you know we have a free guide on AWS security basics?
Do you have an AWS account? Of course you do, you've just read an article about AWS. But do you know how to secure it?
As a certified security specialist I'm familiar with most of the security controls AWS offers. I've compiled this guide so you don't have to take a month off to learn all that.
5 quick and easy steps to avoid the rookie mistakes and reduce the risk of costly events down the road.
Download the free guide here: