Ús d’ AWS Secrets Manager per a contrasenyes en scripts
La gestió de credencials en scripts sempre ha estat un terreny incòmode. Durant anys, el més habitual ha estat lidiar amb variables d’entorn, arxius .env, paràmetres en línia de comandaments i altres mètodes més o menys insegurs o fràgils. AWS Secrets Manager ofereix una alternativa amb avantatges clars, però també amb particularitats que no convé ignorar si es vol fer servir bé.
Aquest article aborda l’ús de Secrets Manager específicament en el context de scripts: tasques automatitzades, cron jobs, wrappers interns, pipelins CI/CD que encara depenen de shell scripts, Python o eines similars. No es cobreix l’ús amb IAM rols a Lambda, ni la integració nativa en contenidors amb sidecars o agents, tot i que aquests escenaris tenen punts en comú.
Per què moure les contrasenyes a Secrets Manager?
La raó més comuna per a moure contrasenyes de scripts a Secrets Manager és que algú de seguretat ho demana. També pot ser perquè algú esborra per accident un .env sense backup. Però n’hi ha una de més rellevant: evitar que les credencials acabin als logs, en els historials de shell o a sistemes de monitorització per accident.
Secrets Manager no evita errors humans ni “assegura” màgicament els teus scripts. El que fa bé és centralitzar, auditar i automatitzar la rotació de secrets. Això ja justifica el canvi, encara que no tots els seus usos valen la pena en tots els contextos. Sovint complica tasques simples si no es configura bé.
Llegir secrets des de scripts: opcions
Hi ha tres formes comunes de consumir secrets en scripts:
- CLI (aws secretsmanager get-secret-value)
- SDK (per exemple, boto3 a Python)
- Injecció prèvia a l’execució (export/env o subshell)
Cadascuna té els seus avantatges i limitacions. No convé triar per costum, sinó per context.
Opción 1: AWS CLI
aws secretsmanager get-secret-value \
--secret-id prod/db/password \
--query SecretString \
--output text
Aquest mètode és directe, però comporta una sèrie de riscos subtils:
- El secret pot aparèixer en ps aux si es construeix la crida malament (fent servir eval, per exemple).
- Si el valor és un JSON, es necessita un pas addicional amb jq, cosa que complica el parsing per a scripts antics.
- L’ accés falla sense un perfil IAM adequat. I si s’executa dins de cron o des de Jenkins, això tendeix a trencar-se més sovint del que hauria de fer.
A més, per defecte la comanda no catxeja res. Si un script l’anomena cinc vegades en un cicle, es fan cinc crides a AWS. Això no és trivial en termes de latència ni de cost si escala.
Recomanació: encapsular aquesta trucada en una funció amb catxe temporal.
get_secret() {
local secret_id=$1
local cache="/tmp/secret_cache_${secret_id//\//_}"
if [[ -f "$cache" && $(find "$cache" -mmin -5) ]]; then
cat "$cache"
else
aws secretsmanager get-secret-value \
--secret-id "$secret_id" \
--query SecretString \
--output text > "$cache"
cat "$cache"
fi
}
Aquest patró evita múltiples crides innecessàries. No és infal·lible, però millora la situació si no es vol muntar un sidecar o sistema més complex.
Opció 2: SDK en scripts (Python, Node.js)
Si l’script ja està escrit en un llenguatge amb bon suport SDK (Python amb boto3, Node.js amb aws-sdk), l’accés a Secrets Manager és més net i controlable. Per exemple:
import boto3
import json
client = boto3.client("secretsmanager")
response = client.get_secret_value(SecretId="prod/db/password")
secret = json.loads(response["SecretString"])
password = secret["password"]
Aquest enfocament permet gestionar errors amb més precisió, aplicar polítiques de reintent i formatejar la sortida sense dependre de jq ni redireccions a bash. També és menys propens a fallades en entorns multifil o concurrents, encara que no és immune a problemes si es comparteix estat (per exemple, en catxejar el secret globalment en un procés llarg).
Compte amb: errors per polítiques IAM mal definides. Si l’ script s’ executa amb un rol sense permisos, boto3 llança una excepció, però no sempre es gestiona correctament. És habitual que en pipelines fallides l’error quedi ocult després de diverses capes de retry o logs truncats.
Opció 3: Injecció prèvia o embolcalls
Una altra pràctica comuna és recuperar els secrets abans d’executar l’script, carregant-los com a variables d’entorn. Això es pot fer des del wrapper del pipeline o en un shell wrapper:
export DB_PASS=$(aws secretsmanager get-secret-value \
--secret-id prod/db \
--query 'SecretString' \
--output text | jq -r .password)
./run_migration.sh
Això simplifica la lògica dins de l’script, que pot continuar llegint DB_PASS com sempre. Però introdueix un risc: qualsevol error de logging o debug pot fer que la variable acabi en un output no desitjat.
Una forma de mitigar-lo és restringir l’entorn del procés amb env -i o sanititzar la sortida de l’script. Una altra és embolicar la lògica de l’script en un subshell amb un entorn limitat:
(
export DB_PASS="..."
./script_que_no_ha_de_sortir_res.sh
)
Això evita que les variables quedin vives fora del subshell.
Bones pràctiques
1. No guardar els secrets com a plain text quan són JSON
Secrets Manager permet emmagatzemar strings simples o JSON. Fer servir JSON és més flexible, però també més propens a errors si no es controla bé el contingut.
Evita guardar secrets com:
"{\"password\":\"abc\",\"username\":\"root\"}"
Això passa quan es fa servir aws secretsmanager put-secret-value sense serialitzar bé. Després recuperar-lo és un embolic: necessites doble parseig amb jq o doble json.loads() a Python.
És millor fer servir JSON directe, no strings JSON-encoded:
aws secretsmanager put-secret-value \
--secret-id prod/db \
--secret-string '{"username":"root","password":"abc"}'
Així, en recuperar-lo, el camp SecretString ja és JSON i no una string de JSON, evitant errors comuns en scripts automatitzats.
2. Noms de secrets predibles i consistents
La nomenclatura importa més del que sembla. Els noms com prod/db/password són fàcils de raonar, però si hi ha múltiples entorns o regions, el millor és fer servir convencions explícites:
- env/app/component
- dev/myapp/api-db
- prod/myapp/admin-user
Això permet fer cerques i rotacions programades més fàcils. També es recomana evitar secrets compartits entre entorns o comptes; fins i tot si la contrasenya és la mateixa, el secret hauria de ser diferent.
3. Evitar accés des de múltiples comptes sense configuració explícita
El cross-account access a Secrets Manager es pot fer, però no és trivial. Si s’intenta accedir a un secret en un altre compte sense polítiques correctes, l’error és poc clar: “AccessDeniedException” sense més pistes.
Si un secret s’ha de fer servir des d’un altre compte (per exemple, un compte de CI que necessita connectar-se a la base de dades de producció), convé fer servir Resource Policies explícites i documentades. Exemple:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCrossAccountAccess",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111122223333:role/CICDRunner"
},
"Action": "secretsmanager:GetSecretValue",
"Resource": "*"
}
]
}
Sense això, l’script falla i l’equip de CI queda bloquejat sense feedback clar. Sempre cal provar l’ accés des del compte de destinació abans d’ automatitzar-lo.
4. No rotar automàticament secrets sense client preparat
Secrets Manager permet configurar rotació automàtica de secrets. Sembla bona idea, fins que el client que els consumeix no està preparat per a detectar el canvi.
Exemple clàssic: un script en cron diari que fa servir una contrasenya per a connectar-se a un sistema legacy. Si el secret canvia cada 7 dies però el client catxeja la contrasenya a l’inici del contenidor i no reintenta, comença a fallar sense diagnòstic clar.
Sempre que es configuri rotació automàtica, cal assegurar-se que:
- El client recupera el secret cada vegada, no només en arrencar.
- Hi ha una estratègia de fallback o retry.
- La rotació es prova en entorns no crítics abans.
5. Auditar els accessos i definir polítiques mínimes
No tots els scripts necessiten accés complet a tots els secrets. Sovint s’ atorguen permisos excessius per comoditat, com ara:
{
"Effect": "Allow",
"Action": "secretsmanager:GetSecretValue",
"Resource": "*"
}
És preferible acotar per ARN o per prefix, i incloure secrets: ListSecrets només quan es necessiti.
També és útil habilitar CloudTrail per auditar accessos i detectar patrons anòmals. Si un cron diari accedeix al secret 100 vegades al dia, alguna cosa està malament a la lògica.
Fallbacks locals: quan té sentit un .env de recolzament?
Tot i que la idea és eliminar els .env, en entorns on la xarxa d’AWS no sempre està disponible (màquines locals, entorns aïllats, entorns de staging on es fan proves amb simulacions de fallades de xarxa), mantenir un fallback local pot ser raonable.
No es tracta de tornar a fer servir .env com a font principal, sinó com a recolzament controlat en cas d’error en obtenir el secret des de Secrets Manager. Un patró que pot funcionar és:
DB_PASS=$(aws secretsmanager get-secret-value \
--secret-id prod/db \
--query 'SecretString' \
--output text 2>/dev/null | jq -r .password)
# fallback si falla la descàrrega
if [[ -z "$DB_PASS" || "$DB_PASS" == "null" ]]; then
echo "Warning: fent servir fallback local" >&2
DB_PASS=$(grep DB_PASS .env | cut -d '=' -f2-)
fi
Aquest tipus de fallback ha d’anar acompanyat d’alertes explícites (per exemple, enviar un avís a un canal de monitoratge si es fa servir l’env) i no s’ha de quedar actiu en producció sense supervisió. Idealment, l’arxiu .env no ha d’existir fora d’entorns locals o de testing.
Integració amb sistemes externs (bases de dades, APIs privades, eines CLI)
Quan el secret no el consumeix directament l’script, sinó que s’injecta en una eina externa (per exemple, psql, mongodump, curl, etc.), cal parar atenció a com es passa la credencial.
Exemple amb psql:
export PGPASSWORD=$(get_secret "prod/db" | jq -r .password)
psql -h db.example.com -U appuser -d appdb -c "SELECT 1"
El problema aquí és que PGPASSWORD estarà a l’entorn del procés psql. Algunes eines exposen l’entorn si fallen o si es llança amb strace o en mode debug.
Millor opció: arxiu temporal amb permisos restringits:
SECRET=$(get_secret "prod/db")
cat > /tmp/.pgpass.$$ <<EOF
db.example.com:5432:appdb:appuser:$(echo "$SECRET" | jq -r .password)
EOF
chmod 600 /tmp/.pgpass.$$
PGPASSFILE=/tmp/.pgpass.$$ psql -h db.example.com -U appuser -d appdb -c "SELECT 1"
rm -f /tmp/.pgpass.$$
Aquest patró redueix el risc d’ exposició i és compatible amb la majoria d’ eines que suporten arxius de configuració de credencials.
Diferències entre Secrets Manager i Parameter Store
És comú que dins de l’equip hi hagi confusió entre AWS Secrets Manager i SSM Parameter Store. Tots dos poden emmagatzemar valors segurs, però la seva semàntica i comportament operatiu són diferents.
| Feature | Secrets Manager | Parameter Store SSM (segur) |
| Rotació automàtica | Sí | No nadiu |
| Cost | Alt ($0.40 per secret/mes) | Descàrrega gratuïta |
| Auditoria | Detallada | Menys granular |
| Mida màxima | 64 KB | 4 KB per paràmetre |
| Versionat | Sí (automàtic) | Sí, però manual |
Si només es necessita una contrasenya que canvia poc i no requereix rotació automàtica, Parameter Store pot ser suficient. Però si hi ha una política de rotació cada 30 dies, integració amb RDS o requisits d’auditoria, Secrets Manager és preferible.
On falla la decisió és quan es barregen tots dos sense una política clara. Això porta a scripts amb lògica duplicada: uns criden a get-parameters, altres a get-secret-value, i el manteniment es torna difícil. És millor estandarditzar per tipus d’ús: Passwords i tokens → Secrets Manager. Flags, toggles i config → Parameter Store.
Errors comuns que convé evitar des del començament
- Codificar l’ ARN complet del secret als scripts: fa el codi menys portable i més difícil de mantenir. Sempre que es pugui, fer servir el nom lògic (prod/db) i configurar l’ARN via permisos IAM.
- Tenir múltiples scripts amb lògica d’ obtenció duplicada. Si tots criden a Secrets Manager a la seva manera, el manteniment es torna inviable. Crear un wrapper comú o funció compartida per a gestionar això estalvia temps.
- No configurar un timeout explícit en scripts que criden a AWS. Si l’endpoint de Secrets Manager està lent, aws o boto3 pot quedar-se esperant indefinidament. Millor definir –cli-read-timeout o botocore timeouts.
- Emmagatzemar secrets innecessaris: contrasenyes de sistemes que ja suporten IAM (com RDS amb auth federada), o claus API de serveis que poden ser reemplaçats per rols. De vegades el secret no és necessari si es redissenya el flux.
Conclusió pràctica
Fer servir AWS Secrets Manager en scripts no és només qüestió de seguretat, sinó de previsibilitat operativa. Si s’implementa malament, es guanya una falsa sensació de control mentre s’introdueixen fallades difícils de depurar. Si s’ implementa bé, permet gestionar secrets de forma centralitzada, auditable i amb menys dependència de l’ entorn local o de configuració manual.
Les claus perquè funcioni bé són:
- Centralitzar l’ accés i evitar duplicació.
- Controlar l’entorn d’execució (evitar filtracions).
- Pensar en errors des del disseny (fallbacks, timeouts, retries).
- Tenir una convenció clara per a noms, formats i rotació.
L’ús de Secrets Manager en entorns moderns és gairebé inevitable. Però continua sent una eina, no una garantia. Que els scripts estiguin segurs depèn més de la cura en els detalls que de l’ús d’eines sofisticades.
Si treballes amb scripts automatitzats en contextos sensibles, adoptar patrons sòlids amb Secrets Manager t’estalvia més que ensurts: també temps de debugging, falses alarmes i incidents innecessaris.

