Obexal Docs

Docs/Sécurité et conformité/Modèle de sécurité

Modèle de sécurité

Les familles de contrôles qui protègent chaque flux Obexal, cryptographie, isolation des tenants, résistance aux abus, fail-closed par défaut et durcissement HTTP.

Obexal applique un petit ensemble de contrôles non négociables, uniformément sur tous les flux d'authentification. Cette page les résume par famille; chaque page liée couvre le détail opérationnel.

Cryptographie

Les mots de passe sont hachés avec Argon2id (fonction à coût mémoire, par défaut 64 MiB de mémoire, 3 itérations, parallélisme 2, ajustable via l'environnement). Chaque hash utilise un sel aléatoire frais issu d'un CSPRNG et est stocké au format standard PHC. La vérification recalcule avec les paramètres stockés et compare en temps constant.

Les secrets applicatifs au repos (secrets TOTP, clé privée de signature OIDC, secrets de signature des webhooks) sont chiffrés en AES-256-GCM, avec une clé de 32 octets fournie par l'environnement et un nonce aléatoire par chiffrement. L'authentification GCM garantit qu'un ciphertext altéré échoue au déchiffrement au lieu de produire un clair corrompu. La clé de chiffrement tourne sans perte de données: voir Rotation des clés.

Les jetons (access token et ID token) sont des JWT signés en RS256 (RSA-2048), avec un en-tête kid. Le JWKS ne publie que les clés publiques. La vérification impose RS256: alg: none et les algorithmes HMAC sont refusés, ce qui ferme les attaques par confusion d'algorithme. Les clés de signature tournent avec une période de grâce, si bien que les jetons encore en vol restent vérifiables. Voir Valider les jetons.

Le transport est en TLS, terminé au reverse proxy: TLS 1.2 minimum, TLS 1.3 négocié quand le client le supporte. Les connexions SMTP sortantes exigent elles aussi TLS 1.2 minimum.

Isolation multi-tenant

Pour toute requête authentifiée, le tenant est celui lié à la session, jamais un en-tête: l'en-tête X-Obexal-Tenant n'est lu que sur les flux pré-authentification (connexion, inscription) pour choisir le tenant d'authentification. Un utilisateur connecté ne peut pas franchir une frontière de tenant en forgeant un en-tête.

Chaque lecture et chaque écriture est cantonnée au tenant de la session. Demander l'objet d'un autre tenant par son identifiant renvoie 404, pas 403: aucune fuite d'existence. Les opérations sur jetons (refresh, révocation, introspection) revérifient explicitement le tenant, et /oauth/authorize refuse qu'un utilisateur autorise un client appartenant à un autre tenant.

La suspension est un kill switch. Suspendre un tenant bloque toute connexion et rend immédiatement inopérante chaque session existante de ce tenant. Le même principe s'applique par utilisateur, et chaque agent IA dispose de son propre kill switch.

Résistance aux abus

  • Anti-énumération: inscription, réinitialisation de mot de passe et démarrage passwordless renvoient toujours le même 202 générique; un échec de connexion est toujours 401 invalid_credentials. Quand le compte n'existe pas, un hash factice est calculé pour maintenir un temps de réponse quasi constant, et l'email transactionnel part de manière asynchrone: le timing ne révèle rien.
  • Rate limiting multicouche: par email et par IP à la connexion, par IP sur la connexion par passkey et les callbacks sociaux, par email plus IP sur les flux passwordless et de récupération, avec des compteurs cantonnés au tenant.
  • Verrouillage de compte: les échecs sont comptés par (tenant, email), si bien que des échecs de mot de passe répétés verrouillent le compte lui-même. La clé étant le compte, la rotation d'IP ne le contourne pas. Les échecs sur un email inconnu produisent le même 429. Un admin déverrouille avec POST /v1/admin/users/unlock.
  • Détection de percée: une connexion qui réussit alors que le compte avait atteint le seuil de verrouillage est consignée comme incident de sécurité critique (bruteforce_breakthrough): le signal fort qu'une campagne de credential stuffing a fini par deviner juste.

Fail-closed sur les chemins sensibles

  • LDAP: si l'annuaire amont est injoignable pendant la connexion, la tentative échoue avec une erreur générique. Il n'existe aucun repli plus faible. Voir LDAP et Active Directory.
  • Gouvernance des agents: le kill switch et les plafonds de permissions des agents échouent fermé. Une erreur de stockage à la lecture de la politique d'un agent refuse l'émission de jetons plutôt que de réactiver silencieusement un agent neutralisé.
Note

Deux contrôles donnent la priorité à la disponibilité pour qu'un signal momentanément absent n'enferme jamais un tenant entier dehors. Les règles par pays de l'accès conditionnel s'appuient sur une base GeoIP locale: installez-la et surveillez sa présence pour que les règles de pays s'appliquent. Le moteur de risque est désactivé par défaut et opt-in, et il est conçu pour ne jamais bloquer une connexion légitime quand l'un de ses signaux est indisponible. Voir Accès conditionnel et Accès basé sur le risque.

Sessions, CSRF et durcissement HTTP

Les sessions sont des jetons opaques de 32 octets aléatoires. Le serveur n'en stocke que le hash SHA-256, jamais la valeur brute. Le cookie de session est HttpOnly, Secure et SameSite. Une réinitialisation de mot de passe ou un changement d'email confirmé révoque toutes les sessions de l'utilisateur. Voir Sessions et déconnexion.

La protection CSRF est en double-submit: les méthodes non sûres doivent envoyer un en-tête X-CSRF-Token égal au cookie CSRF, comparé en temps constant. Les préfixes /oauth/, /scim/, /saml/ et /.well-known/ en sont exemptés parce qu'ils ne s'authentifient jamais par cookie: il n'y a pas de vecteur CSRF à protéger.

Chaque réponse porte des en-têtes de durcissement: HSTS quand le service est servi en HTTPS, une Content-Security-Policy tout-refus sur les réponses d'API (default-src 'none'), X-Content-Type-Options: nosniff, X-Frame-Options: DENY, une Referrer-Policy stricte, et Cache-Control: no-store sur les réponses d'authentification et de jetons. Le CORS est une allowlist stricte, et les messages d'erreur sont génériques (le détail ne va que dans les logs serveur).

Pages liées