Día 12: AWS Lambda con Variables de Entorno y Secrets Manager
🚀 Día 12: Configuración segura en Lambda — Variables de Entorno y Secrets Manager
Hoy vamos a aprender una de las prácticas más importantes en serverless: cómo manejar credenciales y configuraciones sensibles sin hardcodearlas en el código.
Usaremos Variables de Entorno para valores no sensibles y AWS Secrets Manager para almacenar secretos de forma segura (claves API, tokens, contraseñas, etc.).
¿Por qué usar Variables de Entorno y Secrets Manager?
- Evitar credenciales hardcodeadas (riesgo de seguridad alto).
- Separar la configuración del código.
- Facilitar la rotación de secretos sin redeployar la función.
- Cumplir con buenas prácticas de seguridad y compliance.
- Diferenciar fácilmente entre entornos (desarrollo, staging, producción).
⚠️ Nota importante sobre seguridad
Nunca debes guardar valores sensibles directamente en el template de CloudFormation.
En este ejemplo crearemos el secreto manualmente en la consola de Secrets Manager y solo referenciamos su nombre desde CloudFormation. Esta es la forma recomendada en entornos reales.
🛠️ El Código (CloudFormation)
AWSTemplateFormatVersion: '2010-09-09'
Description: Lambda con Variables de Entorno y Secrets Manager (Seguro)
Resources:
# ==================== ROL IAM ====================
LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyName: SecretsManagerPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- secretsmanager:GetSecretValue
Resource: !Sub "arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:prod/lambda/api-key*"
# ==================== FUNCIÓN LAMBDA ====================
MyLambdaWithSecrets:
Type: AWS::Lambda::Function
Properties:
FunctionName: LambdaWithSecrets-Python
Runtime: python3.12
Role: !GetAtt LambdaExecutionRole.Arn
Handler: index.lambda_handler
Timeout: 30
Environment:
Variables:
ENVIRONMENT: "production"
LOG_LEVEL: "INFO"
SECRET_NAME: "prod/lambda/api-key" # Nombre del secreto creado manualmente
Code:
ZipFile: |
import json
import logging
import boto3
import os
logger = logging.getLogger()
logger.setLevel(os.getenv("LOG_LEVEL", "INFO"))
def get_secret(secret_name):
"""Obtiene el secreto de forma segura desde Secrets Manager"""
client = boto3.client('secretsmanager')
response = client.get_secret_value(SecretId=secret_name)
# Si el secreto está en formato string JSON, lo parseamos
if 'SecretString' in response:
return json.loads(response['SecretString'])
# Si está en binario (menos común)
else:
return json.loads(response['SecretBinary'].decode('utf-8'))
def lambda_handler(event, context):
logger.info("=== Inicio de la función Lambda ===")
logger.info(f"Request ID: {context.aws_request_id}")
logger.info(f"Entorno: {os.getenv('ENVIRONMENT')}")
try:
secret_name = os.getenv("SECRET_NAME")
secret = get_secret(secret_name)
api_key = secret.get("api_key")
logger.info("Secreto obtenido correctamente desde Secrets Manager")
# Nunca loguees el valor real del secreto en producción
response_body = {
"mensaje": "Función ejecutada correctamente con Secrets Manager",
"version": "Día 12",
"environment": os.getenv("ENVIRONMENT"),
"api_key_length": len(api_key) if api_key else 0,
"request_id": context.aws_request_id
}
logger.info("Función completada exitosamente")
return {
"statusCode": 200,
"headers": {
"Content-Type": "application/json"
},
"body": json.dumps(response_body)
}
except Exception as e:
logger.error(f"Error en la función: {str(e)}", exc_info=True)
return {
"statusCode": 500,
"headers": {
"Content-Type": "application/json"
},
"body": json.dumps({
"mensaje": "Ocurrió un error al obtener el secreto",
"error": str(e)
})
}
📚 Conceptos Nuevos Explicados
1. Variables de Entorno (Environment.Variables)
Se usan para pasar configuración no sensible a la Lambda (entorno, nivel de logs, nombres, etc.).
2. AWS Secrets Manager
Servicio gestionado para almacenar y rotar secretos de forma segura. Ideal para credenciales.
3. Permisos IAM mínimos
Solo se da el permiso secretsmanager:GetSecretValue sobre el secreto específico (principio de menor privilegio).
4. Creación manual del secreto
Paso previo obligatorio:
Ve a la consola de Secrets Manager → Store a new secret → elige “Other type of secret” → agrega una clave api_key con tu valor → nómbralo exactamente prod/lambda/api-key.
5. Buenas prácticas de seguridad
- Nunca imprimir el valor real del secreto en los logs.
- Usar
os.getenv()para leer variables de entorno. - Rotar los secretos periódicamente desde Secrets Manager.
📌 Nota sobre cambios en Variables de Entorno
Cuando modificas las variables de entorno de una función Lambda:
- El cambio no es instantáneo.
- AWS tarda unos segundos en propagar la nueva configuración.
- Las siguientes ejecuciones después del cambio usarán el nuevo valor.
- Recomendación: Espera 10-15 segundos después de guardar el cambio antes de volver a probar.
Ejemplo: Si cambias LOG_LEVEL de "INFO" a "DEBUG", los logs más detallados aparecerán en las invocaciones posteriores.
🚀 Cómo Desplegarlo
Primero crea el secreto manualmente en Secrets Manager con el nombre prod/lambda/api-key.
Luego despliega el stack:
aws cloudformation update-stack \
--stack-name Mi-Infra-Con-Monitoreo \
--template-body file://20-lambda-secrets-manager.yaml \
--capabilities CAPABILITY_NAMED_IAM
📈 Cómo probar tu función
- Ve a la consola de AWS Lambda → busca
LambdaWithSecrets-Python. - Haz clic en Test → crea un test event vacío (
{}). - Ejecuta el test.
- Revisa la respuesta: deberías ver
api_key_lengthcon un valor mayor a 0. - Ve a Monitor → View logs in CloudWatch para verificar que el secreto se leyó correctamente.
Importante: En los logs solo verás la longitud de la clave, nunca el valor completo.
📂 Código Adjunto
Puedes encontrar el template completo aquí: Ver archivo en GitHub
🎥 Video Tutorial
Mira el proceso paso a paso en video:
💡 Próximos pasos
- Programando ejecuciones automáticas con Amazon EventBridge (Cron Jobs en Lambda) (Funciones que se ejecutan según horario)