docs(livrables): verse le rapport collectif EC02 et le rapport de sécurisation EC04
Sources Markdown et versions figées PDF, relevés du 24/09 sur le commit gelé
9f343e9, et les quatorze preuves anonymisées du rapport de sécurisation,
chacune avec la commande qui la rejoue. Porte la déclaration d'usage de l'IA
et la section anonymisation et RGPD demandées par #154.
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
# Bandit 1.9.4 · 2026-09-24 14:45 CEST · commit 9f343e9 (dev = main)
|
||||
|
||||
## apps/backend/app · seuil CI (MEDIUM, confiance MEDIUM)
|
||||
Run started:2026-09-24 12:45:08.896999+00:00
|
||||
|
||||
Test results:
|
||||
No issues identified.
|
||||
|
||||
Code scanned:
|
||||
Total lines of code: 6136
|
||||
Total lines skipped (#nosec): 0
|
||||
Total potential issues skipped due to specifically being disabled (e.g., #nosec BXXX): 0
|
||||
|
||||
Run metrics:
|
||||
Total issues (by severity):
|
||||
Undefined: 0
|
||||
Low: 0
|
||||
Medium: 0
|
||||
High: 0
|
||||
Total issues (by confidence):
|
||||
Undefined: 0
|
||||
Low: 0
|
||||
Medium: 0
|
||||
High: 0
|
||||
Files skipped (0):
|
||||
exit=0
|
||||
|
||||
## apps/backend/app · tous niveaux
|
||||
Test results:
|
||||
No issues identified.
|
||||
|
||||
Code scanned:
|
||||
Total lines of code: 6136
|
||||
Total lines skipped (#nosec): 0
|
||||
Total potential issues skipped due to specifically being disabled (e.g., #nosec BXXX): 0
|
||||
|
||||
Run metrics:
|
||||
Total issues (by severity):
|
||||
Undefined: 0
|
||||
Low: 0
|
||||
Medium: 0
|
||||
High: 0
|
||||
Total issues (by confidence):
|
||||
Undefined: 0
|
||||
Low: 0
|
||||
Medium: 0
|
||||
High: 0
|
||||
Files skipped (0):
|
||||
|
||||
## ml/enervision_ml · seuil CI (MEDIUM, confiance MEDIUM)
|
||||
Run started:2026-09-24 12:45:09.551911+00:00
|
||||
|
||||
Test results:
|
||||
No issues identified.
|
||||
|
||||
Code scanned:
|
||||
Total lines of code: 722
|
||||
Total lines skipped (#nosec): 0
|
||||
Total potential issues skipped due to specifically being disabled (e.g., #nosec BXXX): 0
|
||||
|
||||
Run metrics:
|
||||
Total issues (by severity):
|
||||
Undefined: 0
|
||||
Low: 0
|
||||
Medium: 0
|
||||
High: 0
|
||||
Total issues (by confidence):
|
||||
Undefined: 0
|
||||
Low: 0
|
||||
Medium: 0
|
||||
High: 0
|
||||
Files skipped (0):
|
||||
exit=0
|
||||
|
||||
## ml/enervision_ml · tous niveaux
|
||||
Test results:
|
||||
No issues identified.
|
||||
|
||||
Code scanned:
|
||||
Total lines of code: 722
|
||||
Total lines skipped (#nosec): 0
|
||||
Total potential issues skipped due to specifically being disabled (e.g., #nosec BXXX): 0
|
||||
|
||||
Run metrics:
|
||||
Total issues (by severity):
|
||||
Undefined: 0
|
||||
Low: 0
|
||||
Medium: 0
|
||||
High: 0
|
||||
Total issues (by confidence):
|
||||
Undefined: 0
|
||||
Low: 0
|
||||
Medium: 0
|
||||
High: 0
|
||||
Files skipped (0):
|
||||
|
||||
## etl/airflow/dags · seuil CI (MEDIUM, confiance MEDIUM)
|
||||
Run started:2026-09-24 12:45:09.903724+00:00
|
||||
|
||||
Test results:
|
||||
No issues identified.
|
||||
|
||||
Code scanned:
|
||||
Total lines of code: 252
|
||||
Total lines skipped (#nosec): 0
|
||||
Total potential issues skipped due to specifically being disabled (e.g., #nosec BXXX): 0
|
||||
|
||||
Run metrics:
|
||||
Total issues (by severity):
|
||||
Undefined: 0
|
||||
Low: 0
|
||||
Medium: 0
|
||||
High: 0
|
||||
Total issues (by confidence):
|
||||
Undefined: 0
|
||||
Low: 0
|
||||
Medium: 0
|
||||
High: 0
|
||||
Files skipped (0):
|
||||
exit=0
|
||||
|
||||
## etl/airflow/dags · tous niveaux
|
||||
Test results:
|
||||
No issues identified.
|
||||
|
||||
Code scanned:
|
||||
Total lines of code: 252
|
||||
Total lines skipped (#nosec): 0
|
||||
Total potential issues skipped due to specifically being disabled (e.g., #nosec BXXX): 0
|
||||
|
||||
Run metrics:
|
||||
Total issues (by severity):
|
||||
Undefined: 0
|
||||
Low: 0
|
||||
Medium: 0
|
||||
High: 0
|
||||
Total issues (by confidence):
|
||||
Undefined: 0
|
||||
Low: 0
|
||||
Medium: 0
|
||||
High: 0
|
||||
Files skipped (0):
|
||||
@@ -0,0 +1,27 @@
|
||||
# pip-audit (méthode du job security-audit de backend.yml : verrou figé, sans dépendances de dev) · 2026-09-24 14:45 CEST · commit 9f343e9
|
||||
|
||||
## apps/backend/uv.lock
|
||||
paquets figés : 50
|
||||
Installed 28 packages in 17ms
|
||||
WARNING:pip_audit._cli:--no-deps is supported, but users are encouraged to fully hash their pinned dependencies
|
||||
WARNING:pip_audit._cli:Consider using a tool like `pip-compile`: https://pip-tools.readthedocs.io/en/latest/#using-hashes
|
||||
No known vulnerabilities found
|
||||
exit=0
|
||||
|
||||
## ml/uv.lock
|
||||
paquets figés : 94
|
||||
WARNING:pip_audit._cli:--no-deps is supported, but users are encouraged to fully hash their pinned dependencies
|
||||
WARNING:pip_audit._cli:Consider using a tool like `pip-compile`: https://pip-tools.readthedocs.io/en/latest/#using-hashes
|
||||
No known vulnerabilities found
|
||||
exit=0
|
||||
|
||||
## etl/airflow/uv.lock
|
||||
paquets figés : 128
|
||||
WARNING:pip_audit._cli:--no-deps is supported, but users are encouraged to fully hash their pinned dependencies
|
||||
WARNING:pip_audit._cli:Consider using a tool like `pip-compile`: https://pip-tools.readthedocs.io/en/latest/#using-hashes
|
||||
Found 2 known vulnerabilities in 1 package
|
||||
Name Version ID Fix Versions
|
||||
----------------------------- ------- ------------- ------------
|
||||
apache-airflow-providers-smtp 2.3.2 PYSEC-2026-24 3.0.0
|
||||
apache-airflow-providers-smtp 2.3.2 PYSEC-2026-24 3.0.0
|
||||
exit=1
|
||||
@@ -0,0 +1,15 @@
|
||||
# npm audit (méthode du job security-audit de frontend.yml : --package-lock-only) · 2026-09-24 14:45 CEST · commit 9f343e9
|
||||
|
||||
## apps/frontend · seuil CI : --audit-level=high
|
||||
found 0 vulnerabilities
|
||||
exit=0
|
||||
## apps/frontend · synthèse tous niveaux (metadata)
|
||||
vulnerabilites: {"info": 0, "low": 0, "moderate": 0, "high": 0, "critical": 0, "total": 0}
|
||||
dependances: {"prod": 13, "dev": 499, "optional": 150, "peer": 0, "peerOptional": 0, "total": 511}
|
||||
|
||||
## tests/e2e · seuil CI : --audit-level=high
|
||||
found 0 vulnerabilities
|
||||
exit=0
|
||||
## tests/e2e · synthèse tous niveaux (metadata)
|
||||
vulnerabilites: {"info": 0, "low": 0, "moderate": 0, "high": 0, "critical": 0, "total": 0}
|
||||
dependances: {"prod": 1, "dev": 26, "optional": 20, "peer": 0, "peerOptional": 0, "total": 26}
|
||||
@@ -0,0 +1,28 @@
|
||||
# Checkov 3.3.19 · 2026-09-24 14:47 CEST · commit 9f343e9, sur un export git archive du commit (aucun fichier ignoré du poste)
|
||||
# Option --quiet : seuls les contrôles en échec sont listés, le résumé JSON donne les réussis.
|
||||
|
||||
## 1. infra/terraform, framework terraform
|
||||
-- résumé : (aucune ressource) {'passed': 0, 'failed': 0, 'skipped': 0, 'parsing_errors': 0, 'resource_count': 0, 'checkov_version': '3.3.19'}
|
||||
Lecture : resource_count 0. Les 6 ressources Terraform du dépôt sont des null_resource (provisioners SSH), pour lesquelles Checkov n'a aucune politique : ce scan ne prouve rien sur ce Terraform, dans un sens comme dans l'autre.
|
||||
|
||||
## 2. framework dockerfile, dépôt entier
|
||||
dockerfile scan results:
|
||||
Passed checks: 268, Failed checks: 3, Skipped checks: 0
|
||||
Check: CKV_DOCKER_2: "Ensure that HEALTHCHECK instructions have been added to container images"
|
||||
FAILED for resource: /ml/Dockerfile.
|
||||
File: /ml/Dockerfile:1-7
|
||||
Check: CKV_DOCKER_2: "Ensure that HEALTHCHECK instructions have been added to container images"
|
||||
FAILED for resource: /etl/airflow/Dockerfile.
|
||||
File: /etl/airflow/Dockerfile:1-50
|
||||
Check: CKV_DOCKER_2: "Ensure that HEALTHCHECK instructions have been added to container images"
|
||||
FAILED for resource: /apps/frontend/Dockerfile.
|
||||
File: /apps/frontend/Dockerfile:1-47
|
||||
-- résumé : dockerfile {'passed': 268, 'failed': 3, 'skipped': 0, 'parsing_errors': 0, 'resource_count': 4, 'checkov_version': '3.3.19'}
|
||||
|
||||
## 3. framework github_actions, dépôt entier
|
||||
github_actions scan results:
|
||||
Passed checks: 596, Failed checks: 0, Skipped checks: 0
|
||||
-- résumé : github_actions {'passed': 596, 'failed': 0, 'skipped': 0, 'parsing_errors': 0, 'resource_count': 0, 'checkov_version': '3.3.19'}
|
||||
|
||||
## 4. framework secrets, dépôt entier
|
||||
-- résumé : (aucune ressource) {'passed': 0, 'failed': 0, 'skipped': 0, 'parsing_errors': 0, 'resource_count': 0, 'checkov_version': '3.3.19'}
|
||||
@@ -0,0 +1,23 @@
|
||||
# gitleaks (image zricethezav/gitleaks:latest, sha256:c00b6bd0aeb3) · 2026-09-24 14:48 CEST
|
||||
# Commande : docker run --rm -v <depot>:/repo:ro zricethezav/gitleaks:latest git /repo --log-opts="--all" --redact --report-format json
|
||||
# Périmètre : toutes les branches locales et distantes, 342 commits hors merges (487 avec merges), commit de tête 9f343e9
|
||||
# Champs Author et Email retirés de cette copie (anonymisation). Valeurs caviardées par --redact.
|
||||
|
||||
Résultat brut : 339 commits scannés, ~3,43 Mo, 8 constats, tous de la règle generic-api-key.
|
||||
|
||||
- generic-api-key · apps/backend/tests/etl/test_reading_retention.py:74 · commit fa815f49 · 2026-09-24 · extrait caviardé : s3_access_key": "REDACTED"
|
||||
- generic-api-key · apps/frontend/src/app/features/auth/change-password/change-password.spec.ts:90 · commit c2f360c5 · 2026-09-23 · extrait caviardé : current_password: 'REDACTED'
|
||||
- generic-api-key · apps/backend/tests/repositories/test_drift.py:110 · commit 9bf2f271 · 2026-09-22 · extrait caviardé : api_current", REDACTED
|
||||
- generic-api-key · apps/backend/tests/repositories/test_drift.py:113 · commit 9bf2f271 · 2026-09-22 · extrait caviardé : api_history", REDACTED
|
||||
- generic-api-key · apps/backend/tests/repositories/test_reading.py:202 · commit c059f838 · 2026-09-18 · extrait caviardé : api_history", REDACTED
|
||||
- generic-api-key · apps/backend/tests/repositories/test_reading.py:205 · commit c059f838 · 2026-09-18 · extrait caviardé : api_current", REDACTED
|
||||
- generic-api-key · apps/backend/tests/repositories/test_reading.py:98 · commit b433e01f · 2026-09-18 · extrait caviardé : api_history", REDACTED
|
||||
- generic-api-key · apps/backend/tests/repositories/test_reading.py:101 · commit b433e01f · 2026-09-18 · extrait caviardé : api_current", REDACTED
|
||||
|
||||
Tri manuel : les 8 constats sont des faux positifs.
|
||||
- 6 constats (test_drift.py, test_reading.py), déjà triés le 23/09 : la règle generic-api-key prend pour une clé la valeur qui suit un identifiant commençant par « api_ ». Les lignes visées sont des appels de fabrique de test du type creer_lecture(..., source="api_history", consumption_kwh=20.0) : « api_history » et « api_current » sont deux des trois valeurs admises pour la colonne source de reading (contrainte ck_reading_source, apps/backend/app/models/energy.py), pas des secrets.
|
||||
- apps/backend/tests/etl/test_reading_retention.py:74 : identifiant de clé S3 factice (« GK » suivi de 10 chiffres) construit par la fabrique de test settings_s3(), à côté de SecretStr("un-secret-garage") ; aucun Garage réel ne l'accepte, les vraies clés sont générées sur la machine par scripts/provision-host.sh.
|
||||
- apps/frontend/src/app/features/auth/change-password/change-password.spec.ts:90 : mot de passe provisoire simulé d'un test unitaire Angular (valeur attendue par un mock de AuthService), sans compte réel derrière.
|
||||
|
||||
Contrôle complémentaire : git log --all -- .env apps/backend/.env ml/.env etl/airflow/.env '*.pem' '*.key' '*terraform.tfvars' '*.tfstate' '*.tfstate.backup' dns.token renvoie 0 commit : aucun fichier de secrets, certificat, clé, state Terraform ni jeton DNS n'a jamais été versionné.
|
||||
Seul fichier de configuration sensible versionné : infra/garage/garage.toml (commit e53c7e4, 24/09), par conception : relu ligne à ligne, il ne porte aucun secret, Garage lit GARAGE_RPC_SECRET, GARAGE_ADMIN_TOKEN et GARAGE_METRICS_TOKEN dans son environnement (ADR 0019).
|
||||
@@ -0,0 +1,66 @@
|
||||
# Trivy config (image aquasec/trivy:latest, sha256:62b1e65e8869) · 2026-09-24 14:46 CEST · commit 9f343e9, sur un export git archive du commit
|
||||
# Commande : docker run --rm -v <export>:/repo:ro aquasec/trivy:latest config /repo
|
||||
|
||||
Report Summary
|
||||
|
||||
┌────────────────────────────────────────┬────────────┬───────────────────┐
|
||||
│ Target │ Type │ Misconfigurations │
|
||||
├────────────────────────────────────────┼────────────┼───────────────────┤
|
||||
│ apps/backend/Dockerfile │ dockerfile │ 0 │
|
||||
├────────────────────────────────────────┼────────────┼───────────────────┤
|
||||
│ apps/frontend/Dockerfile │ dockerfile │ 1 │
|
||||
├────────────────────────────────────────┼────────────┼───────────────────┤
|
||||
│ etl/airflow/Dockerfile │ dockerfile │ 1 │
|
||||
├────────────────────────────────────────┼────────────┼───────────────────┤
|
||||
│ infra/terraform/environments/k3s-cible │ terraform │ 0 │
|
||||
├────────────────────────────────────────┼────────────┼───────────────────┤
|
||||
│ infra/terraform/environments/vm-eni │ terraform │ 0 │
|
||||
├────────────────────────────────────────┼────────────┼───────────────────┤
|
||||
│ ml/Dockerfile │ dockerfile │ 1 │
|
||||
└────────────────────────────────────────┴────────────┴───────────────────┘
|
||||
Legend:
|
||||
- '-': Not scanned
|
||||
- '0': Clean (no security findings detected)
|
||||
|
||||
|
||||
apps/frontend/Dockerfile (dockerfile)
|
||||
=====================================
|
||||
Tests: 27 (SUCCESSES: 26, FAILURES: 1)
|
||||
Failures: 1 (UNKNOWN: 0, LOW: 1, MEDIUM: 0, HIGH: 0, CRITICAL: 0)
|
||||
|
||||
DS-0026 (LOW): Add HEALTHCHECK instruction in your Dockerfile
|
||||
════════════════════════════════════════
|
||||
You should add HEALTHCHECK instruction in your docker container images to perform the health check on running containers.
|
||||
|
||||
See https://avd.aquasec.com/misconfig/ds-0026
|
||||
────────────────────────────────────────
|
||||
|
||||
|
||||
|
||||
etl/airflow/Dockerfile (dockerfile)
|
||||
===================================
|
||||
Tests: 27 (SUCCESSES: 26, FAILURES: 1)
|
||||
Failures: 1 (UNKNOWN: 0, LOW: 1, MEDIUM: 0, HIGH: 0, CRITICAL: 0)
|
||||
|
||||
DS-0026 (LOW): Add HEALTHCHECK instruction in your Dockerfile
|
||||
════════════════════════════════════════
|
||||
You should add HEALTHCHECK instruction in your docker container images to perform the health check on running containers.
|
||||
|
||||
See https://avd.aquasec.com/misconfig/ds-0026
|
||||
────────────────────────────────────────
|
||||
|
||||
|
||||
|
||||
ml/Dockerfile (dockerfile)
|
||||
==========================
|
||||
Tests: 27 (SUCCESSES: 26, FAILURES: 1)
|
||||
Failures: 1 (UNKNOWN: 0, LOW: 1, MEDIUM: 0, HIGH: 0, CRITICAL: 0)
|
||||
|
||||
DS-0026 (LOW): Add HEALTHCHECK instruction in your Dockerfile
|
||||
════════════════════════════════════════
|
||||
You should add HEALTHCHECK instruction in your docker container images to perform the health check on running containers.
|
||||
|
||||
See https://avd.aquasec.com/misconfig/ds-0026
|
||||
────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
# Trivy fs, scanners vuln et secret (image aquasec/trivy:latest, sha256:62b1e65e8869) · 2026-09-24 14:48 CEST · commit 9f343e9
|
||||
# Commande : docker run --rm -v <depot>:/repo:ro aquasec/trivy:latest fs --scanners vuln,secret --skip-dirs node_modules --skip-dirs .venv --skip-dirs .angular /repo
|
||||
# Périmètre : arbre de travail du poste, donc fichiers ignorés par git compris. Le corps de toute clé détectée est retiré de cette copie.
|
||||
|
||||
Report Summary
|
||||
|
||||
┌──────────────────────────────────────────────────────────────────────────────────┬──────┬─────────────────┬─────────┐
|
||||
│ Target │ Type │ Vulnerabilities │ Secrets │
|
||||
├──────────────────────────────────────────────────────────────────────────────────┼──────┼─────────────────┼─────────┤
|
||||
│ apps/backend/uv.lock │ uv │ 0 │ - │
|
||||
├──────────────────────────────────────────────────────────────────────────────────┼──────┼─────────────────┼─────────┤
|
||||
│ apps/frontend/package-lock.json │ npm │ 0 │ - │
|
||||
├──────────────────────────────────────────────────────────────────────────────────┼──────┼─────────────────┼─────────┤
|
||||
│ etl/airflow/uv.lock │ uv │ 1 │ - │
|
||||
├──────────────────────────────────────────────────────────────────────────────────┼──────┼─────────────────┼─────────┤
|
||||
│ ml/mlruns/1/models/m-07209b6bfc7a4edfa1c8a8b1b4577a34/artifacts/requirements.txt │ pip │ 0 │ - │
|
||||
├──────────────────────────────────────────────────────────────────────────────────┼──────┼─────────────────┼─────────┤
|
||||
│ ml/mlruns/1/models/m-37380043d1da4d578858f40853a7f72d/artifacts/requirements.txt │ pip │ 0 │ - │
|
||||
├──────────────────────────────────────────────────────────────────────────────────┼──────┼─────────────────┼─────────┤
|
||||
│ ml/mlruns/1/models/m-39f8a317ffed49288cbdffb66046ecd4/artifacts/requirements.txt │ pip │ 0 │ - │
|
||||
├──────────────────────────────────────────────────────────────────────────────────┼──────┼─────────────────┼─────────┤
|
||||
│ ml/mlruns/1/models/m-3a8d4c6d896b4ca2b0645b323adfb857/artifacts/requirements.txt │ pip │ 0 │ - │
|
||||
├──────────────────────────────────────────────────────────────────────────────────┼──────┼─────────────────┼─────────┤
|
||||
│ ml/mlruns/1/models/m-57dd632c169a4c43b5777f9fe1a52958/artifacts/requirements.txt │ pip │ 0 │ - │
|
||||
├──────────────────────────────────────────────────────────────────────────────────┼──────┼─────────────────┼─────────┤
|
||||
│ ml/mlruns/1/models/m-d1cbcc5b82a1400ea438c01c12e73536/artifacts/requirements.txt │ pip │ 0 │ - │
|
||||
├──────────────────────────────────────────────────────────────────────────────────┼──────┼─────────────────┼─────────┤
|
||||
│ ml/uv.lock │ uv │ 0 │ - │
|
||||
├──────────────────────────────────────────────────────────────────────────────────┼──────┼─────────────────┼─────────┤
|
||||
│ infra/proxy/tls/privkey.pem │ text │ - │ 1 │
|
||||
└──────────────────────────────────────────────────────────────────────────────────┴──────┴─────────────────┴─────────┘
|
||||
Legend:
|
||||
- '-': Not scanned
|
||||
- '0': Clean (no security findings detected)
|
||||
|
||||
|
||||
etl/airflow/uv.lock (uv)
|
||||
========================
|
||||
Total: 1 (UNKNOWN: 0, LOW: 0, MEDIUM: 1, HIGH: 0, CRITICAL: 0)
|
||||
|
||||
┌───────────────────────────────┬────────────────┬──────────┬────────┬───────────────────┬───────────────┬─────────────────────────────────────────────────────────────┐
|
||||
│ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │ Title │
|
||||
├───────────────────────────────┼────────────────┼──────────┼────────┼───────────────────┼───────────────┼─────────────────────────────────────────────────────────────┤
|
||||
│ apache-airflow-providers-smtp │ CVE-2026-41016 │ MEDIUM │ fixed │ 2.3.2 │ 3.0.0 │ apache-airflow-providers-smtp: No certificate validation on │
|
||||
│ │ │ │ │ │ │ SMTP STARTTLS connections in SMTP provider │
|
||||
│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2026-41016 │
|
||||
└───────────────────────────────┴────────────────┴──────────┴────────┴───────────────────┴───────────────┴─────────────────────────────────────────────────────────────┘
|
||||
|
||||
infra/proxy/tls/privkey.pem (secrets)
|
||||
=====================================
|
||||
Total: 1 (HIGH: 1) · AsymmetricPrivateKey (private-key) · infra/proxy/tls/privkey.pem:2-27 · [contenu retiré]
|
||||
|
||||
Lecture :
|
||||
- CVE-2026-41016 (= PYSEC-2026-24 de pip-audit) : dépendance transitive d'apache-airflow 3.3.2, SmtpHook sans validation de certificat au STARTTLS. Aucun DAG n'envoie de courriel et aucune connexion SMTP n'est déclarée dans Airflow : pas d'exposition aujourd'hui. Correctif : providers-smtp >= 3.0.0. Inchangée depuis le 23/09 : le verrou etl/airflow/uv.lock n'a pas bougé et n'est audité ni par la CI ni par Dependabot.
|
||||
- privkey.pem : clé du certificat auto-signé du poste, générée par make tls-selfsigned pour le développement local ; en recette et en production, le certificat vient de Let's Encrypt (ADR 0018). Ignorée par git (.gitignore, motif infra/proxy/tls/*.pem), jamais commitée (git log --all -- '*.pem' vide, cf. 05-gitleaks.txt). Risque réel : la retrouver dans un ZIP fabriqué à partir de l'arbre de travail au lieu d'un clone.
|
||||
@@ -0,0 +1,7 @@
|
||||
# Tests d'accès et d'authentification du backend · 2026-09-24 14:50 CEST · commit 9f343e9
|
||||
# Commande : uv run --frozen pytest tests/api/test_matrice_acces.py tests/api/test_route_protection.py tests/api/test_authorization.py tests/api/test_hardening.py tests/api/test_auth.py tests/core/test_security.py -m 'not integration' --no-cov
|
||||
# Les tests marqués integration (mêmes matrices avec de vrais jetons et une vraie base) tournent en CI sur TimescaleDB, pas ici.
|
||||
|
||||
........................................................................ [ 83%]
|
||||
.............. [100%]
|
||||
86 passed, 5 deselected in 3.36s
|
||||
@@ -0,0 +1,16 @@
|
||||
# Terraform fmt et validate (mêmes étapes que .github/workflows/infra.yml) · 2026-09-24 14:50 CEST · commit 9f343e9, sur un export git archive · Terraform v1.16.3
|
||||
|
||||
## terraform fmt -check -recursive infra/terraform
|
||||
exit=0
|
||||
|
||||
## infra/terraform/environments/k3s-cible/ : init -backend=false puis validate
|
||||
init exit=0
|
||||
Success! The configuration is valid.
|
||||
|
||||
exit=0
|
||||
|
||||
## infra/terraform/environments/vm-eni/ : init -backend=false puis validate
|
||||
init exit=0
|
||||
Success! The configuration is valid.
|
||||
|
||||
exit=0
|
||||
@@ -0,0 +1,78 @@
|
||||
# Reverse proxy, frontal SNI et ports publiés · 2026-09-24 14:51 CEST · commit 9f343e9, configuration lue sur un export git archive
|
||||
|
||||
## nginx -t du proxy de stack (image nginx:1.31-alpine, celle de docker-compose.prod.yml, configuration montée en lecture seule)
|
||||
# --add-host backend et frontend : nginx résout les upstream au test, hors du réseau Compose. Certificat : celui du poste, monté en lecture seule.
|
||||
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
|
||||
nginx: configuration file /etc/nginx/nginx.conf test is successful
|
||||
|
||||
## nginx -t du frontal SNI de la machine (même commande que infra.yml)
|
||||
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
|
||||
nginx: configuration file /etc/nginx/nginx.conf test is successful
|
||||
|
||||
## Directives de sécurité du proxy (grep sur infra/proxy)
|
||||
infra/proxy/nginx.conf:1:# Contrainte : les directives `limit_req_zone` ne sont valides que dans le bloc `http`.
|
||||
infra/proxy/nginx.conf:16: server_tokens off;
|
||||
infra/proxy/nginx.conf:26: client_max_body_size 2m;
|
||||
infra/proxy/nginx.conf:35: limit_req_zone $binary_remote_addr zone=api:10m rate=20r/s;
|
||||
infra/proxy/nginx.conf:36: limit_req_zone $binary_remote_addr zone=auth:10m rate=30r/m;
|
||||
infra/proxy/nginx.conf:37: limit_req_status 429;
|
||||
infra/proxy/conf.d/enervision.conf:14: listen 80 default_server;
|
||||
infra/proxy/conf.d/enervision.conf:22: return 301 https://$host$request_uri;
|
||||
infra/proxy/conf.d/enervision.conf:27: listen 443 ssl default_server;
|
||||
infra/proxy/conf.d/enervision.conf:28: listen 4443 ssl proxy_protocol default_server;
|
||||
infra/proxy/conf.d/enervision.conf:34: real_ip_header proxy_protocol;
|
||||
infra/proxy/conf.d/enervision.conf:40: ssl_protocols TLSv1.2 TLSv1.3;
|
||||
infra/proxy/conf.d/enervision.conf:44: ssl_session_tickets off;
|
||||
infra/proxy/conf.d/enervision.conf:48: add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
||||
infra/proxy/conf.d/enervision.conf:49: add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'" always;
|
||||
infra/proxy/conf.d/enervision.conf:61: limit_req zone=auth burst=20 nodelay;
|
||||
infra/proxy/conf.d/enervision.conf:67: limit_req zone=api burst=40 nodelay;
|
||||
|
||||
## En-têtes posés par l'API elle-même (apps/backend/app/api/middleware.py)
|
||||
apps/backend/app/api/middleware.py:16: "X-Content-Type-Options": "nosniff",
|
||||
apps/backend/app/api/middleware.py:17: "X-Frame-Options": "DENY",
|
||||
apps/backend/app/api/middleware.py:18: "Referrer-Policy": "no-referrer",
|
||||
apps/backend/app/api/middleware.py:21: "Cross-Origin-Resource-Policy": "same-origin",
|
||||
apps/backend/app/api/middleware.py:37: response.headers["Cache-Control"] = "no-store"
|
||||
|
||||
## Frontal SNI (infra/front/nginx.conf) : aiguillage TCP sans déchiffrement
|
||||
map $ssl_preread_server_name $stack {
|
||||
~^rec\. 127.0.0.1:8444;
|
||||
~^dev\. 127.0.0.1:9444;
|
||||
default 127.0.0.1:10444;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443;
|
||||
ssl_preread on;
|
||||
proxy_pass $stack;
|
||||
proxy_protocol on;
|
||||
proxy_connect_timeout 5s;
|
||||
Réseau : network_mode host (infra/front/compose.yml), ports 80 et 443 de la machine. Il lit le nom demandé (SNI), sans déchiffrer, et relaie en PROXY protocol vers l'écouteur 4443 du proxy de la stack visée, sur 127.0.0.1.
|
||||
|
||||
## Ports publiés après fusion (docker compose 5.5.1, --env-file .env.example, profil monitoring, config --format json)
|
||||
# Commande : docker compose --env-file .env.example -f docker-compose.yml -f docker-compose.prod.yml --profile monitoring config --format json, puis extraction des seuls champs ports (aucune variable recopiée)
|
||||
airflow-apiserver 127.0.0.1:8080->8080
|
||||
airflow-dag-processor aucun port publié
|
||||
airflow-init aucun port publié
|
||||
airflow-scheduler aucun port publié
|
||||
alertmanager 127.0.0.1:9093->9093
|
||||
backend aucun port publié
|
||||
cadvisor aucun port publié
|
||||
db 127.0.0.1:5433->5432
|
||||
frontend aucun port publié
|
||||
garage 127.0.0.1:3900->3900, 127.0.0.1:3903->3903
|
||||
grafana 127.0.0.1:3001->3000
|
||||
mailpit 127.0.0.1:8025->8025
|
||||
node-exporter aucun port publié
|
||||
postgres-exporter aucun port publié
|
||||
prometheus 127.0.0.1:9090->9090
|
||||
proxy 0.0.0.0:80->80, 0.0.0.0:443->443, 127.0.0.1:<aléatoire>->4443
|
||||
|
||||
## Ports réellement posés sur la machine par scripts/provision-host.sh (l. 212-214, colonnes https, http, front)
|
||||
prod 127.0.0.1:10443 127.0.0.1:10080 127.0.0.1:10444
|
||||
rec 127.0.0.1:8443 127.0.0.1:8081 127.0.0.1:8444
|
||||
dev 127.0.0.1:9443 127.0.0.1:8083 127.0.0.1:9444
|
||||
Base, Mailpit, Airflow, Garage et supervision décalés par environnement, tous sur 127.0.0.1.
|
||||
|
||||
Lecture : les défauts du dépôt publient le proxy en 80 et 443 pour un poste de développement. Sur la machine, provision-host.sh ramène tous les ports des trois stacks sur la boucle locale : le seul composant joignable depuis le réseau est le frontal SNI, en 80 et 443. Backend, frontend, scheduler, dag-processor et exporteurs ne publient rien. Le port SMTP 1025 de Mailpit, publié en développement, disparaît (!override). L'état effectif de la machine est relevé dans 12-constats-machine.txt.
|
||||
@@ -0,0 +1,11 @@
|
||||
# State Terraform local de la racine vm-eni · relevé 2026-09-24 14:50 CEST · commit 9f343e9
|
||||
# Commande : terraform -chdir=infra/terraform/environments/vm-eni state list (seuls les noms de ressources, aucun attribut recopié)
|
||||
|
||||
null_resource.docker_engine
|
||||
null_resource.environnements
|
||||
null_resource.runner_github
|
||||
|
||||
Dernière écriture du state : 2026-09-23 12:12:09
|
||||
serial 12 · terraform 1.16.3 · 3 ressources appliquées sur 4 déclarées dans vm-eni/main.tf
|
||||
|
||||
Lecture : depuis le relevé du 23/09 9h05 (serial 3, 2 ressources), les applies du 22/09 au 23/09 ont installé Docker, préparé les trois environnements (clones, .env générés sur la machine, certificats) et enregistré le runner GitHub (null_resource.runner_github). La quatrième ressource, null_resource.coffre (volumes Docker dans un coffre LUKS), est désactivée tant que la variable coffre_taille est vide (count = 0), et elle l'est restée : la machine est un conteneur LXC, où LUKS est impossible (ADR 0020), elle n'a donc jamais été appliquée. Clés des triggers relevées (valeurs non recopiées) : docker_engine [hote, user], environnements [depot, domaine, racine, script], runner_github [dossier, labels, nom, version]. L'adresse de la machine et le compte SSH y figurent, mais ni le jeton d'enregistrement du runner ni la clé SSH : le jeton est une variable sensitive hors des triggers (ADR 0010).
|
||||
@@ -0,0 +1,99 @@
|
||||
# Constats sur la machine du groupe, en lecture seule · 24/09/2026 15h20 à 15h30 CEST · commit déployé en production 9f343e9
|
||||
# Commande : ssh root@<IP-VM-G3> 'bash -s' < constats-machine-enervision.sh, puis constats-complement-enervision.sh, lancés par l'auteur
|
||||
# Anonymisation : adresse et nom de la machine masqués, identifiants de cluster Vault et adresses internes des réseaux Docker retirés.
|
||||
|
||||
## Virtualisation (systemd-detect-virt)
|
||||
lxc
|
||||
|
||||
## Ports TCP en écoute (ss -tlnpH, adresse locale et processus)
|
||||
Sur toutes les interfaces :
|
||||
0.0.0.0:22 et [::]:22 sshd
|
||||
0.0.0.0:80, 0.0.0.0:443 frontal SNI (enervision-front)
|
||||
*:6443 k3s-server
|
||||
0.0.0.0:8200, *:8201 vault
|
||||
0.0.0.0:8202, *:8203 vault
|
||||
0.0.0.0:8204, *:8205 vault
|
||||
Sur la boucle locale seulement :
|
||||
proxys des stacks 127.0.0.1:10080, 10443, 10444 (prod) · 8081, 8443, 8444 (rec) · 8083, 9443, 9444 (dev)
|
||||
bases des stacks 127.0.0.1:5433 (prod), 5434 (rec), 5435 (dev)
|
||||
Mailpit 127.0.0.1:8025, 8026, 8027
|
||||
API Airflow 127.0.0.1:8080, 8082, 8084
|
||||
Garage (S3, admin) 127.0.0.1:3900, 3903 (prod) · 3910, 3913 (rec) · aucun pour dev
|
||||
Grafana, Prometheus, Alertmanager (prod) 127.0.0.1:3001, 9090, 9093
|
||||
PostgreSQL 18 de l'hôte 127.0.0.1:5432, [::1]:5432
|
||||
Postfix de l'hôte 127.0.0.1:25, [::1]:25
|
||||
k3s (interne) 127.0.0.1:6444
|
||||
containerd 127.0.0.1:34303
|
||||
résolveur systemd 127.0.0.53:53, 127.0.0.54:53
|
||||
|
||||
## Pare-feu (nft list ruleset, iptables -S)
|
||||
table inet filter : chaînes input, forward et output vides, politique accept.
|
||||
table ip raw : 24 règles posées par Docker, qui rejettent tout paquet destiné à un port publié sur 127.0.0.1 (ou à un conteneur) s'il n'arrive pas par lo (ou par le pont Docker du projet).
|
||||
iptables : -P INPUT ACCEPT, -P FORWARD DROP, -P OUTPUT ACCEPT, et les chaînes DOCKER, DOCKER-USER, DOCKER-FORWARD de Docker.
|
||||
|
||||
## SSH effectif (sshd -T)
|
||||
permitrootlogin yes
|
||||
pubkeyauthentication yes
|
||||
passwordauthentication yes
|
||||
kbdinteractiveauthentication no
|
||||
|
||||
## Stacks (docker ps par projet Compose)
|
||||
### enervision-prod : 15 services démarrés, airflow-init terminé (0)
|
||||
airflow-apiserver (healthy), airflow-dag-processor, airflow-scheduler, alertmanager, backend (healthy), cadvisor (healthy), db (healthy), frontend, garage (healthy), grafana, mailpit (healthy), node-exporter, postgres-exporter, prometheus, proxy
|
||||
### enervision-rec : 9 services démarrés, airflow-init terminé (0)
|
||||
airflow-apiserver (healthy), airflow-dag-processor, airflow-scheduler, backend (healthy), db (healthy), frontend, garage (healthy), mailpit (healthy), proxy
|
||||
### enervision-dev : 8 services démarrés, airflow-init terminé (0), pas de Garage
|
||||
airflow-apiserver (healthy), airflow-dag-processor, airflow-scheduler, backend (healthy), db (healthy), frontend, mailpit (healthy), proxy
|
||||
### enervision-front
|
||||
front, démarré depuis 26 heures
|
||||
### Hors projets EnerVision
|
||||
19 conteneurs arrêtés entre le 16 et le 21/09 ou jamais démarrés, dont 3 hello-world ; aucun en fonctionnement.
|
||||
|
||||
## Sondes de santé par le nom public, certificat vérifié (curl sans -k)
|
||||
prod http=200 verification_tls=0
|
||||
rec http=200 verification_tls=0
|
||||
dev http=200 verification_tls=0
|
||||
|
||||
## Certificats servis par le frontal (openssl s_client -servername)
|
||||
prod : issuer=C = US, O = Let's Encrypt, CN = YE1 · notAfter=Dec 22 09:40:38 2026 GMT
|
||||
rec : issuer=C = US, O = Let's Encrypt, CN = YE1 · notAfter=Dec 22 09:11:04 2026 GMT
|
||||
dev : issuer=C = US, O = Let's Encrypt, CN = YE1 · notAfter=Dec 22 09:17:54 2026 GMT
|
||||
|
||||
## En-têtes servis en production sur / (curl -sI)
|
||||
server: nginx
|
||||
strict-transport-security: max-age=31536000; includeSubDomains
|
||||
content-security-policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'
|
||||
|
||||
## Droits des secrets sur disque (stat)
|
||||
600 enervision /srv/enervision/dev/.env
|
||||
600 enervision /srv/enervision/prod/.env
|
||||
600 enervision /srv/enervision/rec/.env
|
||||
600 enervision /srv/enervision/dns.token
|
||||
|
||||
## Runner GitHub Actions (systemctl)
|
||||
actions.runner.<depot>.<machine-G3>.service loaded active running
|
||||
|
||||
## Journal d'audit de la production (select action, count(*) from audit_log group by action)
|
||||
user.created|5
|
||||
user.password_changed|3
|
||||
|
||||
## Supervision de la production (API Prometheus, 127.0.0.1:9090)
|
||||
cibles : alertmanager, backend, cadvisor, garage, node, postgres, prometheus · toutes health=up
|
||||
règles chargées : ApiErreursServeur, ApiIndisponible, ApiLatenceElevee, BaseConnexionsSaturees, BaseIndisponible, CibleInjoignable, HoteCpuSature, HoteDisquePlein, HoteMemoireSaturee (groupes api, base, hote, supervision)
|
||||
|
||||
## Services hors plateforme
|
||||
k3s : binaire /usr/local/bin/k3s du 17/09/2026 14:07 UTC · systemctl show k3s : ActiveState=activating, SubState=start, NRestarts=51678 · k3s kubectl get pods -A : No resources found, API refusée pendant un redémarrage
|
||||
vault.service : active (running), « HashiCorp Vault »
|
||||
Vault, seal-status sans authentification (champs utiles) :
|
||||
8200 : version 2.1.1, shamir t=2 n=5, initialized=true, sealed=false, storage_type=file, démarré le 22/09 12:28 UTC
|
||||
8202 : version 2.1.1, shamir t=3 n=5, initialized=true, sealed=false, storage_type=file, démarré le 22/09 07:27 UTC
|
||||
8204 : version 2.1.1, shamir t=4 n=5, initialized=true, sealed=false, storage_type=file, démarré le 22/09 07:28 UTC
|
||||
postgresql@18-main : active (running), boucle locale seulement
|
||||
postfix@- : active (running), boucle locale seulement
|
||||
|
||||
Lecture :
|
||||
- La plateforme livrée tient sa promesse : de ses composants, seul le frontal SNI écoute hors de la boucle locale, en 80 et 443. Les trois noms publics répondent 200 avec un certificat Let's Encrypt vérifié, HSTS et CSP sont servis, les .env sont en 600, le runner tourne, les sept cibles Prometheus sont up.
|
||||
- La machine, elle, expose davantage : SSH en root avec mot de passe accepté, l'API k3s sur 6443 et trois serveurs Vault descellés sur 8200 à 8205, tous sur toutes les interfaces, sans pare-feu hôte (table nftables vide, politique accept).
|
||||
- k3s et Vault ne viennent pas du code livré : git grep vault ne trouve rien sur le commit gelé. Vault est visé par le Terraform d'une branche de travail non fusionnée (provider vault, adresse de la machine en 8200). k3s redémarre en boucle depuis son installation du 17/09 (51 678 redémarrages) et ne porte aucun pod : il consomme sans servir.
|
||||
- L'environnement dev tourne encore le commit du 23/09 : pas de Garage. Il suit la dernière branche lancée à la main (ADR 0017), et aucune ne l'a été depuis.
|
||||
- Le journal d'audit ne compte que les événements d'administration des comptes (5 créations, 3 changements de mot de passe) : les connexions vont à login_attempt.
|
||||
@@ -0,0 +1,42 @@
|
||||
# Réglages GitHub du dépôt, lus par l'API (gh api, lecture seule) · 2026-09-24 14:54 CEST · commit 9f343e9
|
||||
# Heures converties en heure locale (CEST). URL des environnements non reproduites (anonymisation).
|
||||
|
||||
## Dépôt
|
||||
visibilité : public · branche par défaut : main · licence : aucune · fork autorisé : oui
|
||||
|
||||
## Protection des branches (gh api repos/<depot>/branches/<b> --jq .protected ; gh api repos/<depot>/rulesets)
|
||||
main : protected=false
|
||||
dev : protected=false
|
||||
rulesets : 0
|
||||
|
||||
## Environnements de déploiement (gh api repos/<depot>/environments/<env>)
|
||||
prod : 1 règle, branch_policy, restreint à la branche main ; relecteurs requis : 0 ; délai : aucun
|
||||
rec : aucune règle
|
||||
dev : aucune règle
|
||||
|
||||
## Approbation des workflows des contributeurs externes (gh api repos/<depot>/actions/permissions/workflow)
|
||||
refusé avec les droits d'un membre (HTTP 403) : réglage non vérifiable, seule l'administratrice le lit
|
||||
|
||||
## Secrets d'Actions (gh secret list, noms seulement)
|
||||
SONAR_TOKEN
|
||||
|
||||
## Derniers runs de ci.yml (gh run list --workflow ci.yml)
|
||||
24/09 11:51 dev 9f343e9 push success
|
||||
24/09 11:08 main 9f343e9 push success
|
||||
24/09 11:08 dev 5a7e2a9 pull_request success
|
||||
24/09 10:59 dev 5a7e2a9 push success
|
||||
|
||||
## Déploiements de l'environnement prod (gh api repos/<depot>/deployments?environment=prod)
|
||||
24/09 11:12 9f343e9
|
||||
23/09 15:56 5afe2fc
|
||||
23/09 14:56 32f1bef
|
||||
23/09 12:49 7f4364d
|
||||
23/09 12:02 84969d3
|
||||
23/09 12:02 93d5cad
|
||||
23/09 11:37 b786f27
|
||||
|
||||
Lecture :
|
||||
- La production n'attend aucune approbation humaine : la seule garde est la restriction à main. L'ADR 0009 annonce une production après approbation, l'ADR 0014 laisse ce réglage à poser par l'administratrice : il ne l'est pas.
|
||||
- Aucune branche protégée : un membre en écriture peut pousser directement sur dev ou main, ce qui est arrivé trois fois sur dev (c2f360c, cbbfaf4, 6c09bee), relus ensuite par les PR de remontée #161 et #163.
|
||||
- Un seul secret côté GitHub, pour SonarCloud : le déploiement n'en consomme aucun, le runner auto-hébergé travaille dans le dossier qui porte déjà le .env.
|
||||
- Chaque push sur main depuis le 23/09 11h37 a produit un déploiement ; le dernier porte le commit gelé 9f343e9.
|
||||
@@ -0,0 +1,21 @@
|
||||
# DAST OWASP ZAP, dernier passage publié en artefact zap-report (run 35840107213, dast.yml) · 23/09/2026 10:59 CEST · commit fe0d422 (PR de remontée dev vers main)
|
||||
# Récupération : gh run download 35840107213 -n zap-report, puis extraction des alertes du JSON et du bilan de zap-stdout.log
|
||||
# Cible : l'API en fonctionnement dans le job (backend seul, sans TLS ni proxy), authentifiée par un compte lecteur jetable
|
||||
|
||||
## Bilan des règles (zap-stdout.log)
|
||||
FAIL-NEW: 0 FAIL-INPROG: 0 WARN-NEW: 0 WARN-INPROG: 0 INFO: 0 IGNORE: 0 PASS: 118
|
||||
|
||||
## Alertes du rapport (ZAP 2.17.0)
|
||||
- Informational (High) · A Client Error response code was returned by the server [100000] · 65 occurrence(s)
|
||||
- Informational (Medium) · Non-Storable Content [10049] · 5 occurrence(s)
|
||||
- Informational (High) · Authentication Request Identified [10111] · 1 occurrence(s)
|
||||
exemple : POST /api/v1/auth/login · paramètre email
|
||||
- Informational (Medium) · Information Disclosure - Sensitive Information in URL [10024] · 1 occurrence(s)
|
||||
exemple : GET /api/v1/auth/reset-password/validate?token=token · paramètre token
|
||||
|
||||
Lecture :
|
||||
- 0 échec, 0 avertissement, 118 règles passées. Les 4 alertes restantes sont informatives.
|
||||
- « Client Error response code » (65) : 42 × 404, 16 × 403, 3 × 405, 2 × 422, 2 × 401, les refus attendus d'un scanner qui sonde des routes inexistantes, gardées par rôle ou appelées avec une charge invalide. Aucun 5xx.
|
||||
- « Non-Storable Content » (5) : login, logout et refresh marqués Cache-Control: no-store par l'API, voulu ; les deux sondes de santé, que ZAP juge non mémorisables parce qu'appelées avec un en-tête Authorization.
|
||||
- « Authentication Request Identified » : repérage de la route de connexion, sans défaut associé.
|
||||
- « Sensitive Information in URL » : le jeton de réinitialisation voyage en paramètre de GET /api/v1/auth/reset-password/validate. Mitigations en place : jeton stocké haché (token_hash), valable 15 minutes (password_reset_ttl_seconds = 900), à usage unique (consumed_at), caviardé des journaux de l'API (app/core/logging.py, motif token=), Referrer-Policy: no-referrer. Résidu réel : le journal d'accès du proxy (log_format enervision, champ "$request", infra/proxy/nginx.conf:18) enregistre l'URL complète, donc le jeton en clair pendant sa durée de validité, lisible par qui administre la machine.
|
||||
Reference in New Issue
Block a user