Durcir une installation de production
Cette page donne la liste de contrôle de sécurité d'une installation de production de la Navin Platform : chaque étape dit ce qu'elle fait, pourquoi, la commande ou l'écran, le résultat attendu et quoi faire en cas d'échec. Elle s'adresse à l'opérateur plateforme (rôle platform_admin) et au responsable sécurité (security_officer). Durée indicative : une demi-journée, sauvegardes comprises.
English version: en/hardening.md
Les réglages cités sont des champs de services/control-plane/src/eai/config.py (préfixe EAI_) ; leur référence complète est dans configuration.md et ../operations/configuration.md. Le durcissement de l'infrastructure (mTLS SPIFFE, Cilium, pods, chaîne d'approvisionnement) est décrit dans ../operations/security-hardening.md et n'est pas répété ici.
Avant de commencer
- Accès : un compte
platform_adminet un comptesecurity_officerdistincts (plusieurs étapes exigent deux personnes). - OpenBao initialisé et descellé, entrées écrites selon ../operations/secrets.md.
- Keycloak avec le realm
deploy/keycloak/realm-enterprise-ai.jsonimporté. - Valeurs Helm de production :
deploy/helm/enterprise-ai/values-prod.yaml.
Ne désactivez jamais la garde de démarrage pour « faire démarrer » le Control Plane. Chaque message qu'elle affiche correspond à une faille réelle : corrigez le réglage.
Étapes
-
Activer la garde de démarrage.
EAI_ENVIRONMENT=prodfait refuser au Control Plane tout démarrage avec un réglage dangereux (eai.security.startup.enforce, qui liste chaque problème). Enstaging, les mêmes problèmes sont seulement journalisés.controlPlane:config:EAI_ENVIRONMENT: prodRésultat attendu : le pod démarre ; aucun message
mustdans le journal du Control Plane.En cas d'échec : le pod s'arrête avec une
RuntimeErrorqui liste chaque réglage fautif ; corrigez-les un par un avec les étapes suivantes. -
Chiffrer chaque liaison (TLS). La garde refuse
EAI_DATABASE_URLetEAI_RUNTIME_DATABASE_URLsanssslmode=require,verify-caouverify-full,EAI_REDIS_URLsansrediss://, et un Redis sansEAI_REDIS_PASSWORD_FILE(utilisateur ACL). Activez HSTS (EAI_HSTS_ENABLED=true,EAI_HSTS_MAX_AGE_SECONDS=31536000). Les moteurs d'analyse de fichiers doivent être enhttps://.psql "<EAI_DATABASE_URL>" -c "SHOW ssl"<EAI_DATABASE_URL>: la chaîne de connexion lue dans OpenBao, avecsslmode=verify-fullet la CA.Résultat attendu :
on. La réponse de l'API porteStrict-Transport-Security: max-age=31536000; includeSubDomains.En cas d'échec : vérifiez
controlPlane.secretFormatsde Helm et le certificat de la base (../operations/data-services-hardening.md). -
Fermer l'authentification de développement.
EAI_AUTH_DEV=false(les en-têtesX-Dev-*authentifient n'importe qui ;eai.auth.dev_auth_activeles ignore de toute façon enprod). Configurez l'OIDC :EAI_OIDC_ISSUER,EAI_OIDC_AUDIENCE(l'audience devient obligatoire dans le jeton), éventuellementEAI_OIDC_JWKS_URL. GardezEAI_JWT_ALGORITHMSasymétrique (HS*etnonesont retirés dans tous les environnements).EAI_AUTH_SPIFFE=trueseulement derrière le maillage, avecEAI_TRUSTED_PROXIESlimité aux pods de la passerelle.curl -s <control-plane-url>/v1/identity/whoami -H "Authorization: Bearer <token>"Résultat attendu :
auth_methodvautoidcettenant_idest celui de l'utilisateur.En cas d'échec : 401
invalid_tokenavecreason; vérifiez l'émetteur, l'audience et l'horloge (EAI_JWT_LEEWAY_SECONDS). -
Sécuriser le compte d'installation. Le compte local créé par
EAI_BOOTSTRAP_ADMIN_USERNAMEetEAI_BOOTSTRAP_ADMIN_PASSWORDa les rôlesplatform_adminettenant_adminet doit changer son mot de passe à la première connexion (POST /v1/auth/password). En production, la garde refuse un mot de passe faible (moins deEAI_LOCAL_PASSWORD_MIN_LENGTHcaractères, sans chiffre ou sans lettre, égal à l'identifiant ou connu). Les comptes locaux se verrouillent aprèsEAI_LOCAL_AUTH_MAX_FAILURESéchecs pendantEAI_LOCAL_AUTH_LOCKOUT_SECONDS. Si toute l'authentification passe par Keycloak, posezEAI_LOCAL_AUTH_ENABLED=false.Résultat attendu : après la première connexion, retirez
EAI_BOOTSTRAP_ADMIN_PASSWORDde l'environnement ; un redémarrage ne réinitialise jamais le compte.En cas d'échec : 403
password_change_requiredsur toute autre route tant que le mot de passe initial n'est pas changé. -
Restreindre hôtes, origines et documentation.
EAI_ALLOWED_HOSTSliste les noms publics de l'API,EAI_CORS_ORIGINSles origines exactes du portail (jamais*),EAI_DOCS_ENABLED=falseferme/docs,/redocet/openapi.json.EAI_METRICS_TOKEN(32 caractères minimum) protègeGET /metrics.Résultat attendu : une requête avec un en-tête
Hostinconnu est refusée ;/docsrépond 404.En cas d'échec : la garde nomme le réglage fautif.
-
Fermer la sortie réseau.
EAI_EGRESS_ALLOW_HTTP=false(obligatoire),EAI_EGRESS_ALLOWED_PORTS=443,EAI_EGRESS_PRIVATE_ALLOWLISTlimité aux services internes exacts. Par tenant,egress_allowed_hostsdePUT /v1/security/settingsrestreint la sortie à une liste. Les serveurs MCP internes sont dansEAI_MCP_INTERNAL_HOSTS.Résultat attendu : un outil qui appelle
169.254.169.254est refusé et l'événementsecurity.egress.deniedest écrit dans l'audit.En cas d'échec : voir runbooks.md et ../operations/network-dns-hardening.md.
-
Brancher OPA et OpenFGA en échec fermé. Avec
EAI_OPA_URL, chaque politiqueengine=opaest évaluée par OPA ; OPA injoignable vaut refus (403policy_denied, raisonpolicy engine unavailable). En production, préférezEAI_OPA_DELIVERY=bundleavecEAI_POLICY_BUNDLE_SIGNING_KEY_FILE(bundles signés RS256, obligatoire en mode bundle). AvecEAI_OPENFGA_URL,EAI_OPENFGA_STORE_ID,EAI_OPENFGA_MODEL_IDetEAI_OPENFGA_API_TOKEN, une panne d'OpenFGA répond 502authz_backend_erroret le refus est audité.EAI_POLICY_DEFAULT=denyrefuse une action qu'aucune règle ne couvre (défaut du code :allow, réglable aussi par tenant avecpolicy_default).curl -s <control-plane-url>/v1/policies/engines -H "Authorization: Bearer <token>"Résultat attendu : OPA joignable et
in_syncàtrueen mode bundle.En cas d'échec :
in_sync: falsesignifie qu'OPA garde l'ancienne révision (bundle refusé) ; voir identity-policy-plane.md. -
Garder les limites de débit et de taille.
EAI_RATE_LIMIT_ENABLED=true,EAI_RATE_LIMIT_PER_MINUTE(par principal),EAI_RATE_LIMIT_GATEWAY_PER_MINUTE(sur/v1/gateway/*),EAI_RATE_LIMIT_AUTH_FAILURES_PER_MINUTE(par IP, puis 429),EAI_MAX_BODY_BYTES,EAI_MAX_UPLOAD_BYTES,EAI_MAX_JSON_DEPTH. Avec plusieurs réplicas,EAI_REDIS_URLpartage les compteurs.Résultat attendu : au-delà du budget, réponse 429 et événement
security.rate_limited.En cas d'échec : réglage dans runbooks.md.
-
Vérifier les en-têtes et la CSP.
EAI_SECURITY_HEADERS_ENABLED=trueajoute à chaque réponse de l'API (erreurs comprises,eai.security.headers) :X-Content-Type-Options: nosniff,X-Frame-Options: DENY,Content-Security-Policy: default-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'none',Referrer-Policy: no-referrer,Permissions-Policy,Cross-Origin-Opener-Policy: same-origin,Cross-Origin-Resource-Policy: same-site. Le portail pose sa propre CSP avec nonce (portal/src/middleware.ts,buildCspdansportal/src/lib/security.ts).curl -sI <control-plane-url>/v1/identity/whoami | grep -i -E "content-security|x-frame|strict-transport"Résultat attendu : les trois en-têtes sont présents.
En cas d'échec : une violation de CSP se traite selon runbooks.md, jamais avec
unsafe-inline. -
Placer les clés hors de la base. La garde exige
EAI_OPENBAO_URLetEAI_OPENBAO_TOKEN(etEAI_AUDIT_SIGNINGdifférent dehmac),EAI_AUDIT_SEAL_SIGNING_KEY_REF(clé Ed25519 des sceaux dans OpenBao),EAI_AUDIT_PAYLOAD_KEK=openbao-transit,EAI_CONTEXT_SIGNING_KEY_REFetEAI_CONTEXT_REQUIRED=true. Pour un HSM :EAI_KMS_PROVIDER=pkcs11et ses trois réglages. Détails : secrets-vault.md.Résultat attendu :
GET /v1/context/jwkspublie une clé ;GET /v1/evidence/verifyrépond sans rupture.En cas d'échec : 503
context_authority_unavailablesignifie que la clé de la Context Authority est illisible. -
Imposer l'analyse des fichiers.
EAI_FILE_SCAN_MODE=enforce,EAI_CLAMAV_URLS,EAI_CLAMAV_API_KEY,EAI_YARAX_URLS,EAI_YARAX_API_KEYetEAI_FILE_SCAN_CA_FILE. Détails : file-security-pipeline.md.Résultat attendu :
GET /v1/file-security/statusmontre les deux moteurs prêts.En cas d'échec : sans moteur joignable, chaque dépôt de fichier est refusé (503), jamais accepté sans analyse.
-
Préparer le kill switch.
EAI_SOC_WEBHOOK_SECRET(32 caractères minimum) authentifie les webhooks SOC et les reçus du cluster ; vide, toute livraison est refusée. Vérifiez que le registre répond et que la procédure de reprise à quatre yeux est connue de deux responsables.curl -s <control-plane-url>/v1/soc/kill-switches -H "Authorization: Bearer <token>"Résultat attendu : une liste (vide sur une installation neuve).
En cas d'échec : voir soc-runbook.md.
-
Borner l'accès d'urgence.
EAI_BREAK_GLASS_MAX_SECONDS(défaut 3600) plafonne la durée d'un accès d'urgence ; l'approbation exige deux approbateurs distincts, jamais le demandeur ni le bénéficiaire. Faites un exercice :POST /v1/break-glass, deux approbations, puisPOST /v1/break-glass/{grant_id}/revoke.Résultat attendu : l'accès expire seul ou est révoqué, et chaque étape apparaît dans
GET /v1/audit/events.En cas d'échec : 403
separation_of_dutiesquand le demandeur tente d'approuver. -
Sauvegarder et prouver la restauration.
services/control-plane/scripts/pg_backup.shetservices/control-plane/scripts/pg_restore.shconservent une chaîne d'audit, des sceaux et des conservations légales vérifiables (test_restored_backup_keeps_verifiable_chain_seals_and_hold_state). Les plans de sauvegarde et de reprise du Navin Manager se lisent dansGET /v1/platform-manager/backup/plans; une restauration exige deux approbateurs autres que le demandeur (POST /v1/platform-manager/backup/restores/{plan_id}/approve).Résultat attendu : après restauration sur un environnement de test,
GET /v1/audit/verifyetGET /v1/evidence/verifysont verts.En cas d'échec : voir ../operations/ha-dr.md.
Vérifier
GET /v1/security/posture (rôles security_officer ou tenant_admin) renvoie chaque contrôle de posture et un résumé. Une installation durcie ne laisse aucun contrôle en échec parmi auth_dev_disabled, cors_restricted, allowed_hosts_restricted, hsts_enabled, docs_disabled, secrets_backend, oidc_configured, egress_http_disabled, egress_private_allowlist_narrow, rate_limit_enabled, trusted_proxies_restricted, spiffe_behind_proxy, database_tls, jwt_algorithms_asymmetric, prompt_injection_enabled.
curl -s <control-plane-url>/v1/security/posture -H "Authorization: Bearer <token>"
Étapes suivantes
- Rôles et droits : authentication-authorization.md.
- Gouvernance : governance.md.
- Procédures d'incident : runbooks.md.
Dépannage
| Symptôme | Cause | Correction |
|---|---|---|
| Le Control Plane refuse de démarrer | Réglage dangereux avec EAI_ENVIRONMENT=prod | Corriger chaque ligne listée par la garde |
| Toutes les requêtes semblent venir de la passerelle | EAI_TRUSTED_PROXIES faux | Mettre les CIDR des pods de la passerelle |
403 policy_denied sur chaque appel d'outil | OPA injoignable | Rétablir OPA ; le refus est voulu (échec fermé) |
502 authz_backend_error | OpenFGA injoignable | Rétablir OpenFGA ; le refus est audité |
| 503 sur chaque dépôt de fichier | Aucun moteur ClamAV ou YARA-X joignable | Rétablir les moteurs (file-security-pipeline.md) |