La politique de gouvernance par agent
Chaque agent porte une politique : un kill switch, un plafond de durée de vie des jetons, un plafond de scopes et une allowlist d'audiences, appliqués fail-closed à chaque émission de jeton et à l'introspection.
Le client de l'agent définit ce qu'il pourrait faire au maximum ; la politique de gouvernance définit ce qu'il peut faire maintenant, et un administrateur peut la resserrer à tout instant sans toucher au code ni aux identifiants de l'agent. L'application se fait dans le chemin d'émission des jetons lui-même : il n'existe aucun moyen de la contourner.
Une politique par agent
PUT /v1/admin/agents/{clientId}/policy pose la politique (permission apps:manage ; depuis la console, le changement exige un step-up MFA récent). Il n'y a pas de GET sur ce chemin : la politique effective de chaque agent se lit dans l'inventaire, GET /v1/admin/agents.
| Champ | Type | Effet |
|---|---|---|
enabled | booléen | Kill switch : false refuse toute nouvelle émission de jeton |
maxTokenTtlSeconds | entier | Plafond de durée de vie des jetons émis (0 signifie sans plafond) |
scopeCeiling | tableau de chaînes | Les scopes émis sont intersectés avec cette liste (vide signifie sans plafond) |
allowedAudiences | tableau d'URI | Allowlist de ressources RFC 8707 pour Token Exchange (vide signifie toute ressource valide) |
curl -sS -X PUT https://accounts.obexal.com/v1/admin/agents/8Zl2vQx0T9hK3mW1s5nDgA/policy \
-H "Authorization: Bearer $OBEXAL_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"enabled": true,
"maxTokenTtlSeconds": 300,
"scopeCeiling": ["tickets:read"],
"allowedAudiences": ["https://api.example.eu/tickets"]
}'
# 204 No ContentRemplacez accounts.obexal.com par votre domaine personnalisé si votre organisation en utilise un.
Le PUT remplace toute la politique. Un corps qui omet enabled le met à false et neutralise l'agent. Envoyez toujours l'objet complet.
La requête est rejetée avec 400 invalid_request quand le client n'est pas un agent, quand scopeCeiling n'est pas un sous-ensemble des scopes du client (un plafond au-dessus du périmètre n'aurait pas de sens), quand une audience n'est pas une URI absolue valide, quand le TTL est négatif, ou quand allowedAudiences est posé sur un agent qui ne porte pas le grant token-exchange : cette liste ne contraint que l'échange, et l'accepter là où elle n'a aucun effet donnerait une fausse impression de sécurité.
Kill switch
enabled: false refuse immédiatement toute nouvelle émission de jeton par l'agent avec invalid_grant, quel que soit le grant, et chaque tentative est enregistrée comme une anomalie killed_use. Les jetons déjà en circulation sont rapportés active: false à l'introspection. La page du kill switch couvre le confinement de bout en bout, y compris le confinement automatique sur dérive d'anomalies.
Plafond de durée de vie des jetons
La durée de vie effective de chaque jeton émis à l'agent est le minimum du défaut serveur et du plafond de la politique. Le défaut serveur est de 10 minutes, donc "maxTokenTtlSeconds": 300 raccourcit les jetons de l'agent à 5 minutes ; le plafond ne peut jamais allonger au-delà du défaut serveur. Combiné à l'absence de refresh token sur les grants machine, cela borne étroitement le rayon d'action d'un jeton fuité.
Plafond de scopes
Le plafond s'applique par intersection à chaque émission : les scopes accordés sont les scopes demandés restreints au plafond. Le champ scope de la réponse fait foi, et il peut être plus étroit que la demande. Quand rien ne survit à l'intersection, la requête échoue :
curl -sS -X POST https://accounts.obexal.com/oauth/token \
-u "8Zl2vQx0T9hK3mW1s5nDgA:$AGENT_CLIENT_SECRET" \
-d grant_type=client_credentials \
-d scope="tickets:write"
# 400 {"error": "invalid_scope"}tickets:write est dans les scopes du client mais hors du plafond ["tickets:read"] posé plus haut : l'intersection est vide et la requête est refusée.
Allowlist d'audiences
Elle n'a de sens que pour les agents portant le grant token-exchange. Une fois la liste posée, le paramètre resource de l'échange devient obligatoire et doit appartenir à la liste, sinon invalid_target : un agent contraint ne peut frapper des jetons que pour les resource servers que vous avez approuvés. La comparaison est canonique des deux côtés (schéma et hôte en minuscules, port par défaut retiré, slash final retiré), si bien que HTTPS://api.example.eu:443/tickets/ et https://api.example.eu/tickets correspondent.
Fail-closed, sur tous les grants
Deux règles distinctes gouvernent la résolution de la politique :
- une politique absente se résout en défauts sûrs (activé, sans plafonds) : la gouvernance est opt-in, un agent sans politique fonctionne normalement ;
- une politique illisible (erreur de stockage) refuse l'émission : un kill switch qu'on ne peut pas vérifier doit échouer fermé, jamais se rouvrir en silence.
L'application est centralisée dans le chemin d'émission : un agent qui porte aussi authorization_code ou refresh_token ne peut pas contourner sa politique par un flux interactif, car le kill switch, l'expiration de l'agent, le plafond de scopes et le plafond de TTL s'appliquent aussi à ces grants. L'introspection suit la même règle : une erreur de stockage ou un agent désactivé produit active: false.
Retour aux défauts
DELETE /v1/admin/agents/{clientId}/policy supprime la politique et renvoie 204 : l'agent revient aux défauts (activé, sans plafonds). L'opération est idempotente et, comme tout changement de politique, elle est écrite dans le journal d'audit.