Un système agentique de production n’est jamais un agent isolé. C’est une constellation : un agent superviseur qui délègue à des agents spécialisés, des agents qui appellent des outils, des outils qui sont eux-mêmes des services internes (un RAG, une base vectorielle, une API métier). À chaque arête de ce graphe, une question : qui parle à qui, et comment le prouver ?
La réponse la plus répandue est aussi la plus fragile : des clés d’API
partagées. Un secret dans un .env, une variable d’environnement, un fichier
de config — distribué à la main, rarement tourné, jamais révoqué proprement, et
trivialement exfiltrable par une injection indirecte. SPIFFE et SPIRE proposent
autre chose : donner à chaque workload — chaque agent, chaque service — une
identité cryptographique forte, vérifiable, à courte durée de vie, et
obtenue sans aucun secret à distribuer.
Le problème : authentifier des agents avec des secrets
Listons franchement ce qui cloche avec une clé d’API partagée entre un agent et le service qu’il appelle :
- Distribution — il faut amener le secret jusqu’au workload (image, volume, variable d’env). Chaque copie est une fuite potentielle.
- Rotation — changer une clé partagée par dix services est un chantier ; en pratique, on ne le fait jamais.
- Révocation — un agent compromis ? La clé est valable jusqu’à ce que quelqu’un, quelque part, pense à la changer partout.
- Granularité — une clé prouve « je connais le secret », pas « je suis l’agent chercheur ». Impossible d’écrire une politique fine sur l’identité réelle.
- Falsifiabilité — un secret qui traîne dans un prompt, un log ou une variable d’env est à un jailbreak ou une injection indirecte près d’être exfiltré.
Ce qu’on veut, c’est une identité qui ne se distribue pas (le workload
l’obtient lui-même, prouvée par ce qu’il est), qui expire vite (donc qu’on
n’a pas besoin de révoquer manuellement), et sur laquelle on peut écrire une
politique (« seul …/agent/researcher peut appeler …/tool/search »).
SPIFFE : un standard d’identité de workload
SPIFFE (Secure Production Identity Framework For Everyone) est une spécification. Elle ne s’installe pas — elle définit trois objets.
Le SPIFFE ID — le nom
Un identifiant universel, sous forme d’URI :
spiffe://agentique.world/agent/researcher
└──── trust domain ────┘└── chemin ──┘
Le trust domain (agentique.world) est la racine de confiance : un périmètre
qui partage la même autorité d’émission. Le chemin nomme le workload. Rien
n’est secret là-dedans — un SPIFFE ID est un nom, public, comme une plaque
d’immatriculation.
Le SVID — la preuve
Le SVID (SPIFFE Verifiable Identity Document) est le document qui prouve qu’un workload détient légitimement un SPIFFE ID. Deux formats :
- X.509-SVID — un certificat X.509 dont le SPIFFE ID est inscrit dans le champ
URI SAN. Idéal pour le mTLS : la connexion elle-même porte l’identité. - JWT-SVID — un jeton JWT signé (
sub= SPIFFE ID,aud= destinataire visé). Idéal quand on ne maîtrise pas la couche transport : appel HTTP via une passerelle, en-têteAuthorization, etc.
Dans les deux cas, le SVID est signé par l’autorité du trust domain et a une durée de vie courte (typiquement une heure, souvent moins). On ne révoque pas un SVID : on le laisse expirer.
Le trust bundle — de quoi vérifier
Pour valider le SVID d’un pair, il faut les clés publiques (CA X.509 ou JWKS) du trust domain : c’est le trust bundle. Vérifier un pair, c’est vérifier que son SVID est signé par un bundle de confiance — et lire le SPIFFE ID qu’il contient.
La Workload API — l’identité sans secret
C’est l’idée la plus subtile, et la plus importante pour des agents. Comment un workload obtient-il son tout premier SVID sans secret de bootstrap ? Le problème de l’œuf et de la poule de toute l’authentification.
SPIFFE le résout avec la Workload API : une API locale (sur un socket Unix) que le workload appelle sans présenter aucun identifiant. Ce n’est pas le workload qui prouve son identité par un secret — c’est l’infrastructure qui l’atteste en l’observant (sous quel utilisateur tourne-t-il ? quel binaire ? quel pod Kubernetes ?). En retour, le workload reçoit son SVID et le trust bundle, et les voit se renouveler tout seuls.
SPIRE : l’implémentation
SPIFFE est la norme ; SPIRE (SPIFFE Runtime Environment) en est l’implémentation de référence. Deux composants.
- SPIRE Server — l’autorité du trust domain. Il détient la CA, signe les SVID, et tient le registre : la liste de qui a le droit de recevoir quelle identité.
- SPIRE Agent — un démon sur chaque nœud. Il est lui-même attesté auprès du serveur, puis expose la Workload API localement et atteste les workloads qui s’y connectent.
Tout repose sur une double attestation — le mot-clé de SPIRE.
- Attestation de nœud — l’agent prouve l’identité de sa machine au serveur (jeton d’amorçage, identité d’instance AWS, Projected Service Account Token Kubernetes…). Il reçoit alors son propre SVID d’agent.
- Attestation de workload — quand un process appelle la Workload API, l’agent inspecte ce process (son uid, le chemin de son binaire, son pod) pour en déduire des sélecteurs, les confronte au registre, et ne délivre que le SVID correspondant.
Le lien entre une identité et les propriétés qui y donnent droit est une
registration entry : un SPIFFE ID, un parent (l’agent/nœud qui peut l’émettre),
et un jeu de sélecteurs (unix:uid:1001, k8s:pod-label:app:researcher…).
C’est là qu’on déclare qui peut être qui.
Hands-on : un plan de contrôle SPIRE en local
Montons un trust domain agentique.world minimal et donnons une identité à deux
workloads : un agent chercheur et un service outil. Les binaires spire-server et
spire-agent se téléchargent depuis les releases du projet.
Configurer le serveur
server.conf — l’autorité du trust domain. SVID valables 1 heure, stockage SQLite,
amorçage par jeton (join_token) pour faire simple en local.
server {
bind_address = "127.0.0.1"
bind_port = "8081"
trust_domain = "agentique.world"
data_dir = "./data/server"
log_level = "INFO"
ca_ttl = "24h"
default_x509_svid_ttl = "1h"
}
plugins {
DataStore "sql" {
plugin_data {
database_type = "sqlite3"
connection_string = "./data/server/datastore.sqlite3"
}
}
KeyManager "memory" {
plugin_data {}
}
NodeAttestor "join_token" {
plugin_data {}
}
}Démarrer le serveur
spire-server run -config server.conf
# Vérifier qu'il répond :
spire-server healthcheckAttester le nœud par un jeton d’amorçage
On génère un jeton lié au SPIFFE ID que portera l’agent du nœud.
spire-server token generate -spiffeID spiffe://agentique.world/agent/node-1
# Token: 8f4c0e2a-... (à passer à l'agent ci-dessous)Configurer puis démarrer l’agent
agent.conf — il expose la Workload API sur un socket Unix et attestera les
process locaux via leur uid (WorkloadAttestor "unix").
agent {
data_dir = "./data/agent"
log_level = "INFO"
server_address = "127.0.0.1"
server_port = "8081"
socket_path = "/tmp/spire-agent/public/api.sock"
trust_domain = "agentique.world"
insecure_bootstrap = true # local uniquement — en prod : trust_bundle_path
}
plugins {
KeyManager "disk" {
plugin_data { directory = "./data/agent" }
}
NodeAttestor "join_token" {
plugin_data {}
}
WorkloadAttestor "unix" {
plugin_data {}
}
}spire-agent run -config agent.conf -joinToken 8f4c0e2a-...Enregistrer les identités des workloads
On déclare deux entrées. L’agent chercheur tournera sous l’uid 1001, le service
outil sous l’uid 1002 — le sélecteur unix:uid suffit à les distinguer.
# Identité de l'agent chercheur
spire-server entry create \
-parentID spiffe://agentique.world/agent/node-1 \
-spiffeID spiffe://agentique.world/agent/researcher \
-selector unix:uid:1001
# Identité du service outil
spire-server entry create \
-parentID spiffe://agentique.world/agent/node-1 \
-spiffeID spiffe://agentique.world/tool/search \
-selector unix:uid:1002 Donner une identité à un agent Python
Côté workload, la bibliothèque py-spiffe parle à la Workload API. On installe
pip install spiffe, et on récupère un X509Source : il va chercher le SVID via
le socket, et le fait tourner en tâche de fond — le code ne gère jamais de
secret ni d’expiration.
# identite.py — lancé sous l'uid 1001 → reçoit l'identité du chercheur
from spiffe import X509Source
# Le socket de la Workload API (cf. socket_path de l'agent).
SOCKET = "unix:///tmp/spire-agent/public/api.sock"
# X509Source s'abonne à la Workload API : récupération + rotation automatiques.
with X509Source(spiffe_socket_path=SOCKET) as source:
svid = source.svid # X509Svid courant
print("Mon identité :", svid.spiffe_id) # spiffe://agentique.world/agent/researcher
print("Expire dans :", svid.leaf.not_valid_after)
mTLS entre deux agents
Avec des X.509-SVID des deux côtés, on établit un mTLS : la connexion porte l’identité des deux pairs. Chacun présente son SVID et vérifie celui de l’autre contre le trust bundle — puis lit le SPIFFE ID du pair pour décider s’il l’autorise.
Le point clé n’est pas le TLS (standard), c’est l’autorisation par SPIFFE ID.
Une fois la poignée de main faite, on extrait le SPIFFE ID du certificat du pair
(son URI SAN) et on le confronte à une liste blanche :
# autorisation.py — qui se cache derrière cette connexion ?
from cryptography import x509
from cryptography.x509.oid import ExtensionOID
# Politique : seul l'agent chercheur peut appeler ce service outil.
APPELANTS_AUTORISES = {"spiffe://agentique.world/agent/researcher"}
def spiffe_id_du_pair(cert_der: bytes) -> str:
"""Lit le SPIFFE ID dans l'URI SAN du certificat du pair."""
cert = x509.load_der_x509_certificate(cert_der)
san = cert.extensions.get_extension_for_oid(ExtensionOID.SUBJECT_ALTERNATIVE_NAME)
uris = san.value.get_values_for_type(x509.UniformResourceIdentifier)
spiffe_ids = [u for u in uris if u.startswith("spiffe://")]
if not spiffe_ids:
raise PermissionError("Pair sans SPIFFE ID : connexion refusée.")
return spiffe_ids[0]
def autoriser(cert_der: bytes) -> str:
sid = spiffe_id_du_pair(cert_der)
if sid not in APPELANTS_AUTORISES:
raise PermissionError(f"Identité non autorisée : {sid}")
return sid # ok — c'est bien un appelant légitime
Sur le fond du transport, deux modes coexistent en production :
- En sidecar — un assistant (
spiffe-helper) ou un proxy (Envoy) consomme la Workload API, écrit les SVID renouvelés et fait le mTLS pour l’application. Le code applicatif n’a même pas besoin de connaître SPIFFE. C’est le mode le plus courant pour des services existants. - En in-process — l’application embarque py-spiffe, gère son
X509Sourceet câble elle-même son contexte TLS. Plus de contrôle, idéal pour un agent qu’on écrit de zéro.
Quand mTLS ne suffit pas : le JWT-SVID
Le mTLS suppose qu’on maîtrise la connexion de bout en bout. Mais un agent appelle souvent un outil via HTTP, à travers une passerelle ou un load-balancer qui termine le TLS. L’identité doit alors voyager dans la requête, pas dans le transport : c’est le rôle du JWT-SVID.
L’agent appelant demande un JWT-SVID destiné au service visé (audience), et
l’attache à sa requête :
# côté agent appelant
from spiffe import JwtSource
import httpx
with JwtSource(spiffe_socket_path=SOCKET) as source:
# Un JWT lié à CE destinataire — inutilisable ailleurs.
jwt_svid = source.fetch_svid(audiences={"spiffe://agentique.world/tool/search"})
httpx.post(
"https://outil.interne/recherche",
headers={"Authorization": f"Bearer {jwt_svid.token}"},
json={"q": "architectures agentiques"},
)
Côté service outil, on valide le jeton via la Workload API — elle vérifie la
signature contre le bundle, l’audience, et renvoie le SPIFFE ID de l’appelant :
# côté service outil
from spiffe import WorkloadApiClient
MON_AUDIENCE = "spiffe://agentique.world/tool/search"
APPELANTS_AUTORISES = {"spiffe://agentique.world/agent/researcher"}
def verifier(token: str) -> str:
with WorkloadApiClient(spiffe_socket_path=SOCKET) as client:
# Échoue si signature, expiration ou audience invalides.
svid = client.validate_jwt_svid(token=token, audience=MON_AUDIENCE)
if str(svid.spiffe_id) not in APPELANTS_AUTORISES:
raise PermissionError(f"Identité non autorisée : {svid.spiffe_id}")
return str(svid.spiffe_id)
Authentification n’est pas autorisation
SPIFFE répond à « qui es-tu ? » de façon irréfutable. Il ne répond pas à « as-tu le droit de faire ça ? », ni même à « pour le compte de qui agis-tu ? ». Le SVID donne une identité fiable ; la politique, elle, reste à votre charge — et c’est exactement ce qu’on veut.
C’est le prolongement direct du principe de moindre privilège vu dans le
deep-dive sécurité & garde-fous : l’identité du chercheur n’est plus
un argument falsifiable que le modèle pourrait inventer, ni un secret qu’une
injection pourrait exfiltrer — c’est un fait prouvé cryptographiquement, sur lequel
le service outil applique sa propre règle (APPELANTS_AUTORISES). On passe d’un
contrôle d’accès déclaratif et crédule à un contrôle attesté.
Rotation & révocation : le vrai gain pour les agents
C’est là que tout converge pour un système agentique :
- SVID à courte durée de vie — un certificat valable une heure, renouvelé seul. Un SVID fuité n’a presque aucune valeur : il expire avant d’être exploitable.
- Révocation par suppression d’entrée — un agent compromis ? On supprime sa
registration entry (
spire-server entry delete). Il ne renouvellera plus son SVID, et l’actuel expire en minutes. Pas de liste de révocation à propager, pas de clé à changer partout. - Aucun secret à gérer — donc rien à oublier de faire tourner, rien à voir fuir dans un log ou un prompt.
Pour une flotte d’agents qui apparaissent, se dupliquent et disparaissent au gré de la charge, c’est le modèle qui passe à l’échelle — là où la distribution manuelle de clés s’effondre.
Application aux architectures agentiques
Concrètement, dans un graphe multi-agents :
- Une identité par rôle d’agent —
…/agent/supervisor,…/agent/researcher,…/agent/writer. Le superviseur n’a pas le droit d’appeler les mêmes outils que le rédacteur, et c’est prouvé, pas déclaré. - Les outils internes deviennent des pairs zero-trust — le RAG, la base vectorielle, l’API métier n’acceptent que les SPIFFE ID autorisés. Plus de clé d’API partagée entre l’agent et son outil.
- Les traces d’attestation sont un signal de sécurité — comme une trace LangFuse anormale, une émission de SVID inattendue (un workload qui demande une identité qu’il ne devrait pas avoir) est un indice de compromission. À surveiller au même titre que l’observabilité applicative.
L’identité de workload n’est pas un substitut aux garde-fous applicatifs (filtrage d’entrée, human-in-the-loop, bornage) : c’est la couche en dessous, celle qui garantit que les pairs qui se parlent sont bien ceux qu’ils prétendent être.
Bonnes pratiques & pièges
- Une identité par workload/rôle d'agent : des SPIFFE ID distincts, jamais partagés.
- Des sélecteurs forts et difficiles à falsifier (chemin + hash du binaire, labels de pod attestés, identité d'instance cloud).
- SVID à courte durée de vie + rotation automatique via py-spiffe ou un sidecar : aucun secret de long terme.
- mTLS quand on maîtrise le transport ; JWT-SVID avec audience précise sinon.
- Autoriser explicitement par SPIFFE ID côté service (liste blanche) : l'authentification ne suffit pas.
- Surveiller les émissions de SVID et les attestations comme un signal de sécurité.
- Réutiliser un même SPIFFE ID pour plusieurs workloads : on perd toute granularité.
- Des sélecteurs faibles (unix:uid seul en prod) : tout process sous cet uid usurpe l'identité.
- Émettre ou accepter un JWT-SVID sans vérifier l'audience : jeton rejouable.
- insecure_bootstrap = true ailleurs qu'en local : l'agent fait confiance à n'importe quel serveur.
- Croire que SPIFFE fait l'autorisation : il prouve l'identité, la politique reste à écrire.
- Stocker un SVID sur disque sans en restreindre les accès : on recrée le problème du secret qui traîne.
Ce qu’il faut retenir
- Entre agents et services, remplacez les clés d’API partagées par une identité de workload forte : nom public (SPIFFE ID), preuve signée et courte (SVID), racine de confiance (trust domain + bundle).
- La Workload API donne cette identité sans secret de bootstrap : le workload est attesté par ce qu’il est, pas par ce qu’il connaît.
- SPIRE implémente le tout via une double attestation — nœud puis workload — et un registre de qui peut être qui.
- mTLS quand on maîtrise le transport, JWT-SVID (avec
audience) sinon ; dans les deux cas, l’autorisation se fait par SPIFFE ID. - Le vrai gain pour des agents : rotation gratuite, révocation par suppression d’entrée, zéro secret à gérer — un modèle qui passe à l’échelle d’une flotte d’agents éphémères.
L’identité de workload est le socle zero-trust sous vos agents — le volet 1 de notre série sur l’identité des agents. Elle prouve qui est chaque process ; reste à faire voyager pour le compte de qui il agit dans une chaîne d’agents : c’est le volet 2 — On-behalf-of & délégation. Au-dessus se construit la défense applicative — garde-fous d’entrée/sortie, moindre privilège, human-in-the-loop, bornage — l’objet du deep-dive sécurité & garde-fous.
Pour aller plus loin : bibliographie
Une sélection annotée sur l’identité de workload, SPIFFE/SPIRE et le zero-trust — de la spécification au retour d’expérience. (Les références propres à la délégation et au token exchange figurent dans le volet 2.)
Spécifications & standards
- SPIFFE — Spécifications (
github.com/spiffe/spiffe). La source normative : les documents SPIFFE-ID, X.509-SVID, JWT-SVID et Workload API. Courtes, précises, à lire dans cet ordre pour comprendre ce qui est garanti — et ce qui ne l’est pas. - SPIRE — Documentation (
spiffe.io/docs). L’implémentation de référence : concepts (serveur, agent, attestation), guides d’installation, plugins d’attestation de nœud et de workload, déploiement Kubernetes. - RFC 7519 — JSON Web Token (JWT), complétée par RFC 7515 (JWS) et
RFC 7517 (JWK) (
rfc-editor.org). Le format et la signature des JWT-SVID, et la publication des clés de vérification (JWKS). - RFC 5280 — Internet X.509 PKI Certificate (
rfc-editor.org). Le socle des X.509-SVID : structure du certificat, extensions, et le champURI SANqui porte le SPIFFE ID. - NIST SP 800-207 — Zero Trust Architecture (
csrc.nist.gov). Le cadre de référence du zero-trust : principes, composants (PEP/PDP), modèles de déploiement. Le « pourquoi » derrière l’« identité avant tout ».
Ouvrages
- Solving the Bottom Turtle — A SPIFFE Way to Establish Trust in Your Infrastructure via Universal Identity (communauté SPIFFE, livre gratuit). L’ouvrage de référence des mainteneurs : le problème de l’œuf et de la poule de l’identité (« la tortue du bas »), le modèle SPIFFE, l’attestation, des études de cas. La meilleure porte d’entrée longue.
- Zero Trust Networks: Building Secure Systems in Untrusted Networks — Evan Gilman & Doug Barth (O’Reilly). Le zero-trust côté réseau et identité, par l’un des créateurs de SPIFFE. Contexte indispensable au-delà du seul outillage.
Implémentations & outils
- SPIRE (
github.com/spiffe/spire) — le code, les releases (binairesspire-server/spire-agent), et les exemples par plateforme. - py-spiffe (
github.com/spiffe/py-spiffe) — la bibliothèque Python utilisée ici (X509Source,JwtSource,WorkloadApiClient). Voir aussigo-spiffepour l’écosystème Go, plus mature. - spiffe-helper & Envoy SDS — pour le mode sidecar : alimenter une application existante (ou un proxy) en SVID renouvelés sans toucher au code.
- Istio Security (
istio.io) — un service mesh qui s’appuie sur des identités de type SPIFFE pour le mTLS automatique entre services. Utile pour voir le modèle à l’échelle d’un cluster.
Contexte & retours d’expérience
- Google — BeyondProd (
cloud.google.com/security). Le whitepaper sur l’identité de service et le zero-trust « production » chez Google — la philosophie qui a essaimé jusqu’à SPIFFE. - CNCF — SPIFFE & SPIRE (projets gradués). Gouvernance, adoption industrielle et feuille de route : un signal de maturité pour un choix de production.
- Uber — Solving the Identity Crisis for AI Agents (
uber.com/blog). Le retour d’expérience qui relie l’identité de workload à la délégation dans une flotte d’agents : le pont direct vers le volet 2 de cette série.