xpeditis2.0/docs/CI-CD-SECURITY.md
David 38a2e08062
All checks were successful
CD Preprod / Security gate (push) Successful in 29s
CD Preprod / Backend — Lint (push) Successful in 1m2s
CD Preprod / Backend — Unit Tests (push) Successful in 1m7s
CD Preprod / Frontend — Lint & Type-check (push) Successful in 1m11s
CD Preprod / Frontend — Unit Tests (push) Successful in 41s
CD Preprod / Backend — Integration Tests (push) Successful in 46s
CD Preprod / Build Frontend (push) Successful in 32s
CD Preprod / Build Backend (push) Successful in 53s
CD Preprod / Build Log Exporter (push) Successful in 32s
CD Preprod / Image security (${{ matrix.service }}, ${{ matrix.arch }}) (amd64, frontend) (push) Successful in 23s
CD Preprod / Image security (${{ matrix.service }}, ${{ matrix.arch }}) (amd64, backend) (push) Successful in 25s
CD Preprod / Image security (${{ matrix.service }}, ${{ matrix.arch }}) (amd64, log-exporter) (push) Successful in 21s
CD Preprod / Image security (${{ matrix.service }}, ${{ matrix.arch }}) (arm64, backend) (push) Successful in 24s
CD Preprod / Image security (${{ matrix.service }}, ${{ matrix.arch }}) (arm64, frontend) (push) Successful in 23s
CD Preprod / Image security (${{ matrix.service }}, ${{ matrix.arch }}) (arm64, log-exporter) (push) Successful in 22s
CD Preprod / Deploy to Preprod (push) Successful in 44s
CD Preprod / Notify Failure (push) Has been skipped
CD Preprod / Notify Success (push) Successful in 2s
fix preprod
2026-09-26 14:01:29 +02:00

17 KiB
Raw Blame History

CI/CD sécurisée — Gitea 1.22.6

La version du serveur https://gitea.ops.xpeditis.com a été vérifiée via /api/v1/version : 1.22.6. Les pipelines ciblent Gitea Actions / act_runner, avec des jobs exécutés dans des conteneurs Linux AMD64 ou ARM64.

Workflows et contrôles

Les quatre workflows actifs se trouvent dans .gitea/workflows/. Les anciennes copies .github/workflows/ sont déplacées pour éviter une double exécution. Les étapes de sécurité sont exposées directement dans chaque workflow pour que Gitea affiche leurs journaux ; les scripts restent communs. Aucun workflow réutilisable GitHub ni client gh n'est nécessaire.

Pipeline Déclenchement Contrôles
Dev CI Push/PR vers dev Lint, types, tests unitaires, sécurité
PR Checks PR vers preprod ou main Idem + tests d'intégration PostgreSQL/Redis
CD Preprod Push vers preprod Idem + build, scan AMD64/ARM64, déploiement, contrôles HTTP
CD Production Push vers main Qualité/tests/sécurité, provenance préprod, build frontend, scan AMD64, déploiement SSH, tests de fumée

Le job Security gate exécute :

  • npm audit --package-lock-only --audit-level=high sur la racine, le backend, le frontend et le log-exporter, sans installer de dépendances ni exécuter leurs scripts. Les dépendances de développement sont incluses.
  • Trivy sur les fichiers suivis du commit : détection de secrets et de mauvaises configurations Docker/Kubernetes/Terraform, seuil HIGH/CRITICAL.
  • La validation statique des workflows et les tests des scripts de promotion/santé.

Les images sont analysées par digest, avec HIGH/CRITICAL bloquants, y compris lorsqu'aucun correctif n'est disponible. Les erreurs de scanner ou de registre échouent aussi : aucune exclusion générale ni échec masqué n'est ajouté. Les installations applicatives utilisent npm ci, le runtime est Node 22 et les actions sont épinglées par SHA. Trivy, Actionlint et Hetzner CLI sont téléchargés avec détection native AMD64/ARM64 et vérification SHA256. Le lint backend ne réécrit plus les fichiers.

Les rapports sont joints aux exécutions Gitea Actions avec actions/upload-artifact v3, compatible avec le protocole de cette version Gitea. security-reports est conservé 7 jours, image-security-* 14 jours selon la politique de rétention du serveur. Les valeurs et extraits de code des secrets sont retirés du rapport téléchargeable. Les URLs GitHub servent uniquement à récupérer les actions publiques épinglées, pas à exécuter les pipelines.

Ces scans ne remplacent pas un audit du code métier ni un test d'intrusion ; la recherche de secrets porte sur le commit courant, pas tout l'historique.

Promotion sans API GitHub

Gitea 1.22.6 n'expose pas d'API de consultation des runs Actions dans son Swagger. La provenance utilise donc le registre Scaleway existant : après scans, webhooks et contrôles HTTP backend/frontend réussis, le job préprod publie des marqueurs validated-preprod-<SHA complet> pour le backend et le log-exporter, à partir des digests construits et scannés. Le log-exporter est construit/scanné par cette chaîne ; son déploiement n'a pas de webhook ni de contrôle HTTP dédié existant.

La production accepte seulement un commit de main ou un de ses parents dont l'arbre Git est identique, avec les deux marqueurs présents. Elle récupère leurs digests, les rescane et les promeut sans reconstruire le backend/log-exporter. Le frontend est reconstruit avec les URLs prod puis scanné par digest. Les tags prod-SHA et latest ne sont publiés que dans le job de déploiement, après scans.

Utiliser un merge classique ou fast-forward de préprod vers main ; un squash ou rebase qui supprime cette provenance sera refusé. Il faut une première préprod réussie avec ces nouveaux workflows avant de promouvoir en production. La preuve repose sur les droits du registre : réserver l'écriture des marqueurs au compte CI et ne pas les créer manuellement. Ce n'est pas une attestation cryptographique.

Les images candidates peuvent rester dans le registre après un échec de scan, sans être publiées sous les alias d'environnement par le pipeline.

Configuration nécessaire sur ton instance

Gitea 1.22 ignore notamment concurrency, permissions, environment, les délais YAML de jobs et workflow_dispatch. Ces paramètres ne sont donc pas présentés comme des protections effectives dans les workflows adaptés.

  1. Activer Actions dans le dépôt et disposer d'un runner Docker Linux AMD64 ou ARM64 avec le label ubuntu-latest, Git, Bash, Python 3, curl, tar, timeout et les outils Docker. Les builds multiarchitecture ont besoin du support QEMU/binfmt. Les services d'intégration sont joints via postgres et redis, sans ports hôte fixes ; un runner en mode host n'est pas la cible de ces workflows.
  2. Les déploiements préprod et production utilisent le runner existant avec le label ubuntu-latest, comme les builds et les audits. Aucun runner dédié xpeditis-deploy n'est nécessaire. Ce runner doit aussi disposer de SSH et rsync pour le déploiement de production. Gitea 1.22 ne prenant pas en charge concurrency, les workflows ne garantissent pas la sérialisation des déploiements si les runners autorisent plusieurs jobs simultanés. Pour éviter les chevauchements, attendre la fin d'un déploiement avant de déclencher le suivant sur le même environnement.
  3. Protéger dev, preprod, main dans les réglages de branches Gitea : interdire les push directs/force-push et exiger Security gate, les deux contrôles qualité et les deux tests unitaires ; ajouter l'intégration pour preprod/main. Choisir les noms exacts proposés après la première exécution. Les approbations humaines doivent être imposées sur les PR, pas via environment.
  4. Renseigner les secrets/variables dans Settings → Actions du dépôt Gitea : registre, webhooks Portainer, URLs de build et identifiants SSH/Hetzner déjà référencés. Les secrets ne sont pas des secrets d'environnement GitHub. Les clés SSH temporaires sont écrites dans le répertoire du job. Les contrôles de santé préprod utilisent par défaut https://api.preprod.xpeditis.com/api/v1/health et https://app.preprod.xpeditis.com/api/health, conformément aux routes Traefik de la stack. Le contrôle frontend cible son endpoint de santé pour éviter la redirection HTTP 307 de la page d’accueil. PREPROD_BACKEND_URL et PREPROD_FRONTEND_URL sont des surcharges facultatives (URL de base sans chemin API), lues dans les secrets puis les variables du dépôt. Une réponse HTTP 200 reste obligatoire pour valider le déploiement.
  5. Les fonctions manuelles workflow_dispatch ne sont pas disponibles sur 1.22. L'ancien workflow de rollback est conservé hors du dossier actif dans .gitea/manual/rollback.reference.yml comme référence, sans prétendre qu'il est exécutable sur cette version. Le rollback SSH automatique de production reste actif en cas d'échec du déploiement ou des tests de fumée. Pour une intervention manuelle, utiliser la procédure serveur existante ; ne pas ajouter un faux bouton de lancement Gitea.

Ces réglages serveur/runners n'ont pas été modifiés depuis cette session. Aucun pipeline distant ni déploiement n'a été lancé.

Mises à jour de dépendances

Dependabot a été remplacé par renovate.json. La configuration propose des PR vers dev pour npm, Docker et les actions, sans fusion automatique. Elle n'installe ni ne démarre un bot. Un service Renovate doit être configuré séparément avec RENOVATE_PLATFORM=gitea, RENOVATE_ENDPOINT=https://gitea.ops.xpeditis.com/api/v1, un compte bot et son token, et le dépôt David/xpeditis2.0. Conserver le token hors du dépôt. Les hashes des outils téléchargés dans les scripts doivent être mis à jour en même temps que leurs versions.

Alertes déjà constatées

Audits npm des lockfiles au 22 septembre 2026 :

Projet HIGH CRITICAL
Racine 0 0
Backend 50 0
Frontend 13 1
Log-exporter 0 0

Ce sont des entrées de dépendances signalées, pas nécessairement autant de CVE distinctes. L'entrée critique frontend concerne next ; npm propose une migration majeure. Aucune mise à jour applicative forcée n'a été faite pour masquer ces résultats.

Le scan local des configurations a relevé 11 alertes Kubernetes HIGH : systèmes de fichiers inscriptibles, droits de monitoring et accès hôte. Elles restent à examiner et bloquantes. Certains accès peuvent être nécessaires au monitoring.

Les secrets Stripe en dur de la stack préprod ont été remplacés par les variables Portainer obligatoires STRIPE_SECRET_KEY et STRIPE_WEBHOOK_SECRET. Renseigner ces variables avant redéploiement. Si les anciennes valeurs sont actives, les révoquer/renouveler dans Stripe : elles restent dans l'historique Git. Le scan de ces configurations ne signale plus ce secret après modification.

Validation

Les 27 tests offline de promotion et santé passent : arbre différent, marqueurs manquants, panne du registre, digest invalide, mauvaise branche, saisie hostile et échec HTTP sont refusés. Les tests vérifient aussi que les échecs et délais des audits restent bloquants et que le rapport masque les valeurs de secrets. Actionlint valide la syntaxe après normalisation des URLs d'actions Gitea ; ce contrôle ne remplace pas une exécution avec act_runner.

ACTIONLINT_BIN=/chemin/vers/actionlint bash scripts/ci/validate-workflows.sh

La correction du 23 septembre 2026 a aussi été exécutée dans un conteneur Linux ARM64 : Trivy 0.74.0, Actionlint 1.7.12 et Hetzner CLI 1.49.0 démarrent avec leurs archives officielles vérifiées. activate-node.sh place le Node 22 natif du cache en tête du PATH et vérifie son exécution ; un Node 18 préinstallé ne peut plus être sélectionné silencieusement. Node 22.23.2 a été vérifié dans l'étape suivante. Le téléchargement/affichage de version dans setup-node ne suffit pas à confirmer le runtime utilisé par les étapes shell ; la nouvelle étape affiche le chemin absolu et la version réellement activés.

L'installateur commun est scripts/ci/install-tool.sh. Chaque architecture a sa propre archive et son empreinte. QEMU couvre AMD64 et ARM64 dans les builds préprod et frontend prod, dont la cible reste AMD64 même si le runner est ARM64.

Les installations ont été vérifiées avec npm ci --dry-run --offline --ignore-scripts --legacy-peer-deps. Les builds Docker, les tests applicatifs sous Node 22, le transport réel des artefacts et les déploiements sont encore à valider sur les runners de l'instance.

Références : différences Gitea 1.22, Renovate sur Gitea.

Diagnostic du run Gitea 146

Le journal complet du job sécurité confirme que Node 22 et Trivy démarrent, que les validations passent et que l'artefact security-reports est bien téléversé. L'affichage de l'action composite s'arrête pourtant visuellement au lancement de Trivy. Les quatre workflows exposent désormais installation, validation, audit, résumé et téléversement comme étapes distinctes. Le résumé s'exécute même si l'audit échoue, sans afficher de valeurs de secrets. Un rapport absent ou invalide est signalé comme indisponible.

Ce run échoue réellement sur les audits : 50 vulnérabilités élevées backend, 13 élevées et une critique frontend, quatre détections de secrets et douze alertes d'infrastructure. Ces nombres décrivent ce run, pas un audit futur. Les seuils restent bloquants ; cette correction d'affichage ne corrige pas les vulnérabilités. Les douze erreurs Prettier du job backend ont été corrigées et le lint sans correction automatique passe localement.

Correction des audits du 24 septembre 2026

Les dépendances ont été actualisées sans suppression de l'audit : Next 15.5.26, React 19, Nest 11.2.6, MJML 5 (rendu attendu de façon asynchrone) et Nodemailer 10. PostCSS 8.5.28 est imposé uniquement sous Next pour corriger sa dépendance épinglée. Les modules Tiptap sont alignés. SheetJS utilise la distribution officielle 0.20.3, avec intégrité dans le lockfile, en conservant l'API des exports Excel.

Les audits actuels passent au seuil HIGH/CRITICAL : zéro vulnérabilité frontend, trois alertes modérées backend (csv-parse, uuid et la dépendance d'exceljs). Ces résultats sont datés et ne garantissent pas l'absence de nouvelles alertes.

Les clés Stripe embarquées dans le script de configuration et les deux stacks préprod sont retirées. Renseigner STRIPE_SECRET_KEY et STRIPE_WEBHOOK_SECRET dans Portainer avant de redéployer ces stacks, puis renouveler les valeurs qui étaient dans Git. Le placeholder de l'adaptateur n'est pas une véritable clé.

Les exceptions approuvées sont dans .trivyignore.yaml, chargées explicitement par l'audit source : quatre règles pour node-exporter et une pour le proxy cAdvisor de Prometheus, uniquement sur leurs chemins précis, jusqu'au 24 octobre 2026. Aucune exclusion npm ou de secret. Le test réel Trivy vérifie qu'une copie dans un autre fichier et une exception expirée échouent toujours. Le droit nodes/proxy inutile de Promtail est retiré.

Les racines des conteneurs backend, migration, Loki, Prometheus et Grafana sont en lecture seule, avec volumes d'écriture dédiés. Le backend initialise son volume CSV à partir de la même image que l'application ; le script Kubernetes applique le manifeste complet pour prendre en charge les déploiements existants. Les données temporaires des volumes emptyDir restent éphémères, comme les anciens fichiers écrits dans les pods. PostgreSQL démarre en UID 999 : le provisionnement du nœud attribue déjà les volumes et certificats à cet utilisateur. L'image WAL-G configurée reste AMD64 ; son archive ARM64 n'est pas publiée à cette URL.

Validation locale : lint backend/frontend, types frontend, 27 tests de scripts plus un test d'intégration Trivy (trois scénarios), builds Docker Node 22 backend et frontend, rendu email et copie CSV sur système en lecture seule, démarrage PostgreSQL AMD64 sans root et scan source Trivy. Les tests applicatifs couvrent les exports, emails, contrôles d'accès et TLS. Aucun déploiement réel ni nouvelle exécution Gitea n'est effectué par cette correction locale.

Sources : migration Next 15, distribution SheetJS, exceptions Trivy.

Images de déploiement : npm global (25 septembre 2026)

Les rapports du run Gitea 150 attribuent huit alertes HIGH identiques, sur AMD64 et ARM64, aux dépendances du npm global livré dans node:22-alpine : usr/local/lib/node_modules/npm/node_modules/.... Les audits npm du projet ne couvrent pas ce gestionnaire global ; un audit applicatif vert ne suffit donc pas à valider l'image finale.

Les trois Dockerfiles désinstallent le npm global avec npm uninstall --global npm uniquement après les installations nécessaires, dans l'image d'exécution. Le backend et le frontend gardent npm dans leurs étages de construction. Les services démarrent avec Node, et les migrations backend utilisent directement TypeORM via startup.js. La procédure manuelle Portainer utilise également Node. Aucune CVE n'est ignorée et les scans par digest restent bloquants en préprod/prod.

Chaque job de scan affiche maintenant un résumé indiquant CVE, paquet, version, correctif disponible et chemin, même après échec. Le rapport JSON complet reste joint en artefact. Les barres de progression sont désactivées pour éviter les journaux illisibles. L'avertissement de Trivy 0.74 sur la liste EOL d'Alpine 3.24 n'est pas la cause du code de sortie 1 observé : il est aussi présent sur les scans corrigés qui retournent 0.

Validation du correctif : reconstruction des trois images ARM64 et du log-exporter AMD64 ; scans Trivy au seuil HIGH/CRITICAL sans exclusions ; HTTP 200 sur les routes de santé frontend et log-exporter ; chargement du démarrage backend et de la CLI TypeORM sans npm/npx ; validation des workflows et 30 tests de scripts. Les images backend/frontend AMD64 et le déploiement restent à confirmer par la prochaine exécution Gitea après publication du correctif. Relancer uniquement les anciens jobs de scan réanalyse les anciens digests vulnérables : il faut rebâtir les images à partir du commit corrigé.