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ête Authorization, 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.

  1. 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.
  2. 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.
Fig.01 · Chaîne d'attestation SPIRE
① node attestation Agent SVID ② workload attestation X.509-SVID SPIRE Server autorité · racine de confiance · signe les SVID SPIRE Agent expose la Workload API · socket UDS Workload (agent / service) py-spiffe · aucun secret de bootstrap
Le serveur atteste l'agent du nœud, qui atteste à son tour le workload local. À aucun moment le workload ne présente de secret : il est identifié par ce qu'il est.

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 healthcheck

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

Fig.02 · mTLS porté par les SVID
mTLS SVID + vérif. du pair SVID + vérif. du pair Agent chercheur spiffe://agentique.world /agent/researcher Service outil spiffe://agentique.world /tool/search Chacun prouve son identité par son SVID et vérifie celle du pair contre le trust bundle — aucun secret partagé.
Aucune clé d'API ne circule. Chaque partie prouve son identité par son SVID et authentifie l'autre par le sien — la connexion EST l'authentification mutuelle.

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 X509Source et 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é.

SPIFFE SVID qui es-tu
trust bundle Vérif. signature + audience
à vous Politique as-tu le droit
autorisé Action outil exécuté

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

Bonnes pratiques
  • 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é.
Pièges à éviter
  • 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 champ URI SAN qui 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 (binaires spire-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 aussi go-spiffe pour 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.
#securite#identite#spiffe#spire#mtls#zero-trust#production