corentin_wakdo/docker/cron/scripts/backup-db.sh
Imugiii b85563b1b8
All checks were successful
CI / secret-scan (pull_request) Successful in 8s
CI / static-tests (pull_request) Successful in 34s
CI / auto-merge (pull_request) Successful in 5s
CI / php-lint (pull_request) Successful in 22s
CI / secret-scan (push) Successful in 9s
CI / php-lint (push) Successful in 24s
CI / static-tests (push) Successful in 37s
CI / auto-merge (push) Has been skipped
fix(db): moindre privilege pour le user applicatif (drop GRANT ALL)
L'image mariadb cree MARIADB_USER avec GRANT ALL PRIVILEGES sur la base, ce qui
donnait au code back-office expose un acces DDL/DROP/GRANT dont il n'a aucun
usage (les migrations tournent en root). La doc (compose, backup-db.sh) decrivait
pourtant un moindre privilege jamais applique.

- db/init/10-scope-app-user.sh : script d'init MariaDB (volume vierge) qui REVOKE
  ALL puis GRANT le set restreint SELECT/INSERT/UPDATE/DELETE + SHOW VIEW/TRIGGER/
  LOCK TABLES (DML + besoins mysqldump), parametre sur MARIADB_USER/DATABASE.
- docker-compose.yml : montage de db/init en /docker-entrypoint-initdb.d (ro).
- backup-db.sh : commentaire aligne sur le set reel (mysqldump --single-transaction
  n'exige que SELECT + SHOW VIEW/TRIGGER).

Verifie : sur volume vierge le user ressort scope (plus de ALL PRIVILEGES) ; sur
la base dev (scopee manuellement, hors volume vierge) /api/health=200 (SELECT) et
les 13 tests d'integration passent (DML create/update/delete en tant que wakdo).
2026-06-16 11:53:38 +00:00

98 lines
3.1 KiB
Bash

#!/usr/bin/env bash
#
# Wakdo - backup quotidien de la BDD
#
# Execute par cron a 03h00 (voir /etc/crontabs/root).
# Dump complet de la BDD dans /backups (bind-mount vers ./var/backups),
# compresse gzip, horodate, rotation sur 14 derniers dumps.
#
# Variables d'env lues (injectees par docker-compose depuis .env) :
# - DB_HOST
# - DB_PORT
# - DB_NAME
# - DB_USER (on utilise le user applicatif, pas root)
# - DB_PASSWORD
#
# Le USER applicatif a un privilege restreint (moindre privilege) : DML
# (SELECT/INSERT/UPDATE/DELETE) + SHOW VIEW, TRIGGER, LOCK TABLES sur wakdo,
# sans DDL ni GRANT OPTION. mysqldump --single-transaction (ci-dessous) n'exige
# que SELECT (+ SHOW VIEW/TRIGGER pour ces objets). Privileges poses par
# db/init/10-scope-app-user.sh (volume vierge) ou manuellement (base existante).
#
# Exit codes :
# 0 - backup OK
# 1 - variables env manquantes
# 2 - mysqldump a echoue
# 3 - rotation a echoue
set -euo pipefail
BACKUP_DIR="/backups"
RETENTION_DAYS=14
TIMESTAMP="$(date +%Y%m%d_%H%M%S)"
DUMP_FILE="${BACKUP_DIR}/wakdo_${TIMESTAMP}.sql.gz"
log() {
echo "[backup-db $(date -Iseconds)] $*" >&2
}
# --- Verification variables ---
for var in DB_HOST DB_PORT DB_NAME DB_USER DB_PASSWORD; do
if [ -z "${!var:-}" ]; then
log "ERROR: variable $var vide ou non definie"
exit 1
fi
done
# --- Verification que /backups est ecrivable ---
if [ ! -w "${BACKUP_DIR}" ]; then
log "ERROR: ${BACKUP_DIR} n'est pas ecrivable (verifier bind-mount et permissions)"
exit 1
fi
# --- Dump ---
log "demarrage dump BDD ${DB_NAME} depuis ${DB_HOST}:${DB_PORT}"
# --single-transaction : dump consistent sans LOCK TABLES (innodb only).
# --routines --triggers : inclut procedures stockees et triggers (si on en a).
# --no-tablespaces : evite le besoin de PROCESS privilege.
if ! mysqldump \
--host="${DB_HOST}" \
--port="${DB_PORT}" \
--user="${DB_USER}" \
--password="${DB_PASSWORD}" \
--single-transaction \
--routines \
--triggers \
--no-tablespaces \
--default-character-set=utf8mb4 \
"${DB_NAME}" \
| gzip -9 > "${DUMP_FILE}"; then
log "ERROR: mysqldump a echoue"
# Supprime un dump partiel si mysqldump a commence a ecrire.
rm -f "${DUMP_FILE}"
exit 2
fi
# --- Verification du dump ---
# Un dump vide ou quasi-vide = probleme silencieux (ex: mauvais USER GRANT).
# On exige une taille min de 512 octets pour alerter tot.
DUMP_SIZE="$(stat -c '%s' "${DUMP_FILE}")"
if [ "${DUMP_SIZE}" -lt 512 ]; then
log "ERROR: dump suspect (${DUMP_SIZE} octets), supprime pour ne pas polluer la rotation"
rm -f "${DUMP_FILE}"
exit 2
fi
log "dump OK : ${DUMP_FILE} (${DUMP_SIZE} octets)"
# --- Rotation : supprime les dumps plus vieux que RETENTION_DAYS ---
if ! find "${BACKUP_DIR}" -maxdepth 1 -type f -name 'wakdo_*.sql.gz' \
-mtime "+${RETENTION_DAYS}" -delete; then
log "WARNING: rotation incomplete"
exit 3
fi
REMAINING="$(find "${BACKUP_DIR}" -maxdepth 1 -type f -name 'wakdo_*.sql.gz' | wc -l)"
log "rotation OK : ${REMAINING} dumps conserves"
exit 0