Contrôleur¶
Le contrôleur (security-review-controller) est le processus des personnes et des
pipelines : il sert l'interface web, l'API REST et les routes de pipeline, gère le schéma de la
base et exécute les tâches de fond. Il n'appelle jamais le serveur MCP : les deux partagent la
base (voir Architecture).
Ce qu'il sert¶
| Route | Utilisée par | Identifiant |
|---|---|---|
/ui/ |
les personnes (navigateur) : interface web | aucun pour les fichiers statiques ; les données viennent de l'API |
/api/v1/auth/ |
la page de connexion : mot de passe et connexion OIDC | aucun |
/api/v1/ |
l'interface web, les scripts, Swagger | session web, jeton admin ou api |
/api/v1/ci/ |
security-review-ci : gate, SARIF, tendance, scans du code et des dépendances |
jeton ci ou jeton mcp personnel |
/metrics |
Prometheus (métriques) | jeton metrics ou admin |
/api/docs |
Swagger, seulement avec SECREVIEW_SWAGGER_ENABLED=true |
aucun (les appels demandent un jeton API) |
/api/ci-client/ |
uv : téléchargement de security-review-ci, sauf avec SECREVIEW_CI_CLIENT_DOWNLOAD_ENABLED=false |
aucun |
/api/health, /api/favicon.svg |
disponibilité de l'API, icône | aucun |
/healthz |
sondes (HEALTHCHECK Docker, Kubernetes) |
aucun |
Les rôles des jetons sont décrits dans Rôles, leur gestion dans Jetons Bearer.
Démarrage¶
security-review-controller serve (port 8000 dans l'image, --host et --port sinon) :
- migre le schéma, ou le vérifie seulement avec
SECREVIEW_DB_AUTO_UPGRADE=false(voir Migrations du schéma) ; - vérifie les réglages (TLS, OIDC, OSV, syslog, connecteurs) : une valeur invalide l'arrête
avec le code
2; - crée le premier administrateur tant qu'aucun compte n'existe ;
- démarre les tâches de fond, puis sert les routes.
Migrations du schéma¶
Le schéma est géré par des migrations Alembic, embarquées
dans le paquet (aucun fichier alembic.ini à fournir) :
security-review-controller db upgrade # crée ou migre le schéma, puis amorce les règles par défaut
security-review-controller db status # {"current": "0032", "head": "0032", "up_to_date": true}
db statusrenvoie le code1si le schéma n'est pas à jour : pratique pour un script ou une sonde.- Le serveur MCP HTTP ne migre jamais : il refuse de démarrer (code
2, messagerun security-review-controller db upgrade first) tant que la base n'est pas à la révision attendue. Sous Kubernetes, le pod redémarre jusqu'à ce que la migration soit faite. - Le contrôleur, le serveur MCP stdio et les commandes CLI migrent au démarrage, sauf avec
SECREVIEW_DB_AUTO_UPGRADE=false. Ils vérifient alors seulement le schéma. Avec Docker Compose, le contrôleur migre à son démarrage ; sous Kubernetes, c'est son init container. - Sur PostgreSQL, une migration prend un verrou consultatif (advisory lock) : des instances qui
démarrent ensemble (plusieurs réplicas, un
db upgradelancé à la main) s'attendent, et la suivante trouve le schéma à jour. Ne désactivez la migration automatique que pour la confier à une tâche dédiée.
Tâches de fond¶
| Tâche | Activée par | Détails |
|---|---|---|
| Mise à jour quotidienne des avis OSV | SECREVIEW_OSV_AUTO_UPDATE=true (désactivée par défaut) |
Mise à jour automatique par le contrôleur |
| Purge des rapports sans finding et du journal d'audit | SECREVIEW_EMPTY_REPORT_RETENTION_DAYS (90), SECREVIEW_AUDIT_RETENTION_DAYS (365), SECREVIEW_AUDIT_MAX_EVENTS |
Conservation, conservation de l'audit |
| Envoi du journal d'audit vers syslog | un serveur syslog défini dans l'environnement ou dans l'interface | Envoi vers syslog |
| Envoi automatique des findings par les connecteurs | un connecteur activé, un seuil de sévérité sur le projet ; SECREVIEW_CONNECTORS_ENABLED=false verrouille tout |
Connecteurs |
Le serveur MCP n'en exécute aucune.
Connexions sortantes¶
Le contrôleur est le seul processus qui peut se connecter à l'extérieur, et chaque connexion est
désactivée par défaut : le téléchargement OSV, le fournisseur OIDC, la recherche
d'une nouvelle version sur la page À propos (SECREVIEW_UPDATE_CHECK_ENABLED), le serveur
syslog et les connecteurs. Elles passent en HTTPS (syslog : UDP, TCP ou TLS), par le proxy
sortant (HTTPS_PROXY, NO_PROXY, ou l'écran Paramètres). Voir
Sécurité et limites.
Exposition¶
Exposez le contrôleur avec TLS (intégré ou reverse proxy) aux personnes (interface web, jetons personnels) et aux runners des pipelines. Derrière un reverse proxy ou un Ingress :
- réglez
SECREVIEW_FORWARDED_ALLOW_IPSsur l'adresse du proxy, sinon tous les clients partagent son adresse pour la limitation des connexions et la limite de débit ; - réglez
SECREVIEW_WEB_URL(lu par le serveur MCP) sur l'URL publique de l'interface, pour qu'un agent sans jeton valide sache où en créer un.
Plusieurs réplicas¶
Le contrôleur peut tourner en plusieurs réplicas sur la même base PostgreSQL : les migrations
s'attendent (verrou consultatif), les sessions sont partagées grâce à SECREVIEW_SECRET_KEY, les
compteurs de connexion et de limite de débit sont tenus en base, un seul réplica à la fois
télécharge les avis OSV, et un lot syslog n'est envoyé qu'une fois. Donnez le même
SECREVIEW_SECRET_KEY à tous les réplicas.
Commandes d'administration¶
Les autres commandes de security-review-controller s'exécutent là où tourne le contrôleur
(même SECREVIEW_DATABASE_URL), par exemple docker compose exec controller … ou
kubectl exec deploy/secreview-controller -c controller -- … :
| Commande | Rôle |
|---|---|
db |
migrer le schéma, afficher sa révision, purger des rapports |
token |
créer, lister, renouveler et révoquer les jetons (Jetons Bearer) |
user, group |
comptes de l'interface web et leurs groupes (Comptes utilisateurs) |
project |
valider ou refuser les projets demandés par un enrôlement |
rules |
importer et exporter les règles internes et les checklists |
advisories |
importer ou télécharger la base OSV |
audit |
lire ou purger le journal d'audit |
Toutes leurs options : Ligne de commande. Les variables du contrôleur : Configuration.
Premier administrateur¶
À son premier démarrage, tant qu'aucun compte n'existe, le contrôleur crée le compte local
admin (SECREVIEW_ADMIN_USERNAME), membre du groupe administrators (rôle admin),
avec un mot de passe aléatoire que personne ne connaît et qui n'est jamais écrit dans les
journaux. Le seul moyen d'obtenir un mot de passe est la CLI, qui en définit un nouveau et
l'affiche une fois :
security-review-controller user reset-password admin
# {"id": 1, "username": "admin", …, "must_change_password": true, "password": "q3V…"}
WARNING security_review_mcp.controller: Initial Web UI administrator 'admin' created: set its password with `security-review-controller user reset-password admin`
Traitez ce mot de passe comme un secret et connectez-vous avec admin. L'interface exige alors
un nouveau mot de passe avant toute autre chose : elle ouvre Mon compte, et l'API refuse
toutes les autres routes (403) tant que le mot de passe n'est pas changé. S'il est perdu,
voir mot de passe perdu.
Il en va de même de tout mot de passe défini par quelqu'un d'autre : un compte local créé ou réinitialisé par un administrateur (écran ou CLI) change son mot de passe à sa première connexion. L'écran Utilisateurs affiche mot de passe à changer jusque-là.
Mot de passe perdu¶
Le mot de passe est stocké sous forme d'empreinte scrypt : il est impossible de le relire. Remplacez-le avec la CLI du contrôleur, sur la même base :
security-review-controller user reset-password admin
La commande affiche le compte en JSON, avec un mot de passe généré dans le champ password
(| jq -r .password pour ne garder que lui). L'ancien mot de passe est aussitôt invalidé, les
sessions ouvertes du compte prennent fin et le nouveau mot de passe doit être changé à la
prochaine connexion. Pour le choisir, donnez-le sur la première ligne de l'entrée standard (il
n'apparaît alors ni dans l'historique du shell ni dans la liste des processus, et le JSON n'a pas
de champ password) :
printf '%s\n' "$NEW_PASSWORD" | security-review-controller user reset-password admin --password-stdin
Lancez-la là où tourne le contrôleur :
# Docker Compose
docker compose exec controller security-review-controller user reset-password admin
# Kubernetes
kubectl -n secreview exec deploy/secreview-controller -c controller -- \
security-review-controller user reset-password admin
Un compte OIDC n'a pas de mot de passe (code de sortie 2). S'il ne reste aucun administrateur,
créez-en un avec security-review-controller user create NAME --group administrators (mot de
passe généré, affiché de la même façon) : le compte admin n'est créé au démarrage que tant
qu'aucun compte n'existe.
Connexion et sessions¶
Quand le contrôleur n'accepte plus la session pendant la navigation (session expirée, mot de passe changé dans un autre onglet, compte supprimé), une fenêtre Session expirée s'ouvre : Se reconnecter ouvre la page de connexion puis revient à la même page.
La page de connexion propose l'identifiant et le mot de passe d'un compte local et, avec la
connexion OIDC, le bouton du fournisseur d'identité ; elle
n'accepte pas de jeton. POST /api/v1/auth/login vérifie le mot de passe d'un compte local et
renvoie un jeton de session signé, valable SECREVIEW_SESSION_HOURS heures (8 par défaut),
que l'onglet utilise comme un jeton Bearer :
curl -s -X POST https://secreview.example.com/api/v1/auth/login \
-H 'Content-Type: application/json' -d '{"username": "alice", "password": "…"}'
# {"session": "eyJ…", "expires_in": 28800, "username": "alice"}
Chaque session désigne un compte (local ou OIDC) ; à chaque requête, le contrôleur le relit en base :
- un changement de ses groupes, de leurs rôles ou d'un rôle s'applique immédiatement aux sessions ouvertes ;
- une suppression ou un nouveau mot de passe (changé par l'utilisateur ou réinitialisé
par un administrateur) ferme toutes les sessions du compte ;
PUT /api/v1/me/passwordremet une nouvelle session à l'onglet qui a changé le mot de passe.
Les sessions sont signées avec SECREVIEW_SECRET_KEY (32 caractères au moins), partagé par
toutes les répliques du contrôleur. Sans lui, un secret aléatoire est tiré à chaque démarrage
(un avertissement est journalisé) : les sessions se terminent au redémarrage du contrôleur et ne
sont pas partagées entre répliques. Définissez-le en production (en Kubernetes, depuis un
Secret : voir Kubernetes).
Les échecs de connexion sont limités : après 5 échecs en 15 minutes pour un identifiant
depuis une même adresse, ou 20 échecs depuis une adresse tous identifiants confondus, la
connexion est refusée (429, en-tête Retry-After), même avec le bon mot de passe. Les
compteurs sont tenus en base : tous les workers et réplicas du contrôleur les partagent. Chaque
adresse est aussi limitée sur toute l'API REST (par défaut 600 requêtes par minute, 30 sur les
routes de connexion, voir SECREVIEW_RATE_LIMIT_*). Derrière un reverse proxy, l'adresse vue est celle du
proxy, et la limite s'appliquerait alors à tous les clients du proxy : déclarez le proxy dans
SECREVIEW_FORWARDED_ALLOW_IPS pour que l'adresse du client
soit lue dans X-Forwarded-For. Chaque connexion et chaque échec
sont journalisés, sans le mot de passe :
INFO security_review_mcp.web: Web login of alice (role triager) from 10.0.0.12
WARNING security_review_mcp.web: Failed web login of alice from 10.0.0.12
Sécurité de l'interface web¶
- Les fichiers statiques de
/ui/sont publics (ils ne contiennent aucune donnée) ; l'API/api/v1/exige une session ou un jetonadminouapi(les routes de pipeline/api/v1/ci/prennent les jetonsci, voir Ce qu'il sert). Seules les routes de connexion (/api/v1/auth/…) sont publiques, ainsi que le téléchargement du client de pipeline (/api/ci-client/) sauf s'il est désactivé. - Les mots de passe des comptes ne sont jamais stockés : seule une empreinte scrypt salée
(N=2¹⁴, r=8, p=5) est enregistrée et aucune réponse de l'API ne la renvoie. La vérification
prend le même temps que l'identifiant existe ou non, et la réponse d'échec est identique. La documentation Swagger (
/api/docs) et son schéma (/api/openapi.json) sont désactivés par défaut (SECREVIEW_SWAGGER_ENABLED=trueles sert) ; une fois activés, ils sont publics et ne donnent accès à aucune donnée. Leurs fichiers sont fournis avec la build de l'interface web : rien n'est chargé depuis Internet, et Swagger n'envoie jamais le schéma au validateur en ligne. Dans Swagger, le cadenas d'une route indique qu'elle exige un jeton : il reste ouvert tant que vous n'avez pas saisi de jeton avec Authorize, et ne veut pas dire que la route est accessible sans jeton. Saisissez-y un jeton API. - Le contrôleur n'expose aucune route d'écriture sans jeton.
L'enrôlement est servi par le serveur
MCP (
POST /api/v1/enroll, public) : il demande un projet, jamais un jeton ; le contrôleur liste et traite les demandes (GET /api/v1/projects/pending,POST /api/v1/projects/pending/{id}/approve,DELETE /api/v1/projects/pending/{id}, permissionprojects.create). - Le jeton est envoyé dans l'en-tête
Authorization: pas de cookie, donc pas de CSRF. Seule la connexion OIDC pose un cookie,HttpOnly,SameSite=Lax,Secureen HTTPS, limité à l'URL de retour et valable 10 minutes : il lie le retour du fournisseur au navigateur qui a lancé la connexion (protection contre le login CSRF). Le jeton de session est remis dans le fragment d'URL (#session=...), jamais envoyé à un serveur, et l'interface l'efface aussitôt de la barre d'adresse et de l'historique. - Il est conservé dans le
sessionStoragede l'onglet et disparaît à sa fermeture. - Les pages sont servies avec une CSP stricte (
script-src 'self', aucune ressource externe),X-Frame-Options: DENY,X-Content-Type-Options: nosniffetReferrer-Policy: no-referrer. - Servez l'interface en HTTPS (TLS intégré ou reverse proxy) dès qu'elle n'écoute plus sur localhost : le jeton circule à chaque requête.
Le paquet publié et l'image Docker contiennent l'application déjà compilée. Si vous installez depuis les sources sans la compiler, le contrôleur ne sert que l'API REST et un avertissement est journalisé. Voir Contribuer pour la compiler.