Daniel López Azaña

Theme

Social Media

Blog

GNU/Linux, Open Source, Cloud Computing, DevOps and more...

How to automatically update all your AWS EC2 security groups when your dynamic IP changes

AWS security groups

One of the biggest annoyances when working with AWS and your Internet connection has a dynamic IP is that when it changes, you immediately stop accessing to all servers and services protected by an EC2 security group whose rules only allow traffic to certain specific IP’s instead of allowing open connections to everyone (0.0.0.0.0/0).

Certainly the simplest thing to do is always allowing traffic on a given port to everyone, so that even if you have a dynamic IP on your Internet connection you will always be able to continue accessing even if it changes. But opening traffic on a port to everyone is not the right way to proceed from a security point of view , because then any attacker will be able to access that port without restrictions, and that is not what you want.

A much safer alternative is to restrict traffic to a certain port (for example SSH TCP/22 or RDP TCP/3389) only to your own public IP , because then you can be sure that only you will have access to that port (without considering of course IP spoofing attacks and others, from which on the other hand Amazon already protects you).

But of course, if your IP is dynamic and changes frequently, it is a big nuisance going over and over again editing one by one the different security groups in your infrastructure to update your IP. If, as in my case, you have multiple clients, each with numerous security groups in different geographic regions, then this task will be a considerable waste of time that you will incur over and over again.

Of course, it would be optimal if your infrastructure did not have servers with open ports to the outside. Ideally, access should be provided through a jump box (also known as a bastion host) that can only be accessed using 2FA or MFA, all services should be protected on internal or private subnets without Internet access, and access to necessarily open ports such as 80/443 should only be provided through load balancers properly protected with a WAF and ACL rules and other protection services such as AWS Shield, etc.

But these are architectural issues that go beyond the scope of this article. The fact is that in practice, many companies continue protecting access to their servers and services by only relying on EC2 security groups. So, this task of updating my own source public IP needs to be performed over and over again at least until I help them to improve their infrastructure and security.

To avoid having to do this task by hand I wrote the following bash script that updates all security groups in all regions of an AWS account:

#! /bin/bash

#set -x

# This script finds all the security groups that have an IP range that meets the condition of affecting the defined ports and whose description has
# a given keyword, and then replace that IP range by the one defined at the beginning of the script or the one obtained as the current public IP of
# our Internet connection, so that we can continue accessing all servers and services protected by those security groups when our dynamic IP changes.

regions='eu-west-1 eu-central-1 us-east-1'
description_keyword='daniloaz'
ports="22 3389"

# Get current public IP from opendns.com service
my_public_ip="$(dig +short myip.opendns.com @resolver1.opendns.com)/32"
if [ $? -ne 0 ];then
    echo "ERROR: couldn't get current public IP from opendns.com service! Aborting!"
    exit 2
fi

# Check if ~/.my_public_ip file already exists, otherwise create it
if [ ! -f ~/.my_public_ip ];then
    echo "WARNING: file .my_public_ip doesn't exist! Creating a new one..."
    echo "${my_public_ip}" > ~/.my_public_ip
    update_security_groups=1
else
    # Check if public ip changed
    my_old_public_ip="$(cat ~/.my_public_ip)"
    if [ "${my_old_public_ip}" != "${my_public_ip}" ];then
        update_security_groups=1
        echo "WARNING: current public IP ${my_public_ip} is different from old public IP ${old_public_ip}!"
    else
        update_security_groups=0
        echo "INFO: current public IP ${my_public_ip} didn't change. Exiting..."
        exit 0
    fi
fi

echo "INFO: updating security groups..."

for region in ${regions};do
  for port in ${ports};do
    # Get all security groups that give access to given port
    security_group_ids=$(/usr/bin/aws ec2 describe-security-groups --region "${region}" --filters Name=ip-permission.to-port,Values=${port} \
                                                                   --query "SecurityGroups[*].[GroupId]" --output text)

    for security_group_id in ${security_group_ids};do
      # Get existing IP range that match our keyword within the security group IP permissions description
      my_old_public_ip=$(/usr/bin/aws ec2 describe-security-groups --region "${region}" --group-ids "${security_group_id}" \
                       | jq -c '.SecurityGroups[].IpPermissions[] | select(.FromPort=='${port}' and .ToPort=='${port}') | .IpRanges[] | select(.Description=="'${description_keyword}'") | .CidrIp' | sed 's/"//g')
      if [ $? -eq 0 ] && [ ! -z "${my_old_public_ip}" ];then
        # Update IP range: first remove old IP range and then create new range with new public IP
        /usr/bin/aws ec2 revoke-security-group-ingress --region "${region}" --group-id "${security_group_id}" --protocol tcp --port ${port} \
                                                       --cidr "${my_old_public_ip}" && \
        /usr/bin/aws ec2 authorize-security-group-ingress --region "${region}" --group-id "${security_group_id}" --ip-permissions \
            "IpProtocol=tcp,FromPort=${port},ToPort=${port},IpRanges=[{CidrIp=${my_public_ip},Description=${description_keyword}}]"
        if [ $? -eq 0 ];then
          echo "OK: ${region} | ${security_group_id} -> replaced previous ${my_old_public_ip} IP range with current ${my_public_ip} public IP on security group $security_group_id for port ${port}"
        else
          echo "ERROR: couldn't replace previous ${my_old_public_ip} IP range with current ${my_public_ip} public IP on security group $security_group_id (${region}) for port ${port}!"
        fi
      fi
    done
  done
done

This script can be run via cron every minute to quickly update security groups as soon as you public IP changes.

It goes without saying that you need to have the aws command correctly configured on your computer to be able to run the script. Apart from that, the only requirement is to have the jq tool to better handle the JSON returned by the aws command. You can install it by simply running apt install jq or yum install jq , as it is included in the repositories of all Linux distributions.

I hope it helps you save time and make less mistakes!

Daniel López Azaña

About the author

Daniel López Azaña

Tech entrepreneur and cloud architect with over 20 years of experience transforming infrastructures and automating processes.

Specialist in AI/LLM integration, Rust and Python development, and AWS & GCP architecture. Restless mind, idea generator, and passionate about technological innovation and AI.

Related articles

Script to automatically change all gp2 volumes to gp3 with aws-cli

Script to automatically change all gp2 volumes to gp3 with aws-cli

Last December Amazon announced its new EBS gp3 volumes, which offer better performance and a cost saving of 20% compared to those that have been used until now (gp2). Well, after successfully testing these new volumes with multiple clients, I can do nothing but recommend their use, because they are all advantages and in these 2 and a half months that have passed since the announcement I have not noticed any problems or side effects.

February 16, 2021
terraform-and-route53-logos

How to quickly import all records from a Route53 DNS zone into Terraform

The terraform import command allows you to import into HashiCorp Terraform resources that already existed previously in the provider we are working with, in this case AWS. However, it only allows you to import those records one by one, with one run of terraform import at a time. This, apart from being extremely tedious, in some situations becomes impractical. This is the case for the records of a Route53 DNS zone. The task can become unmanageable if we have multiple DNS zones, each one with tens or hundreds of records. In this article I offer you a bash script that will allow you to import in Terraform all the records of a Route53 DNS zone in a matter of seconds or a few minutes.

February 8, 2022
Logo AWS EBS

How to enlarge the size of an EBS volume in AWS and extend an ext4 partition

When we completely fill up an ext4 filesystem mounted on a partition hosted in an EBS volume of Amazon Web Services and we can not do anything to free space because we do not want to lose any of the stored data, the only solution is to grow up the volume and extend the associated partition up to 100% of its capacity to obtain free space again.We start in our example with a 50 GB volume full to 100%. We want to extend it to double the size, 100 GB:

May 23, 2017

Comments

Naveen July 12, 2021
Hi, This works when we configure in our machine. But, Can we modify the existing individual rules in security groups? Say we have rules for multiple users in ABC security group every users public IP dynamically changes so using Jenkins job we should allow users to input their Changed IP and update. How can we achieve this?
Daniel July 12, 2021
Yes, it's possible by adapting the script to update rules based on specific keywords and passing users Ip addresses to script as command line arguments or configuration file.
Naveen July 12, 2021
Hi Daniel, Can you please help me to modify example script for this scenario , As i am not much popular with scripting help here would be much appreciated!

Submit comment