¿Por qué usar Secrets Manager en lugar de hardcodear credenciales?

El repositorio es privado, el equipo es pequeño, la fecha de entrega es mañana — y la contraseña de la base de datos termina incrustada directamente en el código fuente. Es una decisión que se toma en segundos y que puede costar semanas de trabajo de remediación cuando ese repositorio se clona en un entorno de CI, se expone en un log de error, o simplemente un ex-colaborador conserva acceso al historial de Git. AWS Secrets Manager existe precisamente para eliminar esta clase de deuda de seguridad antes de que se convierta en un incidente.

TL;DR — Secrets Manager vs. credenciales hardcodeadas

Dimensión Credencial hardcodeada AWS Secrets Manager
Exposición en repositorio Permanente en historial de Git El código nunca contiene la credencial
Rotación de contraseña Manual, propensa a omisión Automática vía Lambda integrada
Auditoría de acceso Imposible rastrear quién usó qué Cada llamada queda en CloudTrail
Control de acceso granular Quien tiene el código, tiene la clave IAM + políticas de recursos por secreto
Propagación de cambios Requiere redespliegue de la app La app obtiene el valor en runtime

Cómo funciona AWS Secrets Manager — antes de actuar

Secrets Manager almacena secretos como pares clave-valor en formato JSON, cifrados con una clave de AWS KMS. Cuando tu aplicación necesita una credencial, realiza una llamada a la API GetSecretValue en runtime — nunca lee un archivo de configuración estático ni una variable de entorno con el valor real incrustado.

El flujo de rotación automática es el componente más importante y el más malentendido. Secrets Manager no rota la contraseña directamente: invoca una función Lambda que tú asocias al secreto. Esa Lambda ejecuta cuatro fases en orden — createSecret, setSecret, testSecret, finishSecret — y solo si cada fase tiene éxito, la nueva versión del secreto queda activa. AWS proporciona funciones Lambda preconfiguradas para motores de base de datos comunes (RDS MySQL, PostgreSQL, entre otros) a través de AWS Serverless Application Repository.

graph LR App["Aplicación"] -->|"GetSecretValue"| SM["Secrets Manager"] SM -->|"Decrypt"| KMS["AWS KMS"] KMS -->|"Valor descifrado"| SM SM -->|"SecretString JSON"| App App -->|"Conecta con credencial"| RDS["Amazon RDS"] Scheduler["Rotación programada"] -->|"Invoca"| Lambda["Lambda de rotación"] Lambda -->|"Nueva contraseña"| RDS Lambda -->|"Registra nueva versión"| SM SM -->|"Promueve AWSCURRENT"| SM
  1. App → GetSecretValue: la aplicación solicita el secreto en tiempo de ejecución. Nunca almacena el valor localmente más allá del ciclo de vida de la petición.
  2. Secrets Manager → KMS Decrypt: el servicio descifra el secreto usando la clave KMS asociada antes de devolverlo.
  3. Rotación programada → Lambda: cuando se cumple el intervalo configurado, Secrets Manager invoca la Lambda de rotación con el contexto del secreto.
  4. Lambda → RDS (nueva contraseña): la Lambda genera una nueva credencial, la aplica en la base de datos y la registra como nueva versión del secreto.
  5. Versión AWSCURRENT actualizada: solo tras validar que la nueva credencial funciona, Secrets Manager promueve esa versión como activa. La versión anterior pasa a AWSPREVIOUS.

¿Por qué 'el repo es privado' no es una defensa válida?

Esta es la suposición incorrecta más común. El argumento asume que la superficie de exposición se limita a quién puede leer el repositorio hoy. En la práctica, las credenciales hardcodeadas se filtran por vectores completamente distintos:

  • Historial de Git: aunque elimines la credencial en un commit posterior, permanece accesible mediante git log o git show en cualquier clon existente.
  • Logs de CI/CD: muchos pipelines imprimen variables de entorno o parámetros de conexión en modo debug. Si la cadena de conexión incluye la contraseña, queda en los artefactos de log.
  • Stack traces y error logs: un error de conexión a base de datos puede serializar la cadena de conexión completa — contraseña incluida — en el log de la aplicación.
  • Acceso de ex-colaboradores: los permisos de repositorio se revocan, pero los clones locales no desaparecen.
Tratar la privacidad del repositorio como control de seguridad es como asumir que nadie mirará debajo del teclado porque la oficina tiene cerradura. El secreto está en el lugar equivocado desde el principio.

La diferencia estructural con Secrets Manager es que el código fuente nunca contiene el valor sensible — solo contiene el nombre del secreto (un identificador no sensible). Incluso si el repositorio queda expuesto, no hay credencial que rotar de emergencia.

Implementación práctica — Secrets Manager paso a paso

Paso 1: Crear el secreto

Antes de modificar la aplicación, el secreto debe existir en Secrets Manager. Este comando crea un secreto con las credenciales de base de datos en formato JSON estándar que la Lambda de rotación de RDS espera encontrar.

aws secretsmanager create-secret \
  --name prod/myapp/db-credentials \
  --description 'Credenciales de base de datos para myapp en produccion' \
  --secret-string '{"username":"dbadmin","password":"InitialPassword123!","engine":"mysql","host":"mydb.cluster-xxxx.us-east-1.rds.amazonaws.com","port":3306,"dbname":"myappdb"}' \
  --region us-east-1

El nombre del secreto (prod/myapp/db-credentials) es lo único que vivirá en tu código fuente o variables de entorno. No contiene información sensible.

Paso 2: Configurar la política IAM para la aplicación

La aplicación necesita permisos para leer el secreto y para que Secrets Manager pueda usar la clave KMS asociada. El principio de mínimo privilegio aquí es crítico: la política debe restringir el acceso al ARN exacto del secreto, no a todos los secretos de la cuenta.

🔽 Ver política IAM completa
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowGetSecretValue",
      "Effect": "Allow",
      "Action": [
        "secretsmanager:GetSecretValue",
        "secretsmanager:DescribeSecret"
      ],
      "Resource": "arn:aws:secretsmanager:us-east-1:123456789012:secret:prod/myapp/db-credentials-*"
    },
    {
      "Sid": "AllowKMSDecrypt",
      "Effect": "Allow",
      "Action": [
        "kms:Decrypt",
        "kms:GenerateDataKey"
      ],
      "Resource": "arn:aws:kms:us-east-1:123456789012:key/YOUR-KMS-KEY-ID"
    }
  ]
}

El sufijo -* en el ARN del secreto es necesario porque Secrets Manager agrega un sufijo aleatorio de 6 caracteres al ARN al momento de la creación. Verificar el ARN exacto con describe-secret antes de finalizar la política.

aws secretsmanager describe-secret \
  --secret-id prod/myapp/db-credentials \
  --region us-east-1 \
  --query 'ARN' \
  --output text

Paso 3: Leer el secreto desde la aplicación

El patrón recomendado es recuperar el secreto una vez al inicio del proceso (o con un caché con TTL corto) y no en cada petición individual. El SDK de AWS gestiona reintentos y throttling automáticamente.

# Ejemplo en Python usando boto3
import boto3
import json

def get_db_credentials(secret_name: str, region: str = 'us-east-1') -> dict:
    client = boto3.client('secretsmanager', region_name=region)
    response = client.get_secret_value(SecretId=secret_name)
    return json.loads(response['SecretString'])

# En el arranque de la aplicacion
creds = get_db_credentials('prod/myapp/db-credentials')
db_host = creds['host']
db_user = creds['username']
db_pass = creds['password']

Nótese que secret_name puede venir de una variable de entorno sin valor sensible — solo es un identificador. Esto es lo que vive en el repositorio.

Paso 4: Habilitar la rotación automática

La rotación requiere que exista una función Lambda de rotación. Para RDS con MySQL o PostgreSQL, AWS proporciona funciones preconfiguradas. Este comando asocia la Lambda al secreto y define el intervalo de rotación.

aws secretsmanager rotate-secret \
  --secret-id prod/myapp/db-credentials \
  --rotation-lambda-arn arn:aws:lambda:us-east-1:123456789012:function:SecretsManagerRDSMySQLRotationSingleUser \
  --rotation-rules AutomaticallyAfterDays=30 \
  --region us-east-1

Tras ejecutar este comando, Secrets Manager inicia una rotación inmediata para validar que la Lambda funciona correctamente. Verificar el estado de la rotación antes de asumir que está operativa.

aws secretsmanager describe-secret \
  --secret-id prod/myapp/db-credentials \
  --region us-east-1 \
  --query '{RotationEnabled:RotationEnabled,LastRotatedDate:LastRotatedDate,NextRotationDate:NextRotationDate}'
stateDiagram-v2 [*] --> createSecret : Secrets Manager invoca Lambda createSecret --> setSecret : Nueva contraseña generada
almacenada como AWSPENDING setSecret --> testSecret : Contraseña aplicada en RDS
ambas versiones válidas testSecret --> finishSecret : Conexión con nueva
credencial verificada testSecret --> Error : Fallo de conexión
AWSCURRENT sin cambios finishSecret --> [*] : AWSPENDING promovido
a AWSCURRENT Error --> [*] : Rotación abortada
secreto anterior activo
  1. createSecret: la Lambda genera una nueva contraseña y la almacena como versión pendiente (AWSPENDING) en Secrets Manager.
  2. setSecret: la Lambda aplica la nueva contraseña en la base de datos RDS. En este punto, ambas contraseñas son válidas simultáneamente.
  3. testSecret: la Lambda verifica que puede conectarse a RDS usando la nueva credencial. Si falla, el proceso se detiene y la versión anterior permanece activa.
  4. finishSecret: Secrets Manager promueve AWSPENDING a AWSCURRENT y degrada la versión anterior a AWSPREVIOUS. La rotación es atómica desde la perspectiva de la aplicación.

El error que nadie documenta — diagnóstico de rotación fallida

La primera vez que configuras rotación automática en un entorno de producción, hay una probabilidad alta de encontrar este síntoma: el secreto existe, la Lambda existe, el comando rotate-secret no devuelve error — pero RotationEnabled aparece como false en describe-secret, o la rotación queda en estado pendiente indefinidamente.

El diagnóstico instintivo es revisar los permisos IAM de la Lambda. Eso está bien, pero no es suficiente. El error real suele estar en dos capas que se ignoran:

Primera capa — la Lambda no puede alcanzar Secrets Manager: si la Lambda está desplegada dentro de una VPC (requisito cuando la base de datos es privada), necesita un VPC endpoint para Secrets Manager o acceso a internet vía NAT Gateway. Sin esto, la fase createSecret falla silenciosamente desde la perspectiva de la consola de Secrets Manager.

# Verificar si existe VPC endpoint para Secrets Manager
aws ec2 describe-vpc-endpoints \
  --filters 'Name=service-name,Values=com.amazonaws.us-east-1.secretsmanager' \
  --query 'VpcEndpoints[*].{ID:VpcEndpointId,State:State,VpcId:VpcId}' \
  --region us-east-1 \
  --output table

Segunda capa — la política de recursos del secreto bloquea a la Lambda: si el secreto tiene una política de recursos adjunta que no incluye explícitamente el rol de la Lambda, el acceso queda denegado aunque la política IAM del rol sea correcta. Una Explicit Deny en la política de recursos del secreto anula cualquier Allow en la política IAM.

# Revisar la politica de recursos del secreto
aws secretsmanager get-resource-policy \
  --secret-id prod/myapp/db-credentials \
  --region us-east-1

Los logs de CloudWatch de la Lambda de rotación son la fuente de verdad. Si la función no aparece en CloudWatch Logs en absoluto, el problema es de red o de permisos de invocación — no de lógica de rotación.

Interacción entre caché de SDK y rotación — señal de profundidad

Hay un comportamiento que no es obvio leyendo la documentación de forma aislada: si tu aplicación cachea las credenciales obtenidas de Secrets Manager durante períodos largos (por ejemplo, reutilizando una conexión de base de datos durante horas), una rotación exitosa puede provocar fallos de autenticación en conexiones existentes aunque el secreto esté correctamente actualizado.

La solución documentada por AWS es usar el cliente de caché de Secrets Manager (aws-secretsmanager-caching para Java y Python), que invalida el caché cuando detecta un error de autenticación y reintenta la obtención del secreto. Sin este patrón, la aplicación puede quedar en un estado donde el secreto en Secrets Manager es correcto, la base de datos acepta la nueva contraseña, pero las conexiones en el pool de la aplicación siguen usando la contraseña anterior hasta que se reciclan.

Configurar el intervalo de rotación con suficiente margen respecto al tiempo de vida máximo de las conexiones del pool mitiga este problema, pero la solución robusta es implementar el cliente de caché con manejo de errores de autenticación.

Verificación de auditoría con CloudTrail

Una de las ventajas operacionales concretas de Secrets Manager es que cada llamada a GetSecretValue queda registrada en CloudTrail con la identidad IAM del caller. Esto permite responder preguntas que con credenciales hardcodeadas son imposibles de responder.

aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=EventName,AttributeValue=GetSecretValue \
  --start-time 2024-01-01T00:00:00Z \
  --end-time 2024-01-02T00:00:00Z \
  --region us-east-1 \
  --query 'Events[*].{Time:EventTime,User:Username,Source:EventSource}' \
  --output table

Este nivel de trazabilidad es el que exigen los marcos de cumplimiento como SOC 2, PCI DSS e ISO 27001 — y es imposible de replicar con credenciales estáticas en código.

Conclusión y próximos pasos con Secrets Manager

Hardcodear credenciales en código fuente no es un atajo técnico — es trasladar el riesgo a un momento futuro donde el costo de remediación es mucho mayor. AWS Secrets Manager elimina la credencial del repositorio, automatiza la rotación y proporciona auditoría de acceso sin cambios arquitecturales significativos en la aplicación.

Los pasos inmediatos recomendados:

  • Auditar el repositorio actual con herramientas como git-secrets o TruffleHog para identificar credenciales existentes en el historial.
  • Migrar las credenciales de base de datos a Secrets Manager siguiendo los pasos de esta guía.
  • Habilitar rotación automática con la Lambda de RDS correspondiente al motor de base de datos.
  • Implementar el cliente de caché de Secrets Manager si la aplicación mantiene pools de conexiones de larga duración.
  • Revisar la documentación oficial: AWS Secrets Manager User Guide.

Glosario de términos clave

Término Definición operacional
Secret version Cada valor almacenado en Secrets Manager tiene versiones etiquetadas. AWSCURRENT es la activa, AWSPREVIOUS la anterior, AWSPENDING la que está siendo rotada.
Rotation Lambda Función Lambda que ejecuta las cuatro fases de rotación. Puede ser provista por AWS (para RDS) o implementada de forma personalizada.
Resource policy Política adjunta directamente al secreto que controla qué principals pueden acceder a él, independientemente de las políticas IAM del caller.
KMS CMK Clave gestionada por el cliente en AWS KMS usada para cifrar el secreto. Si no se especifica, Secrets Manager usa una clave gestionada por AWS.
VPC Endpoint Punto de entrada privado que permite a recursos dentro de una VPC comunicarse con Secrets Manager sin atravesar internet público.

Related Posts

Comentarios

Entradas populares de este blog

EC2 sin acceso a Internet en VPC personalizada: Internet Gateway y Route Table

Actualizar Contenido en CloudFront: Cómo Crear una Invalidación para Limpiar el Caché del Edge

Aumentar el Timeout de Lambda: Configuración, Límites y Diagnóstico en Producción