Secrets Management
Gerenciamento seguro de secrets e credenciais.
Estratégia
AWS Secrets Manager
Para: - Database credentials - API keys de terceiros - OAuth client secrets - Certificates e private keys
Vantagens: - Rotação automática - Encryption at rest (KMS) - Audit trail (CloudTrail) - Versioning
Systems Manager Parameter Store (SSM)
Para: - Configurações não-sensíveis - Endpoints de serviços - Feature flags - Database connection strings (menos sensíveis)
Vantagens: - Grátis para standard parameters - Integração fácil - Hierarquia de parâmetros - SecureString com KMS
Secrets Manager
Criar Secret
# Via CLI
aws secretsmanager create-secret \
--name /app/production/database \
--description "Production database credentials" \
--secret-string '{
"username": "app_user",
"password": "super-secure-password",
"host": "prod-db.rds.amazonaws.com",
"port": 5432,
"database": "app_db"
}'
Usar em Lambda (SAM)
Resources:
MyFunction:
Type: AWS::Serverless::Function
Properties:
Handler: app.main.lambda_handler
Runtime: python3.11
Policies:
- AWSSecretsManagerGetSecretValuePolicy:
SecretArn: !Ref DatabaseSecret
Environment:
Variables:
DATABASE_SECRET_ARN: !Ref DatabaseSecret
DatabaseSecret:
Type: AWS::SecretsManager::Secret
Properties:
Name: !Sub '/${AWS::StackName}/database'
Description: Database credentials
SecretString: !Sub |
{
"username": "app_user",
"password": "${DatabasePassword}",
"host": "${DatabaseHost}",
"database": "app_db"
}
Acessar no Código
import boto3
import json
from functools import lru_cache
secretsmanager = boto3.client('secretsmanager')
@lru_cache()
def get_secret(secret_name: str) -> dict:
"""Get secret from Secrets Manager (cached)."""
try:
response = secretsmanager.get_secret_value(SecretId=secret_name)
return json.loads(response['SecretString'])
except Exception as e:
logger.error(f"Failed to get secret {secret_name}: {e}")
raise
# Usage
db_secret = get_secret(os.environ['DATABASE_SECRET_ARN'])
DATABASE_URL = f"postgresql://{db_secret['username']}:{db_secret['password']}@{db_secret['host']}/{db_secret['database']}"
Rotação Automática
DatabaseSecretRotation:
Type: AWS::SecretsManager::RotationSchedule
Properties:
SecretId: !Ref DatabaseSecret
RotationLambdaARN: !GetAtt RotationFunction.Arn
RotationRules:
AutomaticallyAfterDays: 30
SSM Parameter Store
Criar Parameters
# String simples
aws ssm put-parameter \
--name /app/production/api-endpoint \
--value "https://api.example.com" \
--type String
# SecureString (encrypted)
aws ssm put-parameter \
--name /app/production/api-key \
--value "secret-key-value" \
--type SecureString \
--key-id alias/aws/ssm
# Hierarquia
/app/production/database/host
/app/production/database/port
/app/staging/database/host
Usar em Lambda
Resources:
MyFunction:
Type: AWS::Serverless::Function
Properties:
Policies:
- SSMParameterReadPolicy:
ParameterName: app/production/*
Environment:
Variables:
API_ENDPOINT: '{{resolve:ssm:/app/production/api-endpoint}}'
Acessar no Código
import boto3
ssm = boto3.client('ssm')
def get_parameter(name: str, decrypt: bool = False) -> str:
"""Get parameter from SSM."""
response = ssm.get_parameter(
Name=name,
WithDecryption=decrypt
)
return response['Parameter']['Value']
# Usage
api_endpoint = get_parameter('/app/production/api-endpoint')
api_key = get_parameter('/app/production/api-key', decrypt=True)
Environment Variables
Hierarquia de Precedência
- AWS Secrets Manager (mais sensível)
- SSM SecureString
- SSM String
- Environment variables (menos sensível)
Exemplo Completo
import os
from functools import lru_cache
class Settings:
"""Application settings."""
# Non-sensitive: env vars
ENVIRONMENT: str = os.getenv('ENVIRONMENT', 'development')
LOG_LEVEL: str = os.getenv('LOG_LEVEL', 'INFO')
# Moderately sensitive: SSM
@property
@lru_cache()
def API_ENDPOINT(self) -> str:
return get_parameter('/app/production/api-endpoint')
# Highly sensitive: Secrets Manager
@property
@lru_cache()
def DATABASE_URL(self) -> str:
secret = get_secret(os.environ['DATABASE_SECRET_ARN'])
return f"postgresql://{secret['username']}:{secret['password']}@{secret['host']}/{secret['database']}"
settings = Settings()
Regras de Segurança
✅ Fazer
- Usar Secrets Manager para dados sensíveis
- Rotacionar secrets regularmente
- Criptografar secrets at rest
- Audit trail de acesso (CloudTrail)
- Least privilege IAM policies
- Usar roles, não access keys
❌ Nunca
- Hardcode secrets no código
- Commit secrets no Git
- Compartilhar secrets via email/Slack
- Usar mesmo secret em staging e production
- Logar secrets (mesmo em debug)
- Usar secrets em URLs
Secrets no CI/CD
GitHub Actions
- name: Deploy
env:
DATABASE_PASSWORD: ${{ secrets.PROD_DATABASE_PASSWORD }}
run: |
# Password não aparece nos logs
sam deploy --parameter-overrides DatabasePassword=$DATABASE_PASSWORD
GitHub Secrets
Configurar em: Repository → Settings → Secrets
Production:
- PROD_DATABASE_PASSWORD
- AWS_PROD_ROLE_ARN
- SLACK_WEBHOOK
Staging:
- STAGING_DATABASE_PASSWORD
- AWS_STAGING_ROLE_ARN
Rotação de Secrets
Scheduled Rotation
# Lambda de rotação
def lambda_handler(event, context):
"""Rotate database password."""
secret_arn = event['SecretId']
token = event['ClientRequestToken']
step = event['Step']
if step == "createSecret":
# Gerar nova senha
new_password = generate_secure_password()
secretsmanager.put_secret_value(
SecretId=secret_arn,
ClientRequestToken=token,
SecretString=json.dumps({"password": new_password}),
VersionStages=['AWSPENDING']
)
elif step == "setSecret":
# Atualizar senha no banco
pending_secret = get_secret_version(secret_arn, "AWSPENDING")
update_database_password(pending_secret['password'])
elif step == "testSecret":
# Testar nova senha
pending_secret = get_secret_version(secret_arn, "AWSPENDING")
test_database_connection(pending_secret)
elif step == "finishSecret":
# Marcar como current
secretsmanager.update_secret_version_stage(
SecretId=secret_arn,
VersionStage='AWSCURRENT',
MoveToVersionId=token
)
Security Scan
Pre-commit Hook
# .pre-commit-config.yaml
- repo: https://github.com/Yelp/detect-secrets
rev: v1.4.0
hooks:
- id: detect-secrets
args: ['--baseline', '.secrets.baseline']
CI/CD Scan
- name: Scan for secrets
run: |
pip install detect-secrets
detect-secrets scan --baseline .secrets.baseline
detect-secrets audit .secrets.baseline
Incident Response
Se secret foi comprometido:
- Imediatamente: Rotacionar secret
- Investigar: Como foi exposto?
- Revogar: Invalidar todas as sessões com secret antigo
- Monitorar: Uso não autorizado
- Documentar: Postmortem
- Prevenir: Adicionar controles