Aller au contenu principal

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_admin et un compte security_officer distincts (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.json importé.
  • Valeurs Helm de production : deploy/helm/enterprise-ai/values-prod.yaml.
danger

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​

  1. Activer la garde de démarrage. EAI_ENVIRONMENT=prod fait refuser au Control Plane tout démarrage avec un réglage dangereux (eai.security.startup.enforce, qui liste chaque problème). En staging, les mêmes problèmes sont seulement journalisés.

    controlPlane:
    config:
    EAI_ENVIRONMENT: prod

    Résultat attendu : le pod démarre ; aucun message must dans le journal du Control Plane.

    En cas d'échec : le pod s'arrête avec une RuntimeError qui liste chaque réglage fautif ; corrigez-les un par un avec les étapes suivantes.

  2. Chiffrer chaque liaison (TLS). La garde refuse EAI_DATABASE_URL et EAI_RUNTIME_DATABASE_URL sans sslmode=require, verify-ca ou verify-full, EAI_REDIS_URL sans rediss://, et un Redis sans EAI_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 en https://.

    psql "<EAI_DATABASE_URL>" -c "SHOW ssl"

    <EAI_DATABASE_URL> : la chaîne de connexion lue dans OpenBao, avec sslmode=verify-full et la CA.

    Résultat attendu : on. La réponse de l'API porte Strict-Transport-Security: max-age=31536000; includeSubDomains.

    En cas d'échec : vérifiez controlPlane.secretFormats de Helm et le certificat de la base (../operations/data-services-hardening.md).

  3. Fermer l'authentification de développement. EAI_AUTH_DEV=false (les en-têtes X-Dev-* authentifient n'importe qui ; eai.auth.dev_auth_active les ignore de toute façon en prod). Configurez l'OIDC : EAI_OIDC_ISSUER, EAI_OIDC_AUDIENCE (l'audience devient obligatoire dans le jeton), éventuellement EAI_OIDC_JWKS_URL. Gardez EAI_JWT_ALGORITHMS asymétrique (HS* et none sont retirés dans tous les environnements). EAI_AUTH_SPIFFE=true seulement derrière le maillage, avec EAI_TRUSTED_PROXIES limité aux pods de la passerelle.

    curl -s <control-plane-url>/v1/identity/whoami -H "Authorization: Bearer <token>"

    Résultat attendu : auth_method vaut oidc et tenant_id est celui de l'utilisateur.

    En cas d'échec : 401 invalid_token avec reason ; vérifiez l'émetteur, l'audience et l'horloge (EAI_JWT_LEEWAY_SECONDS).

  4. Sécuriser le compte d'installation. Le compte local créé par EAI_BOOTSTRAP_ADMIN_USERNAME et EAI_BOOTSTRAP_ADMIN_PASSWORD a les rôles platform_admin et tenant_admin et 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 de EAI_LOCAL_PASSWORD_MIN_LENGTH caractères, sans chiffre ou sans lettre, égal à l'identifiant ou connu). Les comptes locaux se verrouillent après EAI_LOCAL_AUTH_MAX_FAILURES échecs pendant EAI_LOCAL_AUTH_LOCKOUT_SECONDS. Si toute l'authentification passe par Keycloak, posez EAI_LOCAL_AUTH_ENABLED=false.

    Résultat attendu : après la première connexion, retirez EAI_BOOTSTRAP_ADMIN_PASSWORD de l'environnement ; un redémarrage ne réinitialise jamais le compte.

    En cas d'échec : 403 password_change_required sur toute autre route tant que le mot de passe initial n'est pas changé.

  5. Restreindre hôtes, origines et documentation. EAI_ALLOWED_HOSTS liste les noms publics de l'API, EAI_CORS_ORIGINS les origines exactes du portail (jamais *), EAI_DOCS_ENABLED=false ferme /docs, /redoc et /openapi.json. EAI_METRICS_TOKEN (32 caractères minimum) protège GET /metrics.

    Résultat attendu : une requête avec un en-tête Host inconnu est refusée ; /docs répond 404.

    En cas d'échec : la garde nomme le réglage fautif.

  6. Fermer la sortie réseau. EAI_EGRESS_ALLOW_HTTP=false (obligatoire), EAI_EGRESS_ALLOWED_PORTS=443, EAI_EGRESS_PRIVATE_ALLOWLIST limité aux services internes exacts. Par tenant, egress_allowed_hosts de PUT /v1/security/settings restreint la sortie à une liste. Les serveurs MCP internes sont dans EAI_MCP_INTERNAL_HOSTS.

    Résultat attendu : un outil qui appelle 169.254.169.254 est refusé et l'événement security.egress.denied est écrit dans l'audit.

    En cas d'échec : voir runbooks.md et ../operations/network-dns-hardening.md.

  7. Brancher OPA et OpenFGA en échec fermé. Avec EAI_OPA_URL, chaque politique engine=opa est évaluée par OPA ; OPA injoignable vaut refus (403 policy_denied, raison policy engine unavailable). En production, préférez EAI_OPA_DELIVERY=bundle avec EAI_POLICY_BUNDLE_SIGNING_KEY_FILE (bundles signés RS256, obligatoire en mode bundle). Avec EAI_OPENFGA_URL, EAI_OPENFGA_STORE_ID, EAI_OPENFGA_MODEL_ID et EAI_OPENFGA_API_TOKEN, une panne d'OpenFGA répond 502 authz_backend_error et le refus est audité. EAI_POLICY_DEFAULT=deny refuse une action qu'aucune règle ne couvre (défaut du code : allow, réglable aussi par tenant avec policy_default).

    curl -s <control-plane-url>/v1/policies/engines -H "Authorization: Bearer <token>"

    Résultat attendu : OPA joignable et in_sync à true en mode bundle.

    En cas d'échec : in_sync: false signifie qu'OPA garde l'ancienne révision (bundle refusé) ; voir identity-policy-plane.md.

  8. 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_URL partage 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.

  9. Vérifier les en-têtes et la CSP. EAI_SECURITY_HEADERS_ENABLED=true ajoute à 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, buildCsp dans portal/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.

  10. Placer les clés hors de la base. La garde exige EAI_OPENBAO_URL et EAI_OPENBAO_TOKEN (et EAI_AUDIT_SIGNING différent de hmac), EAI_AUDIT_SEAL_SIGNING_KEY_REF (clé Ed25519 des sceaux dans OpenBao), EAI_AUDIT_PAYLOAD_KEK=openbao-transit, EAI_CONTEXT_SIGNING_KEY_REF et EAI_CONTEXT_REQUIRED=true. Pour un HSM : EAI_KMS_PROVIDER=pkcs11 et ses trois réglages. Détails : secrets-vault.md.

    Résultat attendu : GET /v1/context/jwks publie une clé ; GET /v1/evidence/verify répond sans rupture.

    En cas d'échec : 503 context_authority_unavailable signifie que la clé de la Context Authority est illisible.

  11. Imposer l'analyse des fichiers. EAI_FILE_SCAN_MODE=enforce, EAI_CLAMAV_URLS, EAI_CLAMAV_API_KEY, EAI_YARAX_URLS, EAI_YARAX_API_KEY et EAI_FILE_SCAN_CA_FILE. Détails : file-security-pipeline.md.

    Résultat attendu : GET /v1/file-security/status montre 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.

  12. 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.

  13. 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, puis POST /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_duties quand le demandeur tente d'approuver.

  14. Sauvegarder et prouver la restauration. services/control-plane/scripts/pg_backup.sh et services/control-plane/scripts/pg_restore.sh conservent 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 dans GET /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/verify et GET /v1/evidence/verify sont 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​

Dépannage​

SymptômeCauseCorrection
Le Control Plane refuse de démarrerRéglage dangereux avec EAI_ENVIRONMENT=prodCorriger chaque ligne listée par la garde
Toutes les requêtes semblent venir de la passerelleEAI_TRUSTED_PROXIES fauxMettre les CIDR des pods de la passerelle
403 policy_denied sur chaque appel d'outilOPA injoignableRétablir OPA ; le refus est voulu (échec fermé)
502 authz_backend_errorOpenFGA injoignableRétablir OpenFGA ; le refus est audité
503 sur chaque dépôt de fichierAucun moteur ClamAV ou YARA-X joignableRétablir les moteurs (file-security-pipeline.md)