Uso Bitwarden para mis contraseñas, como todo el mundo, y también como almacén de los .env de cada proyecto y como fuente de secretos del pipeline de despliegue. Cero secretos en repositorios, rotación en un solo sitio, y un runner que los pide solo cuando despliega.
Ingenieria
Soft2Help (Internal)
Bash, Bitwarden CLI, jq, Docker, SSH, YAML
Los secretos de un proyecto (claves de API, contraseñas de base de datos, tokens) tienen la mala costumbre de acabar donde no deben: en un chat, en un README "temporal", en un pendrive, o directamente commiteados. Y cuando cambias una clave, empieza la caza: ¿en cuántas máquinas y proyectos estaba copiada?
Cada proyecto guarda sus ficheros .env como adjuntos cifrados de un elemento de Bitwarden. El proyecto solo conserva en local un fichero .bwitem (ignorado por git) con el ID de su elemento. El repositorio no ve un secreto jamás.
# Subir los .env del proyecto actual a su elemento de Bitwarden
bw_push_secrets production
# Recuperarlos en una máquina nueva
bw_get_secrets production
El truco que los hace cómodos: si no pasas nombre de elemento, usan el nombre de la carpeta del proyecto. Entrar en el directorio y teclear el comando, sin recordar nada:
bw_push_secrets() {
local ENVIRONMENT="${1:-development}"
local ITEM_NAME="${2:-$(basename "$PWD")}"
_bw_run push_secrets "$ENVIRONMENT" "$ITEM_NAME"
}
Al subir, el adjunto anterior se reemplaza (Bitwarden no sobreescribe adjuntos, así que hay que borrar el viejo y crear el nuevo):
OLD_ID=$(bw get item "$ITEM_ID" | \
jq -r --arg file "$FILE" '.attachments // [] | .[] | select(.fileName == $file) | .id')
if [ -n "$OLD_ID" ]; then
bw delete attachment "$OLD_ID" --itemid "$ITEM_ID" --quiet
fi
bw create attachment --file "$FILE" --itemid "$ITEM_ID" --quiet
Montar el entorno en una máquina nueva son dos comandos, no una tarde buscando claves por chats antiguos. Y rotar una clave se hace en un sitio: el siguiente bw_get_secrets (o el siguiente despliegue) ya la lleva.
La misma idea, sin humanos: el runner de CI recupera el .env.production de Bitwarden en el momento de desplegar, dentro de un contenedor efímero. Ni el runner ni el repositorio guardan secretos de forma persistente:
docker run --rm -i \
-e BW_CLIENTID -e BW_CLIENTSECRET -e BW_PASSWORD \
-v "$PWD:/work" -w /work \
s2h-bw-cli <<'SCRIPT'
bw login --apikey
export BW_SESSION=$(bw unlock --passwordenv BW_PASSWORD --raw)
bw sync
bw get attachment ".env.production" --itemid "$BW_ITEM" --output .env.production
SCRIPT
Bitwarden limita los logins con API key muy seguidos: dos despliegues en la misma tarde y bw unlock empieza a devolver sesiones vacías de forma intermitente. La solución que quedó en el pipeline, reintentos con espera creciente:
for i in 1 2 3 4; do
bw logout >/dev/null 2>&1 || true
bw login --apikey >/dev/null 2>&1 || true
BW_SESSION=$(bw unlock --passwordenv BW_PASSWORD --raw 2>/dev/null || true)
[ -n "$BW_SESSION" ] && break
sleep $((i*15))
done
Detalle que también muerde: cada push crea el elemento si no existe. Práctico, hasta que un despiste te crea un elemento duplicado y el pipeline sigue leyendo el antiguo. Desde entonces, el .bwitem manda y se versiona el ID en la documentación interna del proyecto.