fix
Some checks are pending
CD Preprod / Deploy to Preprod (push) Blocked by required conditions
CD Preprod / Notify Success (push) Blocked by required conditions
CD Preprod / Notify Failure (push) Blocked by required conditions
CD Preprod / Security gate (push) Successful in 29s
CD Preprod / Backend — Lint (push) Successful in 1m3s
CD Preprod / Frontend — Lint & Type-check (push) Successful in 1m9s
CD Preprod / Backend — Unit Tests (push) Successful in 1m5s
CD Preprod / Frontend — Unit Tests (push) Successful in 43s
CD Preprod / Backend — Integration Tests (push) Successful in 46s
CD Preprod / Build Frontend (push) Successful in 35s
CD Preprod / Build Backend (push) Successful in 1m3s
CD Preprod / Build Log Exporter (push) Successful in 34s
CD Preprod / Image security (${{ matrix.service }}, ${{ matrix.arch }}) (amd64, backend) (push) Successful in 25s
CD Preprod / Image security (${{ matrix.service }}, ${{ matrix.arch }}) (amd64, frontend) (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
Some checks are pending
CD Preprod / Deploy to Preprod (push) Blocked by required conditions
CD Preprod / Notify Success (push) Blocked by required conditions
CD Preprod / Notify Failure (push) Blocked by required conditions
CD Preprod / Security gate (push) Successful in 29s
CD Preprod / Backend — Lint (push) Successful in 1m3s
CD Preprod / Frontend — Lint & Type-check (push) Successful in 1m9s
CD Preprod / Backend — Unit Tests (push) Successful in 1m5s
CD Preprod / Frontend — Unit Tests (push) Successful in 43s
CD Preprod / Backend — Integration Tests (push) Successful in 46s
CD Preprod / Build Frontend (push) Successful in 35s
CD Preprod / Build Backend (push) Successful in 1m3s
CD Preprod / Build Log Exporter (push) Successful in 34s
CD Preprod / Image security (${{ matrix.service }}, ${{ matrix.arch }}) (amd64, backend) (push) Successful in 25s
CD Preprod / Image security (${{ matrix.service }}, ${{ matrix.arch }}) (amd64, frontend) (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
This commit is contained in:
parent
3434fa494d
commit
450f1ffc18
@ -231,8 +231,11 @@ jobs:
|
|||||||
PLATFORM: linux/${{ matrix.arch }}
|
PLATFORM: linux/${{ matrix.arch }}
|
||||||
run: |
|
run: |
|
||||||
trivy image --platform "$PLATFORM" --scanners vuln --severity HIGH,CRITICAL \
|
trivy image --platform "$PLATFORM" --scanners vuln --severity HIGH,CRITICAL \
|
||||||
--ignore-unfixed=false --exit-code 1 --timeout 15m --format json \
|
--ignore-unfixed=false --exit-code 1 --timeout 15m --no-progress --format json \
|
||||||
--output "$RUNNER_TEMP/image-security.json" "$IMAGE"
|
--output "$RUNNER_TEMP/image-security.json" "$IMAGE"
|
||||||
|
- name: Show image scan results
|
||||||
|
if: always()
|
||||||
|
run: python3 scripts/ci/summarize-image-security.py
|
||||||
- name: Save image report
|
- name: Save image report
|
||||||
if: always()
|
if: always()
|
||||||
uses: https://github.com/actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 # v3, Gitea artifact protocol
|
uses: https://github.com/actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 # v3, Gitea artifact protocol
|
||||||
|
|||||||
@ -312,8 +312,11 @@ jobs:
|
|||||||
PLATFORM: linux/${{ matrix.arch }}
|
PLATFORM: linux/${{ matrix.arch }}
|
||||||
run: |
|
run: |
|
||||||
trivy image --platform "$PLATFORM" --scanners vuln --severity HIGH,CRITICAL \
|
trivy image --platform "$PLATFORM" --scanners vuln --severity HIGH,CRITICAL \
|
||||||
--ignore-unfixed=false --exit-code 1 --timeout 15m --format json \
|
--ignore-unfixed=false --exit-code 1 --timeout 15m --no-progress --format json \
|
||||||
--output "$RUNNER_TEMP/image-security.json" "$IMAGE"
|
--output "$RUNNER_TEMP/image-security.json" "$IMAGE"
|
||||||
|
- name: Show image scan results
|
||||||
|
if: always()
|
||||||
|
run: python3 scripts/ci/summarize-image-security.py
|
||||||
- name: Save image report
|
- name: Save image report
|
||||||
if: always()
|
if: always()
|
||||||
uses: https://github.com/actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 # v3, Gitea artifact protocol
|
uses: https://github.com/actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 # v3, Gitea artifact protocol
|
||||||
|
|||||||
@ -40,6 +40,10 @@ RUN npm prune --production --legacy-peer-deps
|
|||||||
# ===============================================
|
# ===============================================
|
||||||
FROM node:22-alpine AS production
|
FROM node:22-alpine AS production
|
||||||
|
|
||||||
|
# Runtime starts with node; remove the base image package manager and its dependencies.
|
||||||
|
# npm remains available in the dependency and build stages.
|
||||||
|
RUN npm uninstall --global npm
|
||||||
|
|
||||||
# Install dumb-init for proper signal handling
|
# Install dumb-init for proper signal handling
|
||||||
RUN apk add --no-cache dumb-init
|
RUN apk add --no-cache dumb-init
|
||||||
|
|
||||||
|
|||||||
@ -50,6 +50,10 @@ RUN npm run build
|
|||||||
# ===============================================
|
# ===============================================
|
||||||
FROM node:22-alpine AS production
|
FROM node:22-alpine AS production
|
||||||
|
|
||||||
|
# Runtime starts with node; remove the base image package manager and its dependencies.
|
||||||
|
# npm remains available in the dependency and build stages.
|
||||||
|
RUN npm uninstall --global npm
|
||||||
|
|
||||||
# Install dumb-init for proper signal handling
|
# Install dumb-init for proper signal handling
|
||||||
RUN apk add --no-cache dumb-init curl
|
RUN apk add --no-cache dumb-init curl
|
||||||
|
|
||||||
|
|||||||
@ -5,6 +5,9 @@ WORKDIR /app
|
|||||||
COPY package.json package-lock.json ./
|
COPY package.json package-lock.json ./
|
||||||
RUN npm ci --omit=dev
|
RUN npm ci --omit=dev
|
||||||
|
|
||||||
|
# Only Node and application dependencies are needed at runtime.
|
||||||
|
RUN npm uninstall --global npm
|
||||||
|
|
||||||
COPY src/ ./src/
|
COPY src/ ./src/
|
||||||
|
|
||||||
EXPOSE 3200
|
EXPOSE 3200
|
||||||
|
|||||||
@ -234,3 +234,34 @@ exécution Gitea n'est effectué par cette correction locale.
|
|||||||
Sources : [migration Next 15](https://nextjs.org/docs/app/guides/upgrading/version-15),
|
Sources : [migration Next 15](https://nextjs.org/docs/app/guides/upgrading/version-15),
|
||||||
[distribution SheetJS](https://docs.sheetjs.com/docs/getting-started/installation/nodejs/),
|
[distribution SheetJS](https://docs.sheetjs.com/docs/getting-started/installation/nodejs/),
|
||||||
[exceptions Trivy](https://trivy.dev/docs/dev/configuration/filtering/).
|
[exceptions Trivy](https://trivy.dev/docs/dev/configuration/filtering/).
|
||||||
|
|
||||||
|
## 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é.
|
||||||
|
|||||||
@ -144,12 +144,12 @@ NEXT_PUBLIC_API_URL=https://api.xpeditis.com
|
|||||||
|
|
||||||
## Migrations automatiques
|
## Migrations automatiques
|
||||||
|
|
||||||
Le backend exécute les migrations automatiquement au démarrage via le script `docker-entrypoint.sh` :
|
Le backend exécute les migrations automatiquement via `startup.js`, qui attend PostgreSQL,
|
||||||
|
appelle directement TypeORM puis lance le serveur Node. npm n’est pas présent dans l’image finale :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# apps/backend/docker-entrypoint.sh attend PostgreSQL puis :
|
# Commande de démarrage de l’image
|
||||||
npm run migration:run
|
node startup.js
|
||||||
node dist/main.js
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Les logs Portainer affichent :
|
Les logs Portainer affichent :
|
||||||
@ -199,8 +199,7 @@ traefik.http.services.frontend.loadbalancer.server.port=3000
|
|||||||
### Migrations échouent
|
### Migrations échouent
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker exec -it xpeditis-backend sh
|
docker exec -it xpeditis-backend node -e 'require("./startup").runMigrations().catch(() => process.exit(1))'
|
||||||
cd /app && npm run migration:run
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Si blocage : vérifier que PostgreSQL est accessible (`DATABASE_HOST` = nom du service Docker).
|
Si blocage : vérifier que PostgreSQL est accessible (`DATABASE_HOST` = nom du service Docker).
|
||||||
|
|||||||
32
scripts/ci/summarize-image-security.py
Normal file
32
scripts/ci/summarize-image-security.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
"""Show actionable image findings even when the blocking Trivy step failed."""
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def summarize(path):
|
||||||
|
try:
|
||||||
|
report = json.loads(path.read_text())
|
||||||
|
if not isinstance(report, dict) or not isinstance(report.get('Results'), list):
|
||||||
|
raise ValueError('Missing scan results')
|
||||||
|
except (OSError, ValueError):
|
||||||
|
print('Image report missing or invalid: inspect the Trivy step; scan not verified.')
|
||||||
|
return 1
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
for result in report['Results']:
|
||||||
|
for finding in result.get('Vulnerabilities') or []:
|
||||||
|
if finding.get('Severity') not in ('HIGH', 'CRITICAL'):
|
||||||
|
continue
|
||||||
|
count += 1
|
||||||
|
print(f'{finding["Severity"]} {finding.get("VulnerabilityID", "unknown")}: '
|
||||||
|
f'{finding.get("PkgName", "unknown")} '
|
||||||
|
f'{finding.get("InstalledVersion", "unknown")} -> '
|
||||||
|
f'{finding.get("FixedVersion") or "no fixed version published"}')
|
||||||
|
print(f' Path: {finding.get("PkgPath") or result.get("Target", "unknown")}')
|
||||||
|
print(f'{count} HIGH/CRITICAL image findings. Full details: image-security artifact.')
|
||||||
|
return int(count > 0)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
raise SystemExit(summarize(Path(os.environ['RUNNER_TEMP']) / 'image-security.json'))
|
||||||
40
scripts/ci/test_image_summary.py
Normal file
40
scripts/ci/test_image_summary.py
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import contextlib
|
||||||
|
import importlib.util
|
||||||
|
import io
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
spec = importlib.util.spec_from_file_location(
|
||||||
|
'image_summary', Path(__file__).with_name('summarize-image-security.py'))
|
||||||
|
summary = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(summary)
|
||||||
|
|
||||||
|
|
||||||
|
class ImageSummary(unittest.TestCase):
|
||||||
|
def test_reports_global_npm_path_and_preserves_failure(self):
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
path = Path(directory) / 'image-security.json'
|
||||||
|
path.write_text(json.dumps({'Results': [{'Vulnerabilities': [{
|
||||||
|
'Severity': 'HIGH', 'VulnerabilityID': 'CVE-example',
|
||||||
|
'PkgName': 'pacote', 'InstalledVersion': '19.0.2',
|
||||||
|
'FixedVersion': '21.5.1',
|
||||||
|
'PkgPath': 'usr/local/lib/node_modules/npm/node_modules/pacote/package.json',
|
||||||
|
}]}]}))
|
||||||
|
output = io.StringIO()
|
||||||
|
with contextlib.redirect_stdout(output):
|
||||||
|
self.assertEqual(summary.summarize(path), 1)
|
||||||
|
self.assertIn('19.0.2 -> 21.5.1', output.getvalue())
|
||||||
|
self.assertIn('usr/local/lib/node_modules/npm/', output.getvalue())
|
||||||
|
|
||||||
|
def test_missing_invalid_and_clean_reports(self):
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
path = Path(directory) / 'image-security.json'
|
||||||
|
with contextlib.redirect_stdout(io.StringIO()):
|
||||||
|
self.assertEqual(summary.summarize(path), 1)
|
||||||
|
for content in ('broken', '{}', '{"Results": null}'):
|
||||||
|
path.write_text(content)
|
||||||
|
self.assertEqual(summary.summarize(path), 1)
|
||||||
|
path.write_text('{"Results": []}')
|
||||||
|
self.assertEqual(summary.summarize(path), 0)
|
||||||
Loading…
Reference in New Issue
Block a user