RFC-010: Secrets Management Strategy
| Campo | Valor |
|---|---|
| Status | Draft |
| Author | Time de Tecnologia |
| Created | 2026-02-05 |
| Updated | 2026-02-05 |
| Reunião | A ser agendada |
| Deciders | Todo o time técnico |
Status
🟡 Draft - RFC em elaboração, aguardando apresentação
Contexto e Problema
AWS Secrets Manager documentado em docs/security/secrets-management.md, mas falta:
- Rotação automática de secrets
- Acesso granular (IAM policies específicas)
- Secrets para desenvolvimento local (processo claro)
- Auditoria de acesso a secrets
- Emergency access procedures
Por que resolver isso agora?
- Secrets não são rotacionados regularmente
- Risco de secrets comprometidos não detectados
- Developers têm acesso excessivo
- Processo manual e propenso a erros
- Falta auditoria de quem acessa o quê
Impacto de não resolver
- Secrets comprometidos não rotacionados
- Acesso não autorizado a sistemas críticos
- Violation de compliance
- Dificuldade em revogar acesso
- Secrets hardcoded em código
Documentação relacionada:
- docs/security/secrets-management.md - Processo atual
- docs/infrastructure/iam-policies.md - Políticas de acesso
- docs/development/local-development.md - Setup local
- docs/security/secure-coding.md - Nunca commitar secrets
Proposta de Solução
Secrets management com AWS Secrets Manager, rotação automática, e least privilege access.
Hierarquia de Secrets
AWS Secrets Manager (Production): - Database credentials - API keys externas - JWT signing keys - Third-party service credentials - Rotação automática habilitada
AWS SSM Parameter Store (Configuration): - URLs de serviços - Feature flags - Configurações não-sensíveis - Valores públicos
Environment Variables (.env.local):
- Desenvolvimento local apenas
- Nunca commitado
- .env.example com template
Naming Convention
# Secrets Manager
/prod/database/master-password
/prod/api-keys/stripe-secret
/prod/jwt/signing-key
/staging/database/master-password
/staging/api-keys/stripe-test-key
# SSM Parameter Store
/prod/config/api-base-url
/prod/config/max-connections
/staging/config/api-base-url
Rotação Automática
Database credentials (a cada 90 dias):
# Lambda function para rotação
import boto3
import json
secrets = boto3.client('secretsmanager')
rds = boto3.client('rds')
def lambda_handler(event, context):
"""Rotate RDS master password."""
secret_arn = event['SecretId']
token = event['ClientRequestToken']
step = event['Step']
if step == "createSecret":
# Gerar nova senha
new_password = generate_secure_password()
# Criar nova versão do secret
secrets.put_secret_value(
SecretId=secret_arn,
ClientRequestToken=token,
SecretString=json.dumps({
"password": new_password
}),
VersionStages=['AWSPENDING']
)
elif step == "setSecret":
# Atualizar senha no RDS
rds.modify_db_instance(
DBInstanceIdentifier='prod-db',
MasterUserPassword=new_password,
ApplyImmediately=True
)
elif step == "testSecret":
# Testar conexão com nova senha
test_db_connection(new_password)
elif step == "finishSecret":
# Marcar nova versão como CURRENT
secrets.update_secret_version_stage(
SecretId=secret_arn,
VersionStage='AWSCURRENT',
MoveToVersionId=token
)
def generate_secure_password():
"""Generate 32-char secure password."""
import secrets
import string
alphabet = string.ascii_letters + string.digits + string.punctuation
return ''.join(secrets.choice(alphabet) for _ in range(32))
API keys (rotação manual com checklist):
- Gerar nova key no serviço externo
- Adicionar nova key ao Secrets Manager (versão PENDING)
- Deploy código que tenta PENDING, fallback para CURRENT
- Validar funcionamento
- Marcar PENDING como CURRENT
- Revogar key antiga no serviço externo
Acesso Granular (IAM)
Lambda functions (least privilege):
# SAM template
UserFunctionRole:
Type: AWS::IAM::Role
Properties:
Policies:
- PolicyName: SecretsAccess
PolicyDocument:
Statement:
- Effect: Allow
Action:
- secretsmanager:GetSecretValue
Resource:
- !Sub 'arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:/prod/database/*'
- !Sub 'arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:/prod/jwt/*'
# Não tem acesso a API keys de third-party services
PaymentFunctionRole:
Type: AWS::IAM::Role
Properties:
Policies:
- PolicyName: SecretsAccess
PolicyDocument:
Statement:
- Effect: Allow
Action:
- secretsmanager:GetSecretValue
Resource:
- !Sub 'arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:/prod/api-keys/stripe-*'
# Apenas Stripe, não tem acesso a outros secrets
Developers (read-only staging):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret"
],
"Resource": "arn:aws:secretsmanager:*:*:secret:/staging/*"
},
{
"Effect": "Deny",
"Action": "secretsmanager:*",
"Resource": "arn:aws:secretsmanager:*:*:secret:/prod/*"
}
]
}
DevOps/Tech Lead (production access):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"secretsmanager:*"
],
"Resource": "*",
"Condition": {
"StringEquals": {
"aws:RequestedRegion": "us-east-1"
}
}
}
]
}
Desenvolvimento Local
Setup process:
# 1. Criar .env.local (nunca commitado)
cp .env.example .env.local
# 2. Obter secrets do staging (se tem acesso)
aws secretsmanager get-secret-value \
--secret-id /staging/database/master-password \
--query SecretString \
--output text > .secrets
# 3. Ou usar valores de desenvolvimento
# .env.local
DATABASE_URL=postgresql://user:pass@localhost:5432/dev_db
JWT_SECRET=dev_secret_key_not_for_production
STRIPE_KEY=sk_test_xxxxx # Test key, ok para dev
.gitignore obrigatório:
Pre-commit hook (detectar secrets):
# .pre-commit-config.yaml
repos:
- repo: https://github.com/Yelp/detect-secrets
rev: v1.4.0
hooks:
- id: detect-secrets
args: ['--baseline', '.secrets.baseline']
Auditoria
CloudTrail logging:
# Habilitar CloudTrail para Secrets Manager
AuditTrail:
Type: AWS::CloudTrail::Trail
Properties:
EventSelectors:
- DataResources:
- Type: AWS::SecretsManager::Secret
Values:
- !Sub 'arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:*'
ReadWriteType: All
S3BucketName: !Ref AuditBucket
Alertas para acesso suspeito:
# Lambda function processando CloudTrail logs
def detect_suspicious_access(event):
"""Alert on suspicious secrets access."""
# Alert se:
# - Acesso fora de horário comercial
# - Acesso de IP não reconhecido
# - Multiple failed attempts
# - Acesso a prod secrets por dev account
if is_suspicious(event):
send_alert_to_security_team(event)
Emergency Access
Break-glass procedure:
- Declarar emergência (P0 incident)
- Obter aprovação (Tech Lead ou superior)
- Usar role especial (emergency-access role)
- Executar ação necessária
- Documentar tudo em incident log
- Revogar acesso imediatamente após
- Audit de todas as ações tomadas
- Postmortem obrigatório
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789:role/EmergencyAccessRole"
},
"Action": "secretsmanager:*",
"Resource": "*",
"Condition": {
"StringEquals": {
"aws:PrincipalTag/Emergency": "true"
}
}
}
]
}
Accessing Secrets no Código
Python (FastAPI/Django):
import boto3
import json
from functools import lru_cache
secrets_client = boto3.client('secretsmanager')
@lru_cache(maxsize=128)
def get_secret(secret_name: str) -> dict:
"""Get secret from Secrets Manager with caching."""
try:
response = secrets_client.get_secret_value(SecretId=secret_name)
return json.loads(response['SecretString'])
except ClientError as e:
logger.error(f"Failed to get secret {secret_name}: {e}")
raise
# Uso
db_creds = get_secret('/prod/database/master-password')
DATABASE_URL = f"postgresql://{db_creds['username']}:{db_creds['password']}@{db_creds['host']}/db"
Evitar:
# ❌ Nunca hardcode
API_KEY = "sk_live_abc123..."
# ❌ Nunca em environment variables (Lambda)
# Use Secrets Manager integration do Lambda
# ❌ Nunca em logs
logger.info(f"Using API key: {api_key}") # BAD
# ✅ Correto
logger.info("API key loaded successfully") # GOOD
Alternativas Consideradas
Opção 1: HashiCorp Vault
Prós: - Feature-rich - Open source - Multi-cloud
Contras: - Infraestrutura adicional para manter - Custo operacional - Secrets Manager já integrado com AWS
Por que não escolhemos: Overhead não justificado. Secrets Manager é suficiente.
Opção 2: Environment variables apenas
Prós: - Simples - Sem dependencies
Contras: - Sem rotação automática - Sem auditoria - Expostos em process list - Não escalável
Por que não escolhemos: Inseguro e não compliance.
Opção 3: Git-crypt
Prós: - Secrets em repositório (conveniente) - Versionados com código
Contras: - Ainda são commitados (risco) - Sem rotação automática - Difícil revogação - Não é best practice
Por que não escolhemos: Anti-pattern. Secrets não devem estar em repositório.
Análise de Impacto
Impacto Técnico
- Secrets Manager para todos os secrets críticos
- Rotação automática a cada 90 dias
- IAM policies granulares
- CloudTrail audit
Impacto em Negócio
- ✅ Risco de secrets comprometidos reduzido
- ✅ Compliance com SOC2/ISO27001
- ✅ Auditoria completa de acesso
- ⚠️ Custo Secrets Manager: ~$0.40/secret/mês
Riscos
Risco: Rotação automática quebra aplicação
Mitigação: Testing rigoroso em staging, monitoramento, rollback rápido.
Plano de Implementação
Fase 1: Migração (Semana 1-2)
- Migrar secrets críticos para Secrets Manager
- Atualizar código para usar Secrets Manager
- Testar em staging
Fase 2: Rotação (Semana 3)
- Configurar rotação automática (database)
- Testar processo de rotação
- Documentar
Fase 3: IAM (Semana 4)
- Implementar least privilege policies
- Audit current access
- Revogar acesso desnecessário
Fase 4: Auditoria (Mês 2)
- Habilitar CloudTrail
- Configurar alertas
- Training do time
Métricas de Sucesso
Após 1 mês: - ✅ 100% secrets críticos no Secrets Manager - ✅ Rotação automática configurada - ✅ Zero secrets hardcoded no código
Após 3 meses: - ✅ Secrets rotacionados automaticamente - ✅ Zero incidentes de secrets comprometidos - ✅ Audit logs funcionais