Aller au contenu

WIF

Informations d'identification éphémères pour Claude Code avec fédération d'identité de charge de travail

Par Olivares AI 10 min de lecture

Vous déployez Claude Code pour une équipe de plateforme. Vous configurez la fédération d’identité de charge de travail de Anthropic afin que chaque session échange une assertion OIDC attestée contre un jeton de courte durée. L’examen de sécurité est propre : pas de clés statiques, d’identité par session, de jetons qui expirent. Le lendemain matin, un ingénieur ajoute export ANTHROPIC_API_KEY=sk-ant-... à son profil shell car un script en a besoin. La fédération est désormais ignorée silencieusement pour chaque session exécutée par l’ingénieur. Aucune erreur, aucun avertissement, aucune entrée de journal. La clé statique est prioritaire et le chemin attesté n’est jamais invoqué.

Il ne s’agit pas d’une condition de concurrence théorique. Il s’agit de l’ordre documenté de résolution des informations d’identification de Anthropic, et c’est la manière la plus courante par laquelle les déploiements de fédération échouent silencieusement.

Le problème avec les clés statiques

La méthode par défaut pour authentifier Claude Code est une clé API statique : une chaîne sk-ant- définie comme ANTHROPIC_API_KEY. Cela fonctionne et possède trois propriétés qui entrent en conflit avec la gestion des identités d’entreprise.

Pas d’expiration, pas de signal de rotation. Une clé statique est valide jusqu’à ce que quelqu’un la révoque. Il n’y a pas de durée de vie intégrée, pas de rappel de rotation, pas de mécanisme forçant la réauthentification. Une clé émise lors d’une preuve de concept peut toujours authentifier les charges de travail de production des mois plus tard.

Identité partagée. Chaque session utilisant la même clé s’authentifie en tant que même principal. La piste d’audit indique quel espace de travail a été utilisé, mais elle ne peut pas distinguer quel ingénieur, quelle machine ou quel automatisme a exécuté une requête donnée. L’attribution par session est structurellement impossible.

Précédence silencieuse sur la fédération. C’est le pistolet à pied. La résolution des informations d’identification de Anthropic place la clé statique (ANTHROPIC_API_KEY, niveau 2) au-dessus du chemin de fédération (niveau 4). Lorsque les deux existent dans le même environnement, la clé statique l’emporte. L’échange de fédération n’est jamais tenté. Aucune erreur n’est générée. La charge de travail s’exécute correctement sous l’identité de la clé statique, et chaque hypothèse de gouvernance fondée sur la fédération (identité à l’échelle de la session, assertions attestées, expiration du jeton) est invalidée en silence.

Même une variable vide (ANTHROPIC_API_KEY="") occupe son emplacement de priorité. L’authentification échouera, mais cela empêchera toujours le runtime d’atteindre le chemin de la fédération. Le mode d’échec est une “erreur d’authentification”, et non un “passage à la fédération”.

Fonctionnement de la fédération d’identité de charge de travail

WIF remplace la clé statique par un échange : une assertion vérifiée entre, un jeton éphémère en sort. L’assertion est un JWT : soit un JWT-SVID provenant d’un fournisseur d’identité SPIFFE, soit un jeton OIDC standard provenant de n’importe quel émetteur auquel l’organisation Anthropic fait confiance.

L’échange fait suite à RFC 7523 (subvention au porteur JWT). La charge de travail présente son assertion au point de terminaison du jeton de Anthropic avec trois identifiants : à quelle règle de fédération correspondre (fdrl_), quel compte de service agir (svac_) et à quelle organisation appartient l’échange. Dans le code, le cœur de l’échange est un POST avec un corps JSON :

type exchangeRequest struct {
    GrantType        string `json:"grant_type"`         // "urn:ietf:params:oauth:grant-type:jwt-bearer"
    Assertion        string `json:"assertion"`          // le JWT-SVID ou le jeton OIDC vérifié
    FederationRuleID string `json:"federation_rule_id"` // fdrl_...
    OrganizationID   string `json:"organization_id"`
    ServiceAccountID string `json:"service_account_id"` // svac_...
    WorkspaceID      string `json:"workspace_id,omitempty"`
}

La réponse est une réponse de jeton RFC 6749. Le jeton émis porte le préfixe sk-ant-oat (OAT = OAuth Access Token), une portée déclarée (workspace:developer pour un accès API non administratif complet, ou org:manage_tunnels pour la gestion du tunnel MCP) et une durée de vie comprise entre 60 secondes et 24 heures.

Il n’y a pas de jeton d’actualisation. Lorsque le jeton émis expire, la charge de travail doit présenter à nouveau son assertion et réexécuter l’échange. C’est délibéré : l’assertion elle-même est attestée (vérifiée en amont par l’API de charge de travail SPIFFE ou le fournisseur OIDC), donc chaque rééchange est une ré-attestation. Un jeton compromis n’est utile que pour sa durée de vie restante, et il n’existe aucun chemin d’actualisation qu’un attaquant puisse exploiter pour l’étendre.

Le résultat est une identité par session. Chaque session Claude Code échange sa propre assertion, reçoit son propre jeton de courte durée et s’authentifie en tant que principal distinct. La piste d’audit enregistre quel compte de service a agi, lié à quelle règle de fédération et étendu à quel espace de travail.

Détection du pistolet à pied statique-key-shadows-federation

Déployer WIF ne suffit pas. Vous devez également détecter quand quelque chose dans l’environnement le contourne silencieusement. Le connecteur inspecte l’environnement d’exécution pour détecter la présence d’informations d’identification statiques (ANTHROPIC_API_KEY ou ANTHROPIC_AUTH_TOKEN) et vérifie si la fédération est utilisée simultanément (soit via des règles de fédération déclarées, soit via le signal ANTHROPIC_IDENTITY_TOKEN_FILE). Lorsque les deux conditions sont vraies, cela génère un résultat de gouvernance de haute gravité.

func (s *Source) detectShadowing(at time.Time) (model.FindingReport, bool) {
    _, hasKey := s.envLookup(envAPIKey)
    _, hasAuth := s.envLookup(envAuthToken)
    if !hasKey && !hasAuth {
        return model.FindingReport{}, false
    }
    _, hasTokenFile := s.envLookup(envIdentityTokenFile)
    federationInUse := len(s.federation) > 0 || hasTokenFile
    if !federationInUse {
        return model.FindingReport{}, false
    }
    // ...
    return model.FindingReport{
        Kind:     "governance",
        Severity: model.SeverityHigh,
        Title:    "Static Anthropic key shadows Workload Identity Federation",
        // DetailHash identifie la variable qui prend le pas — jamais sa valeur
    }, true
}

Le hachage détaillé de la découverte enregistre quelle variable statique est présente et quelle fédération signale qu’elle masque, sans jamais intégrer la valeur de la clé. Pas même une forme masquée. Le hachage est stable d’une exécution à l’autre, de sorte que le moteur de gouvernance le déduplique et qu’un SIEM peut l’interroger au cas par cas.

Une clé statique sans fédération utilisée n’est qu’une clé statique : le connecteur ne la signale pas. Le résultat se déclenche uniquement lorsque les deux existent, car il s’agit de la configuration spécifique dans laquelle l’opérateur pense que la fédération est active, alors qu’elle ne l’est pas.

La boucle de réconciliation : déclarée vs réelle

La détection du pistolet à clé statique ne représente que la moitié du tableau. L’autre moitié vérifie que la configuration de la fédération elle-même n’a pas dérivé. Le connecteur conserve une ligne de base déclarée (les règles de fédération que l’opérateur déclare explicitement comme régies) et la compare à l’état actif de la configuration WIF de l’organisation Anthropic.

L’état actif provient de trois points de terminaison de l’API d’administration WIF :

  • GET /v1/organizations/service_accounts : les comptes de service (svac_) ciblés par les règles de fédération.
  • GET /v1/organizations/federation_issuers : les émetteurs OIDC/SPIFFE (fdis_) auxquels l’organisation fait confiance
  • GET /v1/organizations/federation_rules : les règles (fdrl_) qui lient les émetteurs aux comptes de service

Ces points de terminaison nécessitent un jeton de porteur org:admin OAuth – un identifiant distinct de la clé API d’administration sk-ant-admin que la liste utilise. L’API d’administration WIF rejette explicitement les clés de l’API d’administration, c’est pourquoi le connecteur utilise un client authentifié distinct pour la réconciliation.

La différence entre déclaré et live produit sept catégories de résultats :

Cas de dériveCe que cela signifieGravité
undeclared_live_ruleUne règle en direct que l’opérateur n’a jamais déclarée ni régieÉlevé
declared_rule_not_liveUne règle déclarée qui n’existe plus en amontMoyen
scope_driftLa portée réelle a divergé de la portée déclaréeMoyen (Élevé si étendu à l’ensemble de l’organisation)
lifetime_driftLa durée de vie du jeton actif s’écarte de celle déclaréeMoyen (Élevé si plus long que déclaré)
over_broad_subjectUne règle dynamique sans réelle contrainte de sujetMoyen
orphan_ruleUne règle faisant référence à un émetteur ou à un compte de service manquantMoyen
orphan_issuerUn émetteur référencé par aucune règleFaible

Deux cas passent automatiquement à une gravité élevée : une portée active qui s’est étendue à une portée à l’échelle de l’organisation ou de l’administrateur que l’opérateur n’a pas déclarée, et une durée de vie de jeton actif qui est plus longue que la ligne de base gouvernée. Les deux élargissent le rayon d’explosion au-delà de ce que l’opérateur a approuvé. La normalisation de la portée (couper, dédupliquer, trier) empêche les faux positifs dus aux espaces ou aux différences d’ordre.

Lorsqu’aucun jeton org:admin n’est configuré, la passe de réconciliation ne s’exécute tout simplement pas. Le connecteur fonctionne avec la ligne de base déclarée uniquement et est honnête quant à sa couverture : il ne fabrique jamais de liste en direct. Lorsque l’API en direct n’est pas accessible (erreur réseau, expiration du jeton), elle émet un seul résultat reconciliation_unavailable et continue : les autorisations de liste et la détection des armes à pied ne doivent pas être couplées à la santé du jeton org:admin.

Lecture en premier et données minimales

Le connecteur suit un contrat strict de minimisation des données. Chaque appel API est un GET. Il ne crée, ne met à jour ou ne supprime jamais un objet Anthropic. Il contient uniquement des métadonnées d’identité : identifiants, noms, e-mails, rôles, indices clés. Jamais un secret clé. Jamais une clé privée. Jamais un jeton frappé au repos.

Le jeton créé à partir de l’échange WIF est renvoyé à l’appelant et n’est jamais enregistré, conservé ou émis par le connecteur. Le seul enregistrement qui parvient au grand livre de gouvernance est la structure ExchangeAudit - qui ne contient délibérément aucun jeton :

type ExchangeAudit struct {
    FederationRuleID string
    OrganizationID   string
    ServiceAccountID string
    WorkspaceID      string
    Scope            string
    TokenType        string
    ExpiresAt        time.Time
}

La même minimisation s’applique à la réconciliation en direct. Lorsque le connecteur lit la configuration JWKS d’un émetteur de fédération, il réduit la réponse à deux booléens : le mode de découverte (discovery, explicit_url ou inline) et si un certificat d’autorité de certification personnalisé est épinglé. Le matériel JWK en ligne (clés publiques, mais volumineux et jamais nécessaire pour les décisions de gouvernance) n’est jamais décodé dans un champ stocké ou émis. Le certificat CA PEM est décodé uniquement pour dériver un indicateur de présence et est immédiatement rejeté.

Les conditions CEL sur les règles de fédération sont utilisées pour l’analyse de posture (l’expression CEL d’une règle fait partie de sa limite de sécurité), mais elles ne sont jamais évaluées. Le connecteur n’a aucune dépendance au moteur CEL – l’évaluation est une préoccupation distincte.

Ce que cela permet

Les clés statiques confondent authentification et identité. Chaque session est le même principal, chaque jeton vit éternellement et la présence d’une clé dans un fichier dot désactive silencieusement toute fédération que vous pensiez vous protéger.

WIF sépare les deux. L’authentification s’effectue via une assertion attestée qui prouve que la charge de travail est bien celle qu’elle prétend. L’identité est limitée à la session : un jeton de courte durée, un compte de service spécifique, un espace de travail spécifique, une portée déclarée. Lorsque le jeton expire, la charge de travail atteste à nouveau. Lorsque la configuration dérive, la boucle de réconciliation fait apparaître le gap. Lorsqu’une clé statique masque l’ensemble du mécanisme, le connecteur la détecte et la signale avant qu’elle ne devienne une découverte d’incident.

Le résultat est un modèle d’identité dans lequel les informations d’identification expirent, les sessions sont attribuables et les hypothèses de gouvernance sont continuellement vérifiées par rapport à l’état réel de l’organisation, et non seulement à l’état souhaité.


Le connecteur d’identité, l’échange WIF et la détection des armes à pied font partie du module de gouvernance des identités. Pour le modèle de sécurité plus large – le grand livre, la carte d’accès, l’architecture de collecte en lecture première – voir sécurité.

Articles liés

Questions fréquentes

La clé statique doit-elle contenir une clé API valide pour la fédération fantôme ?

Non. La priorité des informations d'identification de Anthropic est basée sur la présence et non sur la validité. Un ANTHROPIC_API_KEY="" vide remporte son emplacement de priorité, tout comme le ferait une clé renseignée : il se situe au-dessus des niveaux de fédération dans l'ordre de résolution, de sorte que le moteur d'exécution ne tombe jamais sur le chemin attesté. Le connecteur détecte cela en vérifiant si la variable d'environnement est définie (os.LookupEnv), et non si sa valeur n'est pas vide. Une clé vide échouera à l’authentification, mais elle occultera la fédération tout aussi complètement qu’une clé réelle.

Que se passe-t-il si je n'ai pas de jeton org:admin OAuth pour la réconciliation en direct ?

Le connecteur fonctionne sans. Il modélise exactement ce que l'opérateur déclare dans la configuration de la fédération, émet la liste NHI gouvernée et les bords d'autorisation autorisés à partir de ces déclarations, et exécute la détection des armes à feu à clé statique. Il ignore simplement l'étape de réconciliation en direct, car les points de terminaison de l'API d'administration WIF rejettent tout autre chose qu'un jeton de support org:admin OAuth. Vous obtenez une base de référence uniquement déclarée et honnête quant à sa couverture : elle ne fabrique jamais de liste en direct.

Découvrez ce que vos agents peuvent atteindre

Olivares AI est la plateforme ouverte et auto-hébergée pour votre parc informatique d'IA. Déployez-la sur votre propre infrastructure et obtenez la cartographie des accès que réclament vos équipes de sécurité et de plateforme.