Vam acabar 04-02 amb la base de dades mercadofresco-pedidos xifrada amb AES-256 i una clau de KMS amb política, rotació i separació de funcions. I tot i així, qualsevol que s'assegui davant del portàtil del Luis es pot connectar a aquesta base de dades, perquè la contrasenya de l'usuari mfadmin està escrita en text pla com a mínim en cinc llocs:

  1. Al user data de la plantilla de llançament lt-mercadofresco-tienda, que es pot llegir des del servei de metadades de qualsevol instància de la botiga.
  2. A /etc/mercadofresco/tienda.conf dins de cada instància.
  3. En un fitxer .env que el Luis es va enviar a si mateix per xat quan va muntar l'entorn de desenvolupament.
  4. A l'historial d'ordres de la sessió on la va fer servir per primer cop.
  5. Al cap de la Marta, que la va triar fa catorze mesos i que no ha canviat des d'aleshores.

El xifratge no ajuda aquí. Com vam veure a la primera taula de 04-02, el xifratge en repòs protegeix del disc robat i de l'snapshot compartit per error, però no protegeix d'unes credencials vàlides en mans equivocades. Aquesta lliçó resol exactament aquest problema.

AWS ofereix dos serveis per custodiar valors de configuració i credencials: AWS Systems Manager Parameter Store i AWS Secrets Manager. No són competidors, són complementaris, i la meitat d'aquesta lliçó consisteix a saber quin fer servir per a cada cosa.

Advertiment. Els exemples són didàctics i les credencials, fictícies. Tota gestió real de credencials, rotació i compliment (RGPD, PCI DSS) ha de ser revisada per un professional de seguretat abans d'aplicar-se a un entorn amb dades de clients. Un secret mal migrat —especialment una rotació mal configurada— deixa l'aplicació fora de servei i sense manera evident de tornar enrere. No facis servir mai credencials reals en un entorn de proves.

Contingut

  1. Per què les credencials al codi són un problema estructural
  2. Què passa exactament quan un secret arriba a Git
  3. Configuració i secret no són el mateix
  4. Parameter Store: tipus de paràmetre
  5. Jerarquia per rutes, versions i nivells
  6. La configuració de MercadoFresco a Parameter Store
  7. Secrets Manager: secrets, versions i etapes
  8. Xifratge amb KMS i polítiques de recurs
  9. Rotació automàtica: el cicle de quatre passos
  10. Configurar la rotació de mercadofresco/produccion/rds/mfadmin
  11. Usuari únic enfront d'alternança de dos usuaris
  12. Taula comparativa i la recomanació per a MercadoFresco
  13. Consum des de l'aplicació: boto3 i memòria cau
  14. Consum des de Lambda, EC2, ECS i CloudFormation
  15. Permisos mínims per llegir un secret
  16. La migració: treure la contrasenya del user data
  17. Què no cal guardar aquí
  18. Auditoria de l'accés als secrets
  19. Detecció de secrets filtrats al repositori
  20. Cost, quotes i neteja

Per què les credencials al codi són un problema estructural

No és descuit, és un problema de disseny. Una credencial escrita en un fitxer de configuració té quatre propietats que la condemnen:

Propietat Conseqüència
Es copia Cada desplegament, cada còpia de seguretat, cada portàtil nou multiplica les còpies
No caduca La contrasenya de mfadmin fa catorze mesos que és la mateixa, i en podria fer deu anys
No deixa rastre Ningú no sap qui l'ha llegit; no hi ha registre possible
No es pot revocar sense trencar res Canviar-la implica actualitzar tots els llocs alhora

La quarta és la que perpetua el problema. La Marta sap que la contrasenya s'hauria de canviar, però canviar-la significa editar la plantilla de llançament, reiniciar totes les instàncies de l'ASG, avisar el Luis i creuar els dits. Com que el risc de l'operació sembla més gran que el de no fer res, no es fa mai. La rotació automàtica trenca aquest cercle, i per això és el cor d'aquesta lliçó.

Què passa exactament quan un secret arriba a Git

Convé ser concret, perquè l'error més car és creure que n'hi ha prou d'esborrar-lo al commit següent:

# El Luis se n'adona i «ho arregla»
git rm --cached .env
git commit -m "Treure el .env del repositori"
git push

Això no elimina res. El fitxer continua a l'historial, accessible amb git show <commit-anterior>:.env. I si el repositori va estar en un servidor compartit o algú en va fer un clone, aquestes còpies també el tenen. L'únic procediment correcte és:

  1. Rota la credencial immediatament. Considera-la compromesa des del moment en què es va escriure. Aquest pas és obligatori i no és opcional.
  2. Reescriure l'historial (git filter-repo, BFG) i forçar el push. Això trenca els clons de tot l'equip, així que cal coordinar-ho.
  3. Buscar als registres si la credencial s'ha fet servir des d'algun lloc inesperat.
  4. Afegir un escàner que impedeixi que torni a passar.

L'ordre importa: rotar primer. Reescriure l'historial mentre la credencial continua sent vàlida és tractar el símptoma. Si el repositori és públic, els bots que rastregen GitHub troben credencials d'AWS en qüestió de minuts, i el cost d'un compte utilitzat per minar criptomonedes es compta en milers de dòlars al dia.

Configuració i secret no són el mateix

Aquesta distinció decideix quin servei fer servir:

Configuració Secret
Exemple Nom del bucket, mida de pàgina, temps d'espera Contrasenya, clau d'API, token
Pot aparèixer en un registre? Sí, sense conseqüències Mai
Necessita rotar? No Sí, periòdicament
Qui la pot veure? Tot l'equip tècnic Només qui la necessita
Servei Parameter Store Secrets Manager

Casos frontera que convé resoldre per endavant:

  • El nom d'usuari de la base de dades (mfadmin) és configuració. La contrasenya és secret. Però es guarden junts, perquè la rotació canvia tots dos en el cas d'alternança.
  • El punt d'enllaç d'RDS és configuració: no és secret i ja és públic dins la teva VPC.
  • Una clau d'API d'un proveïdor de pagaments és un secret, encara que sigui d'un entorn de proves.
  • L'identificador de la distribució de CloudFront (E2QWERTY123ABC) és configuració.

Parameter Store: tipus de paràmetre

Parameter Store és un component d'AWS Systems Manager. Guarda parells clau-valor amb tres tipus:

Tipus Xifrat Ús
String No Valors simples: noms, URL, números
StringList No Llista separada per comes: eu-west-1a,eu-west-1b
SecureString Sí, amb KMS Valors sensibles de perfil baix
# Un paràmetre simple
aws ssm put-parameter \
  --name "/mercadofresco/produccion/tienda/nombre-bucket-fotos" \
  --value "mercadofresco-catalogo-fotos" \
  --type String \
  --description "Bucket del cataleg de fotos de la botiga" \
  --tags Key=Proyecto,Value=mercadofresco Key=Entorno,Value=produccion \
  --profile mercadofresco-dev

# Una llista
aws ssm put-parameter \
  --name "/mercadofresco/produccion/red/zonas" \
  --value "eu-west-1a,eu-west-1b" \
  --type StringList \
  --profile mercadofresco-dev

# Un valor xifrat amb la nostra clau de 04-02
aws ssm put-parameter \
  --name "/mercadofresco/produccion/tienda/clave-api-mensajeria" \
  --value "ficticia-1234567890" \
  --type SecureString \
  --key-id alias/mercadofresco-datos \
  --profile mercadofresco-dev

Notes sobre SecureString:

  • Si no indiques --key-id, es fa servir la clau gestionada per AWS alias/aws/ssm, que és gratuïta però no controlable. Per a valors que importen, fes servir la teva clau.
  • Per llegir el valor desxifrat cal --with-decryption i permís kms:Decrypt sobre la clau. És la mateixa doble autorització d'SSE-KMS.
aws ssm get-parameter \
  --name "/mercadofresco/produccion/tienda/clave-api-mensajeria" \
  --with-decryption \
  --query 'Parameter.Value' --output text \
  --profile mercadofresco-dev

Jerarquia per rutes, versions i nivells

La jerarquia per rutes és la característica que fa útil Parameter Store. Els noms s'estructuren com a rutes i es poden llegir en bloc:

/mercadofresco/
├── produccion/
│   ├── tienda/
│   │   ├── nombre-bucket-fotos
│   │   ├── url-cdn
│   │   ├── pedidos-por-pagina
│   │   └── tiempo-espera-segundos
│   ├── basedatos/
│   │   ├── punto-enlace
│   │   ├── punto-enlace-lectura
│   │   ├── nombre-bd
│   │   └── usuario
│   └── red/
│       └── zonas
└── desarrollo/
    └── tienda/
        └── ...

Amb una sola crida l'aplicació carrega tota la seva configuració:

aws ssm get-parameters-by-path \
  --path "/mercadofresco/produccion/tienda/" \
  --recursive --with-decryption \
  --query 'Parameters[].[Name,Value]' --output table \
  --profile mercadofresco-dev

I la jerarquia es converteix directament en control d'accés: una política que concedeix ssm:GetParametersByPath sobre /mercadofresco/produccion/* deixa fora tot l'entorn de desenvolupament sense enumerar ni un sol paràmetre.

Versions. Cada put-parameter --overwrite crea una versió nova i conserva les anteriors. Es pot llegir una versió concreta amb nom:numero, i etiquetar una versió amb un àlies:

aws ssm put-parameter --name "/mercadofresco/produccion/tienda/pedidos-por-pagina" \
  --value "50" --type String --overwrite --profile mercadofresco-dev

aws ssm label-parameter-version \
  --name "/mercadofresco/produccion/tienda/pedidos-por-pagina" \
  --parameter-version 3 --labels estable --profile mercadofresco-dev

# Llegir una versió concreta o una etiqueta
aws ssm get-parameter --name "/mercadofresco/produccion/tienda/pedidos-por-pagina:2" \
  --profile mercadofresco-dev
aws ssm get-parameter --name "/mercadofresco/produccion/tienda/pedidos-por-pagina:estable" \
  --profile mercadofresco-dev

Tornar enrere després d'un canvi desafortunat és canviar l'etiqueta estable a la versió anterior. No hi ha cap desplegament pel mig.

Nivells.

Estàndard Avançat
Paràmetres per compte i regió 10.000 100.000
Mida del valor 4 KB 8 KB
Polítiques de paràmetre (caducitat, avisos) No
Cost d'emmagatzematge Gratis 0,05 USD per paràmetre i mes
Cost de les crides Gratis fins a 40/s 0,05 USD per cada 10.000

MercadoFresco cap de sobres al nivell estàndard: zero euros per tota la seva configuració. El nivell avançat es justifica quan necessites més de 4 KB —un certificat, per exemple— o les polítiques de caducitat, que avisen quan un paràmetre fa massa temps que no canvia.

Hi ha a més un mode de rendiment alt (--parameter-tier, amb ssm:GetParameters a 3.000 peticions per segon) que es factura a part; només cal si l'aplicació llegeix paràmetres al camí crític de cada petició, cosa que no hauria de fer: per a això hi ha la memòria cau.

La configuració de MercadoFresco a Parameter Store

crear() {
  aws ssm put-parameter --name "$1" --value "$2" --type "${3:-String}" --overwrite \
    --tags Key=Proyecto,Value=mercadofresco Key=Entorno,Value=produccion \
           Key=Componente,Value=tienda Key=Propietario,Value=marta \
           Key=CentroCoste,Value=tecnologia \
    --profile mercadofresco-dev
}

crear "/mercadofresco/produccion/basedatos/punto-enlace" \
      "mercadofresco-pedidos.abc123.eu-west-1.rds.amazonaws.com"
crear "/mercadofresco/produccion/basedatos/punto-enlace-lectura" \
      "mercadofresco-pedidos-lectura.abc123.eu-west-1.rds.amazonaws.com"
crear "/mercadofresco/produccion/basedatos/nombre-bd" "pedidos"
crear "/mercadofresco/produccion/basedatos/usuario" "mfadmin"
crear "/mercadofresco/produccion/tienda/nombre-bucket-fotos" "mercadofresco-catalogo-fotos"
crear "/mercadofresco/produccion/tienda/url-cdn" "https://d111111abcdef8.cloudfront.net"
crear "/mercadofresco/produccion/tienda/pedidos-por-pagina" "50"
crear "/mercadofresco/produccion/tienda/tiempo-espera-segundos" "30"
crear "/mercadofresco/produccion/tienda/tema-alertas" \
      "arn:aws:sns:eu-west-1:111122223333:alertas-mercadofresco"

Fixa't que l'usuari mfadmin és aquí i la contrasenya no. El nom d'usuari és configuració; la contrasenya va a l'altre servei. I observa l'efecte col·lateral: el user data de lt-mercadofresco-tienda, que fins ara era una llista de valors incrustats, passa a ser una crida genèrica idèntica a tots els entorns.

Secrets Manager: secrets, versions i etapes

Un secret de Secrets Manager és un valor —normalment un JSON— xifrat amb KMS, amb versions i un mecanisme de rotació. La peça que cal entendre bé són les etapes de versió.

Cada versió d'un secret porta una o diverses etiquetes d'etapa:

Etapa Significat
AWSCURRENT La versió vigent. És la que retorna get-secret-value per defecte
AWSPENDING Versió candidata durant una rotació en curs, encara no validada
AWSPREVIOUS La versió anterior, conservada per poder tornar enrere

Aquestes tres etiquetes són el mecanisme que fa que la rotació no talli el servei. Durant uns segons coexisteixen la contrasenya vella (AWSCURRENT) i la nova (AWSPENDING), i només quan la nova s'ha provat s'intercanvien les etiquetes. Cap aplicació no es queda sense credencial vàlida en cap moment.

Crear el secret de MercadoFresco:

aws secretsmanager create-secret \
  --name "mercadofresco/produccion/rds/mfadmin" \
  --description "Credencials de l'usuari mfadmin de mercadofresco-pedidos" \
  --kms-key-id alias/mercadofresco-datos \
  --secret-string '{
    "engine": "postgres",
    "host": "mercadofresco-pedidos.abc123.eu-west-1.rds.amazonaws.com",
    "port": 5432,
    "dbname": "pedidos",
    "username": "mfadmin",
    "password": "ContrasenaFicticiaTemporal2026",
    "dbInstanceIdentifier": "mercadofresco-pedidos"
  }' \
  --tags Key=Proyecto,Value=mercadofresco Key=Entorno,Value=produccion \
         Key=Componente,Value=basedatos Key=Propietario,Value=marta \
         Key=CentroCoste,Value=tecnologia \
  --profile mercadofresco-dev

L'estructura del JSON no és arbitrària: aquests noms de camp (engine, host, port, dbname, username, password) són exactament els que espera la funció de rotació gestionada d'AWS per a PostgreSQL. Si els canvies, la rotació no funcionarà. És un dels detalls que fa perdre més temps.

--kms-key-id alias/mercadofresco-datos connecta aquesta lliçó amb l'anterior: el secret es xifra amb la clau que vam construir a 04-02, i per tant queda subjecte a la seva política. Qui no pugui fer servir la clau no podrà llegir el secret encara que tingui secretsmanager:GetSecretValue. Un altre cop la doble autorització.

Xifratge amb KMS i polítiques de recurs

Com un bucket, una cua o una clau, un secret admet política de recurs. Serveix per a dues coses: accés entre comptes i reforç amb Deny.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "SoloLaTiendaYMartaLeenEsteSecreto",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "secretsmanager:GetSecretValue",
      "Resource": "*",
      "Condition": {
        "StringNotEquals": {
          "aws:PrincipalArn": [
            "arn:aws:iam::111122223333:role/rol-mercadofresco-tienda",
            "arn:aws:iam::111122223333:user/marta",
            "arn:aws:iam::111122223333:role/rol-rotacion-mfadmin"
          ]
        }
      }
    }
  ]
}

És un Deny amb StringNotEquals: «denega la lectura a tothom que no sigui un d'aquests tres». Recorda de 04-01 que un Deny guanya sempre, així que ni una política d'identitat amb secretsmanager:* sobre * se'l pot saltar. És la manera més robusta de blindar el secret més sensible del compte, i cal incloure a la llista el rol de rotació o la rotació fallarà.

aws secretsmanager put-resource-policy \
  --secret-id mercadofresco/produccion/rds/mfadmin \
  --resource-policy file:///tmp/politica-secreto.json \
  --block-public-policy \
  --profile mercadofresco-dev

--block-public-policy rebutja la política si concedís accés públic. Fes-lo servir sempre.

Rotació automàtica: el cicle de quatre passos

Aquí hi ha el valor diferencial de Secrets Manager. La rotació l'executa una funció Lambda, que AWS proporciona ja escrita per als motors de base de dades habituals, i que s'invoca quatre vegades amb un pas diferent cada cop.

sequenceDiagram
    participant SM as Secrets Manager
    participant L as Lambda de rotacio<br/>SecretsManagerRDSPostgreSQL...
    participant DB as mercadofresco-pedidos
    participant A as Botiga (aplicacio)

    Note over SM,A: Dia 30: toca rotar
    SM->>L: Step 1 - createSecret
    L->>L: Genera contrasenya aleatoria
    L->>SM: PutSecretValue(etapa=AWSPENDING)
    Note over SM: AWSCURRENT = vella<br/>AWSPENDING = nova

    SM->>L: Step 2 - setSecret
    L->>DB: ALTER USER mfadmin PASSWORD 'nova'
    Note over DB: Ara la BD accepta la NOVA

    SM->>L: Step 3 - testSecret
    L->>DB: Connectar amb AWSPENDING i fer SELECT
    DB-->>L: OK
    Note over L: Si falla aqui, s'avorta<br/>i AWSCURRENT segueix intacta

    SM->>L: Step 4 - finishSecret
    L->>SM: Moure AWSCURRENT a la versio nova
    Note over SM: nova = AWSCURRENT<br/>vella = AWSPREVIOUS

    A->>SM: GetSecretValue (propera lectura)
    SM-->>A: contrasenya nova

Els quatre passos, amb el que importa de cadascun:

Pas Què fa Què passa si falla
createSecret Genera la contrasenya nova i la desa com a AWSPENDING. No toca la base de dades No canvia res; es reintenta
setSecret Canvia la contrasenya a la base de dades Moment delicat: la BD pot tenir la nova i el secret no haver-la promocionat
testSecret Es connecta amb la nova i executa una consulta de prova S'avorta la rotació; AWSCURRENT continua sent la vella
finishSecret Mou l'etiqueta AWSCURRENT a la versió nova Es reintenta

La conseqüència pràctica més important és la que gairebé ningú no anticipa: entre setSecret i finishSecret hi ha una finestra en què la base de dades ja té la contrasenya nova però AWSCURRENT encara retorna la vella. Si la teva aplicació desa el secret a la memòria cau durant una hora i obre una connexió nova just en aquesta finestra, rebrà un error d'autenticació.

Per això el patró correcte a l'aplicació és:

  1. Desar el secret a la memòria cau (una lectura per petició és caríssima i lenta).
  2. En rebre un error d'autenticació, invalidar la memòria cau i reintentar un cop.

Aquest reintent d'una línia és el que converteix la rotació en una cosa transparent. Sense ell, cada rotació produeix un grapat d'errors als registres.

Configurar la rotació de mercadofresco/produccion/rds/mfadmin

La manera més senzilla és deixar que Secrets Manager creï la Lambda des de la seva plantilla gestionada:

aws secretsmanager rotate-secret \
  --secret-id mercadofresco/produccion/rds/mfadmin \
  --rotation-lambda-arn arn:aws:lambda:eu-west-1:111122223333:function:rotacion-mfadmin \
  --rotation-rules '{"AutomaticallyAfterDays": 30, "Duration": "2h",
                     "ScheduleExpression": "cron(0 3 ? * TUE *)"}' \
  --profile mercadofresco-dev

Els paràmetres de --rotation-rules:

  • AutomaticallyAfterDays: 30: cada 30 dies.
  • ScheduleExpression: quan exactament. Aquí, els dimarts a les 3 de la matinada. És deliberat: mai un dijous ni un divendres, pel pic de comandes.
  • Duration: "2h": finestra de dues hores dins la qual pot començar.

La Lambda de rotació necessita tres coses que s'obliden constantment:

  1. Accés de xarxa a la base de dades. Ha d'estar a la VPC vpc-mercadofresco, a les subxarxes snet-mercadofresco-app-a/-b, i sg-mercadofresco-basedatos ha d'acceptar el 5432 des del seu grup de seguretat. Aquí s'aplica el que vam veure a 03-02: referenciar l'SG d'origen en lloc d'un CIDR.
  2. Accés a l'API de Secrets Manager. Com que és a subxarxes privades i la seva sortida a internet passa per nat-mercadofresco-a, o bé s'accepta aquest cost o bé es crea un endpoint de VPC d'interfície per a secretsmanager, igual que vam fer amb vpce-mercadofresco-s3 a 03-01. Sense una de les dues coses, la Lambda es queda penjada fins a esgotar el temps d'espera i el símptoma no diu res.
  3. Permisos. El seu rol necessita secretsmanager:GetSecretValue, PutSecretValue, UpdateSecretVersionStage, DescribeSecret, GetRandomPassword, més kms:Decrypt i kms:GenerateDataKey sobre alias/mercadofresco-datos.

Rotació manual immediata, molt útil per provar abans de confiar en el calendari:

aws secretsmanager rotate-secret \
  --secret-id mercadofresco/produccion/rds/mfadmin \
  --rotate-immediately \
  --profile mercadofresco-dev

# Veure el resultat
aws secretsmanager describe-secret \
  --secret-id mercadofresco/produccion/rds/mfadmin \
  --query '{Rotacio:RotationEnabled, Ultima:LastRotatedDate, Proxima:NextRotationDate,
            Versions:VersionIdsToStages}' \
  --profile mercadofresco-dev

Prova la rotació en desenvolupament abans d'activar-la a producció. És el consell més important d'aquesta lliçó: una rotació mal configurada deixa la botiga sense base de dades, i l'error de setSecret a mitges pot requerir canviar la contrasenya a mà per recuperar-se.

Usuari únic enfront d'alternança de dos usuaris

Hi ha dues estratègies de rotació, i l'elecció té conseqüències reals de disponibilitat:

Usuari únic Alternança de dos usuaris
Com funciona Canvia la contrasenya del mateix usuari Alterna entre mfadmin_a i mfadmin_b
Finestra sense credencial vàlida Existeix, de segons No existeix
Complexitat Baixa Mitjana: cal crear i mantenir dos usuaris
Connexions obertes Continuen funcionant fins a reconnectar Igual
Quan Aplicacions amb reintent correcte Càrregues crítiques sense marge d'error

MercadoFresco comença amb usuari únic perquè és més simple i la botiga implementa el reintent. Quan el volum creixi —o quan entrin en joc contenidors que arrenquen i moren constantment, al mòdul 10— l'alternança de dos usuaris serà l'opció sensata. La plantilla gestionada d'AWS existeix per als dos casos: SecretsManagerRDSPostgreSQLRotationSingleUser i ...RotationMultiUser.

Taula comparativa i la recomanació per a MercadoFresco

Secrets Manager Parameter Store
Rotació automàtica , gestionada, amb Lambda No
Cost 0,40 USD per secret i mes + 0,05 USD/10.000 crides Gratis al nivell estàndard
Mida màxima 64 KB 4 KB (8 KB avançat)
Xifratge Sempre, amb KMS Només SecureString
Polítiques de recurs No
Accés entre comptes No directament
Jerarquia per rutes No (però es fan servir / per convenció) , amb lectura en bloc
Versions Sí, amb etapes Sí, amb etiquetes
Integració amb RDS Nativa, credencials gestionades No
Rèplica entre regions , automàtica No
Accés des de Parameter Store Sí: /aws/reference/secretsmanager/<nom>
Quan fer-lo servir Credencials que han de rotar Configuració i secrets de perfil baix

La recomanació per a MercadoFresco, escrita com a norma de l'equip:

Tot allò que sigui una credencial capaç de donar accés a dades de clients va a Secrets Manager, amb rotació activada. Tota la resta va a Parameter Store.

Aplicat:

Valor On Per què
Contrasenya de mfadmin Secrets Manager Dona accés a dades personals; ha de rotar
Clau d'API de la passarel·la de pagament Secrets Manager Credencial de tercers; ha de rotar
Usuari mfadmin Parameter Store És configuració
Punt d'enllaç d'RDS Parameter Store No és secret
Noms de bucket, URL del CDN Parameter Store Configuració pura
Clau d'API de missatgeria de comandes Parameter Store SecureString Sensible però de baix impacte i sense rotació automàtica disponible

Cost d'aquesta divisió: 2 secrets × 0,40 = 0,80 USD al mes, més uns cèntims de crides. Tota la configuració, gratis. Si ho haguéssim ficat tot a Secrets Manager, els 25 valors costarien 10 USD mensuals; si ho haguéssim ficat tot a Parameter Store, no tindríem rotació. La divisió no és purisme, és economia.

Un detall pràctic molt còmode: Parameter Store pot llegir secrets de Secrets Manager fent servir el prefix /aws/reference/secretsmanager/. Així l'aplicació utilitza una sola API per a tot:

aws ssm get-parameter \
  --name "/aws/reference/secretsmanager/mercadofresco/produccion/rds/mfadmin" \
  --with-decryption --query 'Parameter.Value' --output text \
  --profile mercadofresco-dev

Consum des de l'aplicació: boto3 i memòria cau

La versió ingènua —llegir el secret a cada petició— té tres problemes: latència de desenes de mil·lisegons, cost per crida i risc de superar la quota de peticions. La correcta fa servir memòria cau amb invalidació davant d'error d'autenticació:

import boto3
import json
import psycopg2
from botocore.exceptions import ClientError

SECRET = "mercadofresco/produccion/rds/mfadmin"

sessio = boto3.Session(region_name="eu-west-1")
sm = sessio.client("secretsmanager")

_cache = {"valor": None}


def obtenir_credencials(forcar_recarrega: bool = False) -> dict:
    """Retorna les credencials, fent servir la cache tret que es forci la recarrega."""
    if _cache["valor"] is None or forcar_recarrega:
        resposta = sm.get_secret_value(SecretId=SECRET)
        _cache["valor"] = json.loads(resposta["SecretString"])
    return _cache["valor"]


def connectar():
    """Connecta a la base de dades i reintenta UN cop si la credencial ha rotat."""
    for intent in (1, 2):
        credencials = obtenir_credencials(forcar_recarrega=(intent == 2))
        try:
            return psycopg2.connect(
                host=credencials["host"],
                port=credencials["port"],
                dbname=credencials["dbname"],
                user=credencials["username"],
                password=credencials["password"],
                connect_timeout=5,
            )
        except psycopg2.OperationalError as error:
            if "authentication" not in str(error).lower() or intent == 2:
                raise
            # La contrasenya ha rotat: s'invalida la cache i es reintenta

El bucle for intent in (1, 2) és tota la lògica que cal: primer intent amb la memòria cau, segon amb recàrrega forçada, i només es reintenta si l'error és d'autenticació. Un error de xarxa o de temps d'espera es propaga tal qual, perquè recarregar el secret no ho arreglaria.

Per a producció existeix la biblioteca oficial, que afegeix caducitat per temps i és més completa:

pip install aws-secretsmanager-caching
import boto3
from aws_secretsmanager_caching import SecretCache, SecretCacheConfig

client = boto3.client("secretsmanager", region_name="eu-west-1")
cache = SecretCache(
    config=SecretCacheConfig(
        secret_refresh_interval=3600,   # refrescar cada hora
        max_cache_size=1024,
    ),
    client=client,
)

credencials = json.loads(cache.get_secret_string("mercadofresco/produccion/rds/mfadmin"))

Amb secret_refresh_interval=3600 es fan 24 crides al dia per instància en lloc d'una per petició: de desenes de milers a dues dotzenes.

Consum des de Lambda, EC2, ECS i CloudFormation

Lambda: l'extensió de paràmetres i secrets. AWS publica una capa que arrenca un petit servidor HTTP local dins l'entorn d'execució i en fa la memòria cau per tu. S'afegeix la capa, es configura amb variables d'entorn i es consulta per localhost:

import os
import json
import urllib.request

PORT = os.environ.get("PARAMETERS_SECRETS_EXTENSION_HTTP_PORT", "2773")
TOKEN = os.environ["AWS_SESSION_TOKEN"]


def llegir_secret(nom: str) -> dict:
    url = f"http://localhost:{PORT}/secretsmanager/get?secretId={nom}"
    peticio = urllib.request.Request(url)
    peticio.add_header("X-Aws-Parameters-Secrets-Token", TOKEN)
    with urllib.request.urlopen(peticio) as resposta:
        return json.loads(json.loads(resposta.read())["SecretString"])


def handler(esdeveniment, context):
    credencials = llegir_secret("mercadofresco/produccion/rds/mfadmin")
    ...

L'extensió manté la memòria cau entre invocacions que reutilitzen el mateix entorn d'execució —recorda el model d'arrencades en fred i en calent de 02-05—, així que a la pràctica una funció molt invocada fa una crida cada cinc minuts en lloc d'una per invocació.

EC2. Amb el rol rol-mercadofresco-tienda assignat, boto3 troba les credencials sol. No hi ha res a configurar tret dels permisos.

ECS. Les definicions de tasca permeten injectar secrets directament com a variables d'entorn, sense que el codi cridi l'API. Es veurà a 10-01.

CloudFormation: referències dinàmiques. Es pot referenciar un secret sense escriure'l a la plantilla:

Resources:
  BaseDeDades:
    Type: AWS::RDS::DBInstance
    Properties:
      DBInstanceIdentifier: mercadofresco-pedidos
      MasterUsername: '{{resolve:ssm:/mercadofresco/produccion/basedatos/usuario}}'
      MasterUserPassword: '{{resolve:secretsmanager:mercadofresco/produccion/rds/mfadmin:SecretString:password}}'

La sintaxi {{resolve:secretsmanager:<secret>:SecretString:<camp>}} es resol en el moment del desplegament i el valor no apareix mai a la plantilla ni als esdeveniments de la pila. Es desenvolupa a 09-01.

Permisos mínims per llegir un secret

Aquesta és la política que s'adjunta a rol-mercadofresco-tienda:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "LeerSoloElSecretoDeLaBaseDeDatos",
      "Effect": "Allow",
      "Action": ["secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret"],
      "Resource": "arn:aws:secretsmanager:eu-west-1:111122223333:secret:mercadofresco/produccion/rds/mfadmin-??????"
    },
    {
      "Sid": "DescifrarConLaClaveDeDatos",
      "Effect": "Allow",
      "Action": "kms:Decrypt",
      "Resource": "arn:aws:kms:eu-west-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab",
      "Condition": {
        "StringEquals": {
          "kms:ViaService": "secretsmanager.eu-west-1.amazonaws.com"
        }
      }
    },
    {
      "Sid": "LeerLaConfiguracionDeProduccion",
      "Effect": "Allow",
      "Action": ["ssm:GetParameter", "ssm:GetParameters", "ssm:GetParametersByPath"],
      "Resource": "arn:aws:ssm:eu-west-1:111122223333:parameter/mercadofresco/produccion/*"
    }
  ]
}

Tres detalls que importen:

  • Els sis interrogants finals de l'ARN. Secrets Manager afegeix un sufix aleatori de 6 caràcters a l'ARN de cada secret (...mfadmin-AbCdEf). Si escrius l'ARN sense sufix, la política no coincideix amb res. -?????? fa servir el comodí d'un caràcter per cobrir-ho. És l'error número u amb Secrets Manager i el seu símptoma —AccessDenied amb una política aparentment correcta— desconcerta qualsevol. L'alternativa és acabar en -*, una mica més laxa.
  • kms:ViaService limitat a secretsmanager. La botiga pot desxifrar a través de Secrets Manager i d'S3 (per la declaració de 04-02), però no pot cridar kms:Decrypt directament contra qualsevol blob que aconsegueixi.
  • ssm:GetParametersByPath acotat a /mercadofresco/produccion/*. La botiga de producció no veu la configuració de desenvolupament, ni a l'inrevés.

I el que no hi ha: secretsmanager:ListSecrets, que revelaria els noms de tots els secrets del compte, ni accés a cap altre secret. La botiga llegeix exactament un. Si demà s'afegeix el secret de la passarel·la de pagament, caldrà una decisió explícita per concedir-lo.

La migració: treure la contrasenya del user data

Estat inicial del user data de lt-mercadofresco-tienda, tal com va quedar a 02-01:

#!/bin/bash
cat > /etc/mercadofresco/tienda.conf <<'EOF'
DB_HOST=mercadofresco-pedidos.abc123.eu-west-1.rds.amazonaws.com
DB_USER=mfadmin
DB_PASSWORD=ContrasenaEnClaroQueTodosVen
EOF
systemctl start mercadofresco-tienda

El pla de migració, en ordre i sense tallar el servei:

flowchart TD
    A["1. Crear el secret amb la<br/>contrasenya ACTUAL"] --> B["2. Crear els parametres<br/>de configuracio"]
    B --> C["3. Afegir permisos a<br/>rol-mercadofresco-tienda"]
    C --> D["4. Canviar el codi:<br/>llegir del secret amb reintent"]
    D --> E["5. Nova versio de<br/>lt-mercadofresco-tienda SENSE la clau"]
    E --> F["6. Renovacio progressiva<br/>de l'ASG"]
    F --> G["7. Verificar que tot funciona"]
    G --> H["8. Rotacio manual de prova"]
    H --> I["9. Activar rotacio cada 30 dies"]
    I --> J["10. Netejar: .env, historial,<br/>fitxers de configuracio"]

El pas 1 és crucial i contraintuïtiu: es guarda la contrasenya actual, sense canviar-la. Així els passos 1 a 7 no alteren res funcionalment i es poden revertir en qualsevol moment. La contrasenya només canvia al pas 8, quan tota la resta ja està provada.

Nou user data, sense ni una credencial:

#!/bin/bash
set -euo pipefail

REGION=eu-west-1
RUTA=/mercadofresco/produccion

# Configuració no sensible des de Parameter Store
DB_HOST=$(aws ssm get-parameter --region $REGION \
  --name "$RUTA/basedatos/punto-enlace" --query 'Parameter.Value' --output text)
DB_USER=$(aws ssm get-parameter --region $REGION \
  --name "$RUTA/basedatos/usuario" --query 'Parameter.Value' --output text)

cat > /etc/mercadofresco/tienda.conf <<EOF
DB_HOST=$DB_HOST
DB_USER=$DB_USER
SECRET_BD=mercadofresco/produccion/rds/mfadmin
REGION=$REGION
EOF

systemctl start mercadofresco-tienda

La contrasenya no s'escriu en cap fitxer: el user data deixa el nom del secret, i l'aplicació el llegeix en memòria en arrencar i quan el necessita. El user data, que és visible des de les metadades de la instància, ja no conté res aprofitable.

Desplegament amb renovació progressiva, com a 02-01:

aws ec2 create-launch-template-version \
  --launch-template-name lt-mercadofresco-tienda \
  --source-version '$Latest' \
  --launch-template-data "{\"UserData\":\"$(base64 -w0 /tmp/user-data-nuevo.sh)\"}" \
  --profile mercadofresco-dev

aws autoscaling start-instance-refresh \
  --auto-scaling-group-name asg-mercadofresco-tienda \
  --preferences '{"MinHealthyPercentage": 90, "InstanceWarmup": 120}' \
  --profile mercadofresco-dev

I per fi el pas 10, la neteja, que és la part que s'oblida:

  • Esborrar /etc/mercadofresco/tienda.conf de les instàncies antigues (que es destrueixen soles amb la renovació).
  • Esborrar el .env dels portàtils i reescriure l'historial de Git si va arribar a pujar-se.
  • Purgar l'historial d'ordres de les sessions on es va fer servir.
  • I, sobretot, rotar: la contrasenya anterior s'ha de considerar compromesa.

Què no cal guardar aquí

Ni Secrets Manager ni Parameter Store no són un magatzem de propòsit general:

No guardis Per què On va
Claus d'accés d'IAM d'un rol No haurien d'existir Rols i credencials temporals (04-01)
Fitxers o binaris Límit de 64 KB / 4 KB, i cost S3 xifrat amb KMS
Dades personals de clients No és una base de dades RDS xifrada
Certificats TLS públics Hi ha un servei dedicat i gratuït ACM
Claus privades SSH de llarga vida Haurien de ser efímeres EC2 Instance Connect, Session Manager
Configuració que canvia a cada petició Latència i quotes Memòria cau o base de dades

Un cas que mereix menció: per a RDS existeixen a més les credencials gestionades (--manage-master-user-password), on RDS crea i rota el secret automàticament sense que configuris res. És el més senzill si la base de dades és nova; MercadoFresco no ho fa servir perquè mercadofresco-pedidos ja existia, però per a una instància nova és la primera opció a considerar.

Auditoria de l'accés als secrets

Cada crida a GetSecretValue queda registrada a CloudTrail (05-03), amb qui, quan, des de quina IP i amb quin resultat. És la propietat que cap fitxer .env no pot oferir:

aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=EventName,AttributeValue=GetSecretValue \
  --start-time 2026-08-01T00:00:00Z \
  --query 'Events[].[EventTime,Username,CloudTrailEvent]' \
  --output text \
  --profile mercadofresco-dev

El que la Marta vigila:

  • Lectures des de principals inesperats —qualsevol que no sigui rol-mercadofresco-tienda o el rol de rotació.
  • Lectures fora d'horari o des d'IP desconegudes.
  • Un pic de lectures, que suggereix que algú està enumerant secrets.
  • DeleteSecret o PutResourcePolicy, que haurien de ser raríssims.

A 05-01 convertirem això en una alarma real de CloudWatch que avisi el tema alertas-mercadofresco; de moment n'hi ha prou de saber que el rastre existeix.

Detecció de secrets filtrats al repositori

Prevenir és més barat que rotar. Dues capes:

Al portàtil, abans del commit. git-secrets instal·la un hook que rebutja commits amb patrons de credencials:

git secrets --install
git secrets --register-aws
# Patrons propis de MercadoFresco
git secrets --add 'mfadmin.*password'
git secrets --add --allowed 'ContrasenaFicticia'   # excepcio per a la documentacio
git secrets --scan-history

--scan-history revisa tot l'historial, no només l'estat actual. Executar-lo el primer cop en un repositori amb anys de vida sol donar sorpreses.

Al pipeline. L'escaneig local es pot saltar; el del pipeline no. Eines com gitleaks, trufflehog o l'escaneig de secrets de la plataforma s'executen a cada git push i aturen la construcció. L'integrarem a la fase de compilació a 08-02, juntament amb les proves i l'anàlisi estàtica.

I una regla d'higiene que val més que qualsevol eina:

.env
.env.*
*.pem
*.key
credentials
config/secrets.yml

Cost, quotes i neteja

Concepte Preu
Secrets Manager: per secret 0,40 USD al mes (prorratejat per hora)
Secrets Manager: crides a l'API 0,05 USD per cada 10.000
Secrets Manager: rèplica en una altra regió 0,40 USD addicionals per regió
Parameter Store estàndard Gratis (10.000 paràmetres, 40 peticions/s)
Parameter Store avançat 0,05 USD per paràmetre i mes + 0,05 USD/10.000 crides
Rotació (invocacions de Lambda) Preu normal de Lambda: cèntims a l'any
KMS Ja comptabilitzat a 04-02

Càlcul per a MercadoFresco:

Concepte Quantitat Cost mensual
Secrets (mfadmin + passarel·la de pagament) 2 0,80 USD
Crides a Secrets Manager (6 instàncies × 24 lectures/dia × 30) ~4.320 0,02 USD
Paràmetres a Parameter Store estàndard 25 0,00 USD
Crides a Parameter Store (arrencades d'instància) ~2.000 0,00 USD
Lambda de rotació 1 invocació ×4 passos al mes ~0,00 USD
Total 0,82 USD/mes

Menys d'un dòlar al mes per eliminar el risc més comú i més car d'una infraestructura. Compara-ho amb el cost d'una bretxa notificable sota el RGPD i la discussió es tanca sola.

Neteja. Un secret no s'esborra immediatament: hi ha un període de recuperació d'entre 7 i 30 dies, per la mateixa raó que a KMS.

# Programar l'esborrat amb 30 dies de recuperació (recomanat)
aws secretsmanager delete-secret \
  --secret-id mercadofresco/produccion/rds/mfadmin \
  --recovery-window-in-days 30 --profile mercadofresco-dev

# Penedir-se'n
aws secretsmanager restore-secret \
  --secret-id mercadofresco/produccion/rds/mfadmin --profile mercadofresco-dev

# Esborrat immediat: irreversible, només en proves
aws secretsmanager delete-secret \
  --secret-id secreto-de-prueba --force-delete-without-recovery --profile mercadofresco-dev

# Paràmetres: esborrat immediat, sense xarxa de seguretat
aws ssm delete-parameter --name "/mercadofresco/desarrollo/tienda/prueba" \
  --profile mercadofresco-dev

Compte amb l'asimetria: els paràmetres s'esborren a l'instant i no es recuperen. Abans d'esborrar en bloc una ruta de Parameter Store, exporta'n una còpia.

Errors Habituals i Consells

Oblidar el sufix de sis caràcters a l'ARN del secret. És l'error més freqüent amb Secrets Manager. L'ARN real és ...secret:mercadofresco/produccion/rds/mfadmin-AbCdEf, i una política que acabi en mfadmin sense més no coincideix amb res. Fes servir -?????? o -*.

Desar el secret a la memòria cau sense invalidar-la davant d'un error d'autenticació. Cada rotació produeix llavors una tongada d'errors. El reintent únic són quatre línies de codi i ho resol.

Posar la Lambda de rotació fora de la VPC. No es podrà connectar a mercadofresco-pedidos i la rotació fallarà a setSecret, que és el pitjor moment possible. Ha d'estar a les subxarxes d'aplicació i sg-mercadofresco-basedatos ha d'acceptar el seu grup de seguretat.

Oblidar la sortida a l'API de Secrets Manager des de la subxarxa privada. La Lambda es queda penjada fins a esgotar el temps d'espera i el missatge d'error no diu res útil. Necessita NAT o un endpoint d'interfície.

Canviar els noms dels camps del JSON del secret. La funció de rotació gestionada espera engine, host, port, dbname, username, password. Si els reanomenes «perquè es llegeixin millor», la rotació no funciona.

Activar la rotació a producció sense haver-la provat. Prova-la primer en una còpia de la base de dades, i amb --rotate-immediately per no esperar 30 dies a descobrir que alguna cosa falla.

Guardar-ho tot a Secrets Manager perquè és «el lloc segur». 25 valors són 10 USD al mes en lloc de 0,80. La configuració no sensible va a Parameter Store.

Programar la rotació un dijous a la nit. El pic de MercadoFresco és el divendres. Rota els dimarts de matinada, quan hi ha dos dies laborables per endavant per reaccionar.

Consell: fes servir una convenció de noms des del primer dia. <projecte>/<entorn>/<servei>/<recurs> permet escriure polítiques per prefix i veure d'un cop d'ull a quin entorn pertany cada cosa.

Consell: guarda el punt d'enllaç dins del secret a més de a Parameter Store. És el que espera la rotació gestionada, i evita que l'aplicació hagi de combinar dues fonts per obrir una connexió.

Consell: posa una alarma sobre NextRotationDate. Una rotació que fa 45 dies que no s'executa en un secret configurat a 30 és una rotació trencada que ningú no ha vist.

Consell: audita cada trimestre qui pot llegir cada secret. L'accés creix sol, i list-secrets amb les polítiques de recurs dona la foto en un minut.

Exercicis

Exercici 1: repartir valors entre els dos serveis

MercadoFresco integra una passarel·la de pagament i un proveïdor de missatgeria. Apareixen aquests vuit valors:

  1. Clau d'API de producció de la passarel·la de pagament (rota cada 90 dies per política del proveïdor).
  2. URL base de l'API de la passarel·la: https://api.pasarela.example/v2.
  3. Identificador de comerç: MF-2026-ES-0042.
  4. Secret de signatura dels webhooks entrants de la passarel·la.
  5. Temps màxim d'espera de la passarel·la: 8 segons.
  6. Usuari i contrasenya de l'SFTP del proveïdor de missatgeria.
  7. Llista de codis postals amb repartiment en 24 h.
  8. Contrasenya d'una segona base de dades d'informes, de només lectura.

Decideix per a cadascun el servei, el tipus i el nom complet, seguint la convenció de MercadoFresco, i justifica els tres casos que consideris més discutibles. Calcula el cost mensual resultant.

Exercici 2: diagnosticar una rotació que falla

La Marta activa la rotació de mercadofresco/produccion/rds/mfadmin. La rotació s'executa i falla. Als registres de la Lambda apareix:

[ERROR] setSecret: Unable to connect to database with previous secret
of secret arn mercadofresco/produccion/rds/mfadmin-AbCdEf

I a Secrets Manager, describe-secret mostra una versió amb etapa AWSPENDING que fa tres hores que hi és, mentre AWSCURRENT continua apuntant a la versió antiga.

Respon: (a) en quin pas del cicle ha fallat i què significa exactament aquest missatge?; (b) enumera en ordre les quatre causes més probables i com comprovar cadascuna; (c) està l'aplicació caiguda en aquest moment i per què?; (d) què cal netejar abans de reintentar-ho?

Exercici 3: escriure la política del rol de rotació

Escriu la política de permisos completa del rol rol-rotacion-mfadmin, que assumeix la Lambda de rotació. Ha de poder: llegir i escriure el secret mercadofresco/produccion/rds/mfadmin (i només aquest), moure etapes de versió, generar contrasenyes aleatòries, fer servir alias/mercadofresco-datos a través de Secrets Manager, escriure els seus propis registres i funcionar dins de vpc-mercadofresco.

Solucions

Solució 1

# Valor Servei Tipus Nom
1 Clau d'API de pagament Secrets Manager mercadofresco/produccion/pasarela/clave-api
2 URL de la passarel·la Parameter Store String /mercadofresco/produccion/pasarela/url-base
3 Identificador de comerç Parameter Store String /mercadofresco/produccion/pasarela/id-comercio
4 Secret de signatura de webhooks Secrets Manager mercadofresco/produccion/pasarela/secreto-webhook
5 Temps d'espera Parameter Store String /mercadofresco/produccion/pasarela/tiempo-espera-segundos
6 Credencials SFTP Secrets Manager mercadofresco/produccion/mensajeria/sftp
7 Codis postals de 24 h Parameter Store StringList /mercadofresco/produccion/reparto/codigos-postales-24h
8 Contrasenya de la BD d'informes Secrets Manager mercadofresco/produccion/rds/informes

Casos discutibles:

  • L'identificador de comerç (3). Sembla sensible perquè identifica l'empresa davant del proveïdor, però per si sol no autoritza res: sense la clau d'API no serveix de res, i apareix a les factures. És configuració. Si el proveïdor el tractés com a credencial, canviaria la decisió.
  • El secret de signatura de webhooks (4). Podria semblar configuració perquè no «obre» res: només es fa servir per verificar signatures entrants. Però qui el conegui pot falsificar notificacions de pagament confirmat, és a dir, aconseguir comandes gratis. És un secret de primer ordre.
  • Els codis postals (7). No és secret en absolut —està publicat al web— però és configuració que canvia sovint i convé tenir-la fora del codi per poder ampliar la cobertura sense desplegar. StringList amb get-parameter i --query 'Parameter.Value' retorna la cadena separada per comes.

Cost: 4 secrets nous × 0,40 = 1,60 USD/mes, més els 2 anteriors = 2,40 USD/mes a Secrets Manager. Parameter Store: 4 paràmetres més, 0 USD. Si s'haguessin ficat els vuit valors a Secrets Manager: 3,20 USD només per aquests vuit, quatre vegades més, sense cap avantatge per als quatre que no roten.

Solució 2

(a) Ha fallat al pas setSecret, el segon del cicle. El missatge concret —«unable to connect with previous secret»— diu que la Lambda ha intentat connectar-se a la base de dades amb la credencial actual (AWSCURRENT) per poder executar l'ALTER USER, i no ha pogut. És a dir: el problema no és la contrasenya nova, és que la Lambda no aconsegueix autenticar-se amb la vella.

(b) Quatre causes, en ordre de probabilitat:

  1. Xarxa. La Lambda no és a vpc-mercadofresco, o és a les subxarxes equivocades, o sg-mercadofresco-basedatos no accepta el 5432 des del seu grup de seguretat. Comprovació: aws lambda get-function-configuration --query 'VpcConfig' i revisar les regles d'entrada de l'SG de la base de dades amb describe-security-groups.
  2. La contrasenya d'AWSCURRENT no és la real. Si en crear el secret s'hi va posar una contrasenya inventada en lloc de la que realment té mfadmin, la Lambda no es pot autenticar. Comprovació: intentar connectar-s'hi manualment amb psql amb el valor d'AWSCURRENT des d'un bastió.
  3. Els camps del JSON no són els esperats. Si falta dbname, port o engine, o si estan escrits d'una altra manera, la Lambda construeix malament la cadena de connexió. Comprovació: llegir el secret i comparar camp a camp amb el format esperat.
  4. El punt d'enllaç és incorrecte —per exemple, va quedar l'antic després de la migració a la instància xifrada de 04-02, que va canviar el DNS. Comprovació: aws rds describe-db-instances --query 'DBInstances[].Endpoint.Address' i comparar.

(c) No, l'aplicació no està caiguda. I aquesta és exactament la virtut del disseny d'etapes: AWSCURRENT continua apuntant a la versió antiga, que és la que continua funcionant a la base de dades, i és la que retorna get-secret-value. La rotació fallida és un problema operatiu urgent, però no un incident de servei. Si hagués fallat després de setSecret —amb la contrasenya ja canviada a la base de dades però AWSCURRENT sense promocionar— llavors sí que hi hauria tall.

(d) Abans de reintentar-ho cal eliminar la versió penjada a AWSPENDING, o Secrets Manager intentarà continuar la rotació a mitges en lloc de començar de zero:

aws secretsmanager describe-secret --secret-id mercadofresco/produccion/rds/mfadmin \
  --query 'VersionIdsToStages' --profile mercadofresco-dev

aws secretsmanager update-secret-version-stage \
  --secret-id mercadofresco/produccion/rds/mfadmin \
  --version-stage AWSPENDING \
  --remove-from-version-id <id-de-la-versio-pendent> \
  --profile mercadofresco-dev

Després, corregir la causa, provar en desenvolupament i reintentar amb --rotate-immediately.

Solució 3

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "GestionarSoloEsteSecreto",
      "Effect": "Allow",
      "Action": [
        "secretsmanager:DescribeSecret",
        "secretsmanager:GetSecretValue",
        "secretsmanager:PutSecretValue",
        "secretsmanager:UpdateSecretVersionStage"
      ],
      "Resource": "arn:aws:secretsmanager:eu-west-1:111122223333:secret:mercadofresco/produccion/rds/mfadmin-??????",
      "Condition": {
        "StringEquals": {
          "secretsmanager:resource/AllowRotationLambdaArn":
            "arn:aws:lambda:eu-west-1:111122223333:function:rotacion-mfadmin"
        }
      }
    },
    {
      "Sid": "GenerarContrasenasAleatorias",
      "Effect": "Allow",
      "Action": "secretsmanager:GetRandomPassword",
      "Resource": "*"
    },
    {
      "Sid": "UsarLaClaveSoloViaSecretsManager",
      "Effect": "Allow",
      "Action": ["kms:Decrypt", "kms:GenerateDataKey"],
      "Resource": "arn:aws:kms:eu-west-1:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab",
      "Condition": {
        "StringEquals": {
          "kms:ViaService": "secretsmanager.eu-west-1.amazonaws.com"
        }
      }
    },
    {
      "Sid": "Registros",
      "Effect": "Allow",
      "Action": ["logs:CreateLogStream", "logs:PutLogEvents"],
      "Resource": "arn:aws:logs:eu-west-1:111122223333:log-group:/aws/lambda/rotacion-mfadmin:*"
    },
    {
      "Sid": "InterfazDeRedEnLaVPC",
      "Effect": "Allow",
      "Action": [
        "ec2:CreateNetworkInterface",
        "ec2:DescribeNetworkInterfaces",
        "ec2:DeleteNetworkInterface"
      ],
      "Resource": "*"
    }
  ]
}

Justificació de cada bloc:

  • El secret, un de sol, amb el sufix -?????? i la condició secretsmanager:resource/AllowRotationLambdaArn, que assegura que aquest rol només pot operar sobre secrets la rotació dels quals està assignada precisament a aquesta Lambda. És una defensa extra davant que algú reutilitzi el rol.
  • GetRandomPassword no admet ARN de recurs; per això "Resource": "*". No és perillós: genera cadenes aleatòries i no dona accés a res.
  • KMS acotat amb kms:ViaService a Secrets Manager: el rol no pot desxifrar objectes d'S3 amb la mateixa clau.
  • Els registres del grup propi de la funció, amb el :* final.
  • Les tres accions d'ec2: són obligatòries per a qualsevol Lambda dins d'una VPC: crea i destrueix una interfície de xarxa elàstica a cada arrencada en fred. No admeten ARN concret perquè la interfície encara no existeix quan es comprova el permís. És una de les poquíssimes vegades en què "Resource": "*" és inevitable, i es pot acotar amb condicions sobre ec2:Subnet i ec2:SecurityGroup si es vol apurar.

No hi apareix secretsmanager:ListSecrets, que revelaria l'inventari de secrets del compte, ni cap acció d'RDS: la Lambda canvia la contrasenya connectant-s'hi per SQL, no per l'API d'AWS.

Conclusió

La contrasenya de mfadmin ja no és on no havia de ser. Ha sortit del user data de lt-mercadofresco-tienda, del fitxer de configuració de les instàncies, del .env del Luis i de l'historial d'ordres, i viu a mercadofresco/produccion/rds/mfadmin, un secret xifrat amb alias/mercadofresco-datos, protegit per una política de recurs que denega la seva lectura a tot principal que no sigui rol-mercadofresco-tienda, marta o el rol de rotació, i que rota sola cada 30 dies, els dimarts de matinada, sense que ningú no se n'hagi de recordar ni assumir el risc de fer-ho a mà.

Entens per què la rotació no talla el servei: el mecanisme d'etapes de versióAWSCURRENT, AWSPENDING, AWSPREVIOUS— i el cicle de quatre passos createSecretsetSecrettestSecretfinishSecret, on la contrasenya nova es crea, s'aplica i es prova abans de promocionar-se, i on una fallada a testSecret avorta tot el procés deixant la vella intacta. Saps també on és la finestra perillosa —entre setSecret i finishSecret— i per què l'aplicació ha d'invalidar la memòria cau i reintentar un cop davant d'un error d'autenticació: quatre línies que converteixen la rotació en una cosa invisible.

Distingeixes configuració de secret, i amb això quin dels dos serveis fer servir. La configuració de MercadoFresco viu a Parameter Store sota /mercadofresco/produccion/..., amb jerarquia per rutes que permet carregar-la sencera amb una crida i escriure permisos per prefix, amb versions i etiquetes per tornar enrere sense desplegar, i gratis al nivell estàndard. Els secrets viuen a Secrets Manager, a 0,40 USD cadascun. El total de la lliçó és 0,82 USD al mes, i saps argumentar per què ficar-ho tot al servei car hauria costat deu vegades més sense aportar res.

En la pràctica saps llegir un secret des de boto3 amb memòria cau i invalidació, des de Lambda amb l'extensió de paràmetres i secrets que en fa memòria cau entre invocacions, des d'EC2 amb el rol i sense configurar res, i des de CloudFormation amb referències dinàmiques {{resolve:secretsmanager:...}} que no escriuen mai el valor a la plantilla (09-01); a ECS s'injecten a la definició de tasca i ho veurem a 10-01. Coneixes els permisos mínims per llegir un secret —inclòs el sufix de sis caràcters de l'ARN, l'error que fa perdre més temps— i saps que el rastre de cada lectura queda a CloudTrail (05-03) i que l'escaneig de secrets s'automatitza a 08-02.

MercadoFresco ja té la casa endreçada cap endins: identitats mínimes, dades xifrades i credencials rotades. Però tot això protegeix d'amenaces que passen per la porta de l'API. Queda l'altra meitat del problema, la que arriba per la porta del davant i no necessita cap credencial. Recorda la regla DENY numerada 50 amb què a 03-02 vam bloquejar aquella IP que feia milers de peticions per minut: va funcionar perquè era una adreça. Si demà arriben peticions des de deu mil adreces diferents repartides per tot el món, aquesta regla no serveix de res, la NACL té un límit dur d'entrades, i el grup d'Auto Scaling reaccionarà com està dissenyat per reaccionar —arrencant instàncies— convertint l'atac en una factura. A la lliçó 04-04, «AWS Shield», veurem què és realment un atac de denegació de servei distribuïda, en què es diferencien els atacs volumètrics dels de capa d'aplicació, quina protecció ja tens activada i gratis a CloudFront, Route 53 i l'ALB, què hi afegeix Shield Advanced per 3.000 USD al mes —i si això té sentit per a una empresa de la mida de MercadoFresco—, i com es distingeix en calent un atac d'un pic legítim d'un divendres a la tarda.

Curs d'AWS

Mòdul 1: Introducció a AWS

Mòdul 2: Serveis principals d'AWS

Mòdul 3: Xarxes i lliurament de contingut

Mòdul 4: Seguretat i identitat

Mòdul 5: Monitoratge i gestió

Mòdul 6: Bases de dades

Mòdul 7: Integració d'aplicacions

Mòdul 8: Eines per a desenvolupadors

Mòdul 9: Infraestructura com a codi i govern de comptes

Mòdul 10: Contenidors a AWS

Mòdul 11: Millors pràctiques i gestió de costos

© Copyright 2026. Tots els drets reservats