GNU/Linux, Open Source, Cloud Computing, DevOps y más...

Cómo actualizar automáticamente todos nuestros grupos de seguridad EC2 de AWS cuando nuestra IP dinámica cambia

No hay comentarios

Uno de los mayores fastidios cuando trabajamos con AWS y nuestra conexión a Internet tiene IP dinámica es que cuándo ésta cambia, automáticamente dejamos de tener acceso a todos los servidores y servicios que habíamos protegido mediante un grupo de seguridad EC2 cuyas reglas sólo permiten el tráfico a ciertas IP’s específicas en lugar de abrir las conexiones a todo el mundo (0.0.0.0/0).

Ciertamente lo más sencillo es siempre indicar en el grupo de seguridad que permitimos el tráfico en un puerto a todo el mundo, de modo que aunque tengamos IP dinámica en nuestra conexión a Internet siempre podremos continuar accediendo aunque ésta cambie. Pero abrir el tráfico a un puerto a todo el mundo no es la forma correcta de proceder desde el punto de vista de la seguridad, pues entonces cualquier atacante podrá tener acceso a ese puerto sin restricciones, y eso no es lo que queremos.

Una alternativa que se plantea mucho más segura consiste en restringir el tráfico a un determinado puerto (por ejemplo el TCP/22 de SSH o el TCP/3389 de RDP) únicamente a nuestra propia IP pública, pues entonces nos aseguramos de que sólo nosotros tendremos acceso a dicho puerto (obviando claro está ataques de tipo IP spoofing y demás, de los que ya se encarga de protegernos por otro lado Amazon).

Pero claro, si nuestra IP es dinámica y cambia con frecuencia, es un verdadero fastidio tener que ir una y otra vez editando uno por uno los distintos grupos de seguridad que tenemos definidos en nuestra infraestructura para actualizar nuestra IP. Si como además es mi caso tenemos múltiples clientes distintos, cada uno con numerosos grupos de seguridad en distintas regiones geográficas, pues al final esta tarea constituirá una considerable pérdida de tiempo en la que incurriremos una y otra vez.

Por supuesto, lo óptimo sería que nuestra infraestructura no tuviera servidores con puertos abiertos al exterior. Lo ideal sería canalizar el acceso a través de una máquina de salto (también conocida como jumpbox o bastion host) a la que además sólo se pudiera acceder mediante 2FA o MFA, que todos los servicios quedaran protegidos en subredes internas o privadas sin acceso a Internet y el acceso se brindara únicamente a través de balanceadores de carga debidamente protegido con un WAF y reglas ACL en el caso de aplicaciones web y otros servicios de protección como AWS Shield, etc.

Pero bueno, eso ya son cuestiones de arquitectura que van más allá del ámbito de este artículo. El caso es que en la práctica, muchas empresas siguen basando la protección del acceso a sus servidores y servicios únicamente en los grupos de seguridad EC2 y que esta tarea es necesario realizarla una y otra vez de forma transitoria hasta que les ayudo a mejorar su infraestructura y sus medidas de protección.

Para ello he escrito el siguiente script en bash que se encarga de realizar esta actualización en todos los grupos de seguridad de todas las regiones de una cuenta AWS:

#! /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 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

Este script puede ejecutarse via cron una vez por minuto para actualizar rápidamente nuestros grupos de seguridad tan pronto cambie nuestra IP pública.

Ni que decir tiene que es necesario tener correctamente configurado el comando aws en nuestro equipo para poder ejecutar el script. Aparte de eso, el único requisito es disponer de la herramienta jq para manejar mejor el JSON que devuelve el comando aws. La podemos instalar ejecutando simplemente un apt install jq o yum install jq, pues viene incluida en los repositorios de todas las distribuciones Linux.

¡Espero que os ayude a ahorrar tiempo y cometer menos errores!

 

Sobre el autor

Daniel López Azaña
Arquitecto de soluciones Cloud AWS & Linux Sysadmin Freelance

Emprendedor, generador de ideas y mente inquieta. Apasionado de las nuevas tecnologías, especialmente de los sistemas Linux y del software libre. Me gusta escribir además sobre actualidad tecnológica, Cloud Computing, AWSi, DevOps, DevSecOps, seguridad, desarrollo web y programación, SEO, ciencia, innovación, emprendimiento, etc.

DanielCómo actualizar automáticamente todos nuestros grupos de seguridad EC2 de AWS cuando nuestra IP dinámica cambia

Artículos relacionados

Deja una respuesta

Tu dirección de correo electrónico no será publicada.