CLOUD 16 min

RDS Privado en AWS: VPC, SSM Session Manager y Bastión

Aprende a diseñar una red segura (VPC) en AWS. Implementaremos subredes públicas y privadas, un host bastión EC2 y una base de datos RDS MySQL aislada e inaccesible desde Internet.

Escrito por Samir El Azem Crovetto • Senior Backend Engineer & DevSecOps

El Riesgo Crítico de la Exposición Pública en Bases de Datos Cloud

En el aprovisionamiento de arquitecturas en la nube pública como Amazon Web Services (AWS), uno de los errores operativos más graves consiste en habilitar la opción Publicly Accessible = true en instancias de bases de datos relacionales gestionadas como Amazon RDS o Amazon Aurora. Esta configuración asigna una dirección IPv4 pública directamente a la interfaz de red elástica (ENI) del motor de base de datos, exponiendo puertos críticos como 3306 (MySQL/MariaDB) o 5432 (PostgreSQL) a todo el espacio de direcciones de Internet.

Motores de búsqueda de infraestructura como Shodan, Censys y Shadowserver monitorizan continuamente el espacio IPv4 global, detectando instancias de bases de datos expuestas en cuestión de minutos. Los riesgos inmediatos incluyen:

  • Ataques de fuerza bruta y credential stuffing: Bots automatizados ejecutan miles de intentos de autenticación por segundo contra usuarios administrativos como root o admin.
  • Explotación de vulnerabilidades Zero-Day o N-Day: Cualquier vulnerabilidad en el protocolo de enlace o en el motor de base de datos puede ser explotada directamente sin pasar por capas intermedias de inspección.
  • Denegación de servicio (DoS) a nivel de conexión: Agotamiento de la tabla de conexiones (max_connections) mediante floods TCP SYN dirigidos al puerto de la base de datos, degradando los servicios legítimos.
  • Riesgo regulatorio y de cumplimiento: Marcos normativos como PCI-DSS v4.0, ENS (Esquema Nacional de Seguridad), ISO/IEC 27001 y RGPD exigen explícitamente el aislamiento de datos confidenciales en segmentos de red no accesibles públicamente.

El principio de defensa en profundidad exige que los almacenes de datos residan en subredes privadas aisladas, sin rutas hacia pasarelas de Internet (Internet Gateway), y que cualquier acceso administrativo o de mantenimiento se canalice mediante controles de identidad estrictos y bastiones auditados.


Arquitectura de Red Segura: Topología VPC Multi-Subred

Una Virtual Private Cloud (VPC) bien estructurada separa las cargas de trabajo en al menos tres capas de red jerárquicas distribuidas en múltiples Zonas de Disponibilidad (Multi-AZ) para garantizar resiliencia y contención:

  1. Capa Pública (Public Subnets): Contiene componentes que deben recibir tráfico directo desde Internet, como Application Load Balancers (ALB) o pasarelas NAT (NAT Gateway). Las tablas de enrutamiento de estas subredes apuntan 0.0.0.0/0 hacia un Internet Gateway (IGW).
  2. Capa de Aplicación Privada (Private App Subnets): Aloja contenedores ECS/EKS, microservicios o instancias EC2 backend. No disponen de IPs públicas; el tráfico saliente hacia Internet (por ejemplo, para descargar dependencias o invocar APIs de terceros) se realiza exclusivamente a través de una NAT Gateway situada en la subred pública.
  3. Capa de Datos Aislada (Isolated DB Subnets): Subredes privadas reservadas para Amazon RDS. Sus tablas de enrutamiento no contienen ninguna ruta hacia 0.0.0.0/0 (ni IGW ni NAT Gateway). De este modo, la base de datos no puede recibir tráfico de Internet ni iniciar conexiones salientes, mitigando ataques de exfiltración directa de datos.
graph TD
    subgraph Internet ["🌐 Internet Público"]
        Client["Dispositivo / Operador Remoto"]
        Attacker["⚠️ Escáneres / Shodan"]
    end

    subgraph AWS_Cloud ["☁️ AWS Cloud - VPC (10.0.0.0/16)"]
        IGW["Internet Gateway (IGW)"]

        subgraph Public_Subnet ["Subred Pública (10.0.1.0/24)"]
            NAT["NAT Gateway"]
            ALB["Application Load Balancer"]
        end

        subgraph Private_App_Subnet ["Subred Privada App (10.0.10.0/24)"]
            App["Microservicios / Backend"]
        end

        subgraph Private_DB_Subnet ["Subred Aislada DB (10.0.20.0/24) - Sin Salida Internet"]
            RDS_Primary["Amazon RDS Primaria (3306)"]
            RDS_Standby["Amazon RDS Standby (Multi-AZ)"]
        end

        subgraph Bastion_Subnet ["Subred Privada Gestión (10.0.15.0/24)"]
            SSM_Bastion["EC2 Bastion (SSM Agent) - 0 Puertos Abiertos"]
        end
    end

    Attacker -.->|Bloqueado: Sin IP Pública| RDS_Primary
    Client -->|AWS IAM + SSM Session Manager| SSM_Bastion
    SSM_Bastion -->|TCP 3306 - SG Referenciado| RDS_Primary
    ALB -->|HTTP/gRPC| App
    App -->|TCP 3306| RDS_Primary
    RDS_Primary -.->|Replicación Síncrona| RDS_Standby

    classDef public fill:#e1f5fe,stroke:#0288d1,stroke-width:2px;
    classDef private fill:#e8f5e9,stroke:#388e3c,stroke-width:2px;
    classDef isolated fill:#fff3e0,stroke:#f57c00,stroke-width:2px;
    classDef threat fill:#ffebee,stroke:#d32f2f,stroke-width:2px;

    class Client,ALB,NAT,IGW public;
    class App,SSM_Bastion private;
    class RDS_Primary,RDS_Standby isolated;
    class Attacker threat;

Configuración de Security Groups con Referencias Cruzadas

El firewall virtual a nivel de interfaz de red en AWS son los Security Groups (SG), que operan con estado (stateful). Un error habitual es permitir el acceso a la base de datos mediante rangos CIDR amplios (por ejemplo, 10.0.0.0/16).

La mejor práctica consiste en utilizar Security Group Referencing (referencia cruzada de identificadores de grupo de seguridad). De esta forma, el Security Group de la base de datos no acepta tráfico de ninguna dirección IP específica, sino únicamente de interfaces de red asociadas explícitamente a un Security Group emisor autorizado:

1. Security Group de la Aplicación (sg-app-backend)

  • Reglas de Entrada (Inbound): Puerto 8080/443 desde el ALB (sg-alb).
  • Reglas de Salida (Outbound): TCP 3306 hacia el SG de la base de datos (sg-rds-mysql).

2. Security Group del Bastión SSM (sg-bastion-ssm)

  • Reglas de Entrada (Inbound): Ninguna regla de entrada (0 puertos abiertos, ni siquiera el puerto 22 de SSH).
  • Reglas de Salida (Outbound): TCP 3306 hacia sg-rds-mysql y HTTPS 443 hacia los endpoints de AWS Systems Manager.

3. Security Group de Amazon RDS (sg-rds-mysql)

  • Reglas de Entrada (Inbound):
  • Tipo: MySQL/Aurora (TCP 3306) | Origen: sg-app-backend
  • Tipo: MySQL/Aurora (TCP 3306) | Origen: sg-bastion-ssm
  • Reglas de Salida (Outbound): Ninguna regla de salida requerida (las respuestas TCP a las conexiones establecidas son permitidas automáticamente por el mecanismo stateful).

La Evolución del Bastión: De SSH Tradicional a AWS Systems Manager (SSM)

Históricamente, el acceso a recursos dentro de subredes privadas se resolvía desplegando un servidor bastión (jump host) con una IP pública y el puerto 22 abierto al mundo o a la IP de la oficina, obligando a gestionar claves criptográficas SSH locales y enfrentándose a ataques de fuerza bruta continuos en el puerto 22, como vimos en la guía sobre hardening de un servidor SSH.

AWS Systems Manager (SSM) Session Manager elimina por completo la necesidad de exponer puertos o gestionar claves SSH:

Característica Bastión SSH Tradicional Bastión con AWS SSM Session Manager
Puertos de Entrada Abiertos Requiere TCP 22 abierto 0 puertos de entrada abiertos
Dirección IP Pública Obligatoria en el host bastión No requerida (puede residir en subred privada)
Gestión de Credenciales Claves privadas locales .pem / id_rsa Credenciales efímeras federadas vía AWS IAM
Auditoría y Trazabilidad Logs locales auth.log volátiles Registro completo de sesiones en AWS CloudTrail y S3/CloudWatch
Control de Acceso Claves compartidas o gestión de usuarios en Linux Políticas IAM granulares con autenticación MFA obligatoria
sequenceDiagram
    autonumber
    actor Dev as Ingeniero / DBA
    participant CLI as AWS CLI + Session Manager Plugin
    participant IAM as AWS IAM (MFA)
    participant SSM as AWS Systems Manager Service
    participant Bastion as EC2 Bastion (SSM Agent)
    participant RDS as Amazon RDS Privado (3306)

    Dev->>CLI: aws ssm start-session (Port Forwarding)
    CLI->>IAM: Autenticar credenciales temporales + MFA
    IAM-->>CLI: Token de autorización STS
    CLI->>SSM: Solicitar túnel WebSocket cifrado TLS hacia Instancia
    SSM->>Bastion: Comando vía canal saliente persistente (Outbound HTTPS)
    Bastion->>RDS: Abrir socket TCP 3306 local en VPC
    SSM-->>CLI: Túnel establecido en localhost:3307
    Dev->>CLI: Conectar DBeaver/MySQL Client a 127.0.0.1:3307
    CLI->>Bastion: Tráfico SQL encapsulado en WebSocket
    Bastion->>RDS: Consultas SQL reenviadas en la red interna
    RDS-->>Bastion: Respuesta de datos relacionales
    Bastion-->>Dev: Respuesta transferida de forma segura

Implementación Paso a Paso: Túnel Seguro hacia RDS sin Abrir Puertos

A continuación se detalla la configuración técnica para habilitar port forwarding transparente hacia Amazon RDS a través de SSM:

Paso 1: Rol IAM para la Instancia Bastión

La instancia EC2 bastión debe tener asociado un rol de IAM con la política gestionada AmazonSSMManagedInstanceCore. Esto permite que el agente amazon-ssm-agent preinstalado en Amazon Linux 2023 establezca comunicación saliente con el servicio SSM:

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

Paso 2: Creación de la Instancia EC2 Bastión en Subred Privada

Se despliega una instancia de bajo coste (por ejemplo, t4g.nano o t4g.micro con arquitectura Graviton) en una subred privada que disponga de salida a Internet mediante NAT Gateway (o con endpoints de VPC para ssm, ssmmessages y ec2messages si se requiere aislamiento total sin NAT).

Paso 3: Inicio de Sesión de Port Forwarding Remoto

Desde el equipo de desarrollo local, con AWS CLI y el plugin de Session Manager instalados, se ejecuta el siguiente comando:

aws ssm start-session \
    --region eu-west-1 \
    --target i-0a1b2c3d4e5f67890 \
    --document-name AWS-StartPortForwardingSessionToRemoteHost \
    --parameters '{
        "host":["production-db.c1234567890.eu-west-1.rds.amazonaws.com"],
        "portNumber":["3306"],
        "localPortNumber":["3307"]
    }'

La consola confirmará el inicio del túnel:

Starting session with SessionId: samir-0987654321fedcba
Port 3307 opened for host production-db.c1234567890.eu-west-1.rds.amazonaws.com:3306.
Waiting for connections...

Ahora, cualquier cliente SQL (DBeaver, DataGrip, MySQL Workbench o la CLI de mysql) puede conectarse a 127.0.0.1:3307, y el tráfico viajará de forma segura por el túnel cifrado de AWS hasta la base de datos privada.


Hardening de Datos en RDS: Cifrado en Reposo, TLS y Autenticación IAM

El aislamiento perimetral de red debe complementarse con el hardening del propio motor de base de datos, en línea con los patrones vistos en el despliegue en producción de MySQL con contenedores:

1. Cifrado en Reposo con AWS KMS

Al crear la instancia RDS, debe seleccionarse el cifrado con una clave gestionada por el cliente (KMS CMK - Customer Managed Key), en lugar de la clave por defecto de AWS. Esto garantiza:

  • Cifrado transparente del almacenamiento subyacente (EBS).
  • Cifrado de snapshots automatizados y réplicas de lectura.
  • Capacidad de revocar el acceso a la clave KMS de forma inmediata ante sospecha de compromiso de credenciales.

2. Imposición de Conexiones TLS en Tránsito

Por defecto, muchos motores MySQL permiten conexiones en texto plano si el cliente no solicita SSL. Es imperativo forzar el cifrado TLS creando un DB Parameter Group personalizado y aplicando:

# Forzar cifrado obligatorio para cualquier conexión
require_secure_transport = ON

# Restringir protocolos obsoletos
tls_version = TLSv1.3,TLSv1.2

Tras asociar el Parameter Group a la instancia RDS y aplicar los cambios (algunos parámetros requieren reinicio), cualquier intento de conexión en texto plano será rechazado por el servidor con el error ERROR 3159 (HY000): Connections using insecure transport are prohibited while --requiresecuretransport=ON.

3. Autenticación IAM en Base de Datos

En lugar de almacenar usuarios y contraseñas estáticas en ficheros de configuración o variables de entorno, Amazon RDS soporta IAM Database Authentication. Los microservicios o los desarrolladores obtienen un token de autenticación temporal firmado criptográficamente por AWS STS mediante SigV4, con una vida útil de solo 15 minutos:

# Generar token de autenticación efímero
TOKEN=$(aws rds generate-db-auth-token \
    --hostname production-db.c1234567890.eu-west-1.rds.amazonaws.com \
    --port 3306 \
    --region eu-west-1 \
    --username app_user)

# Conectar pasando el token como contraseña
mysql --host=127.0.0.1 --port=3307 -u app_user --password="$TOKEN" --enable-cleartext-plugin mydb

En la base de datos MySQL, el usuario se configura para autenticar con el plugin AWS:

CREATE USER 'app_user'@'%' IDENTIFIED WITH AWSAuthenticationPlugin AS 'RDS';
GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.* TO 'app_user'@'%';

Esto suprime el riesgo de filtración de credenciales estáticas y permite centralizar el control de accesos en los roles IAM del proveedor cloud, aplicando el principio de mínimo privilegio en armonía con la arquitectura de microservicios corporativa.


Automatización con AWS CLI para Conexiones Diarias

Para simplificar la operativa diaria de los equipos de ingeniería sin comprometer la seguridad ni requerir memorizar comandos complejos, se recomienda estandarizar un script de conexión rápida:

#!/usr/bin/env bash
set -euo pipefail

INSTANCE_ID="i-0a1b2c3d4e5f67890"
RDS_ENDPOINT="production-db.c1234567890.eu-west-1.rds.amazonaws.com"
LOCAL_PORT=3307

echo "Iniciando túnel seguro SSM hacia RDS (${RDS_ENDPOINT}:3306)..."
echo "Conexión local disponible en 127.0.0.1:${LOCAL_PORT}"

aws ssm start-session \
    --target "${INSTANCE_ID}" \
    --document-name AWS-StartPortForwardingSessionToRemoteHost \
    --parameters "{\"host\":[\"${RDS_ENDPOINT}\"],\"portNumber\":[\"3306\"],\"localPortNumber\":[\"${LOCAL_PORT}\"]}"

Conclusión y Checklist de Verificación en Producción

Aprovisionar bases de datos relacionales en la nube no consiste únicamente en seleccionar el tamaño de la instancia de cómputo y el almacenamiento en disco. La resiliencia y la confidencialidad de la información dependen de un diseño de red cerrado por defecto:

  • [x] La instancia RDS está ubicada en subredes privadas aisladas sin rutas hacia Internet Gateway.
  • [x] El parámetro Publicly Accessible está fijado estrictamente en false.
  • [x] Los Security Groups emplean referencias cruzadas y descartan cualquier regla basada en 0.0.0.0/0.
  • [x] El acceso administrativo se canaliza mediante AWS SSM Session Manager, manteniendo 0 puertos de entrada abiertos en el host bastión.
  • [x] El almacenamiento está cifrado mediante claves AWS KMS CMK gestionadas por el cliente.
  • [x] El parámetro requiresecuretransport = ON impide conexiones sin cifrado TLS.
  • [x] Se emplea autenticación IAM para suprimir contraseñas estáticas en entornos de alta sensibilidad.

Si necesitas auditar la arquitectura cloud de tu organización, implementar bastiones Zero Trust o securizar clústeres de contenedores y bases de datos relacionales, puedes consultar mis Servicios de Ciberseguridad y Consultoría o ponerte en contacto conmigo a través de Sobre mí.