Contrôle d'Accès

Contrôle d'accès de niveau entreprise pour les agents d'IA utilisant adk-auth.

Vue d'ensemble

adk-auth fournit un contrôle d'accès basé sur les rôles (RBAC) avec journalisation d'audit et prise en charge du SSO pour les agents ADK. Il permet un contrôle sécurisé et granulaire sur quels utilisateurs peuvent accéder à quels outils.

Architecture

┌─────────────────────────────────────────────────────────────────┐
│                        Agent Request                             │
└─────────────────────────────────────────────────────────────────┘
                                │
                                ▼
┌─────────────────────────────────────────────────────────────────┐
│                     SSO Token Validation                         │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────────┐  │
│  │ Google      │  │ Azure AD    │  │ OIDC Discovery          │  │
│  │ Provider    │  │ Provider    │  │ (Okta, Auth0, etc)     │  │
│  └─────────────┘  └─────────────┘  └─────────────────────────┘  │
│                          │                                       │
│                   ┌──────┴──────┐                                │
│                   │ JWKS Cache  │  ← Auto-refresh keys          │
│                   └─────────────┘                                │
└─────────────────────────────────────────────────────────────────┘
                                │
                                ▼ TokenClaims
┌─────────────────────────────────────────────────────────────────┐
│                       Claims Mapper                              │
│                                                                  │
│    IdP Groups          →        adk-auth Roles                  │
│    ─────────────────────────────────────────                    │
│    "AdminGroup"        →        "admin"                         │
│    "DataAnalysts"      →        "analyst"                       │
│    (default)           →        "viewer"                         │
└─────────────────────────────────────────────────────────────────┘
                                │
                                ▼ Roles
┌─────────────────────────────────────────────────────────────────┐
│                      Access Control                              │
│                                                                  │
│    Role: admin                                                   │
│    ├── allow: AllTools                                          │
│    └── allow: AllAgents                                         │
│                                                                  │
│    Role: analyst                                                 │
│    ├── allow: Tool("search")                                    │
│    ├── allow: Tool("summarize")                                 │
│    └── deny:  Tool("code_exec")  ← Deny takes precedence        │
└─────────────────────────────────────────────────────────────────┘
                                │
                                ▼ Check Result
┌─────────────────────────────────────────────────────────────────┐
│                      Audit Logging                               │
│                                                                  │
│    {"user":"alice","resource":"search","outcome":"allowed"}     │
│    {"user":"bob","resource":"exec","outcome":"denied"}          │
└─────────────────────────────────────────────────────────────────┘
                                │
                                ▼
┌─────────────────────────────────────────────────────────────────┐
│                     Tool Execution                               │
│               (only if access granted)                          │
└─────────────────────────────────────────────────────────────────┘

Principes de conception

1. Prépondérance du refus

Lorsqu'un Role a à la fois des règles d'autorisation (allow) et de refus (deny), le refus l'emporte toujours :

let role = Role::new("limited")
    .allow(Permission::AllTools)      // Allow everything...
    .deny(Permission::Tool("admin")); // ...except admin

// Résultat : Peut accéder à n'importe quel Tool SAUF "admin"

2. Union de plusieurs Roles

Les utilisateurs avec plusieurs Role obtiennent l'union des Permission, mais les règles de refus (deny) de n'importe quel Role s'appliquent toujours :

let ac = AccessControl::builder()
    .role(reader)    // allow: search
    .role(writer)    // allow: write
    .assign("alice", "reader")
    .assign("alice", "writer")
    .build()?;

// Alice peut accéder à la fois à "search" ET à "write"

3. Explicite plutôt qu'Implicite

Les Permission sont explicites - aucun accès n'est accordé par défaut :

let role = Role::new("empty");
// Ce Role n'accorde AUCUNE permission

ac.check("user", &Permission::Tool("anything")); // → Refusé

4. Séparation de l'authentification et de l'autorisation

  • Authentification (SSO) : Vérifie QUI est l'utilisateur (via JWT)
  • Autorisation (RBAC) : Détermine À QUOI ils peuvent accéder
// Authentification : valider le JWT, extraire les claims
let claims = provider.validate(token).await?;

// Autorisation : vérifier une permission spécifique
ac.check(&claims.sub, &Permission::Tool("search"))?;

// Combiné : SsoAccessControl fait les deux
sso.check_token(token, &permission).await?;

Installation

[dependencies]
adk-auth = "0.2.0"

# Pour le support SSO/OAuth
adk-auth = { version = "0.2.0", features = ["sso"] }

Composants principaux

Permission

pub enum Permission {
    Tool(String),     // Tool spécifique par nom
    AllTools,         // Joker : tous les Tool
    Agent(String),    // Agent spécifique par nom  
    AllAgents,        // Joker : tous les Agent
}

Role

let analyst = Role::new("analyst")
    .allow(Permission::Tool("search".into()))
    .allow(Permission::Tool("summarize".into()))
    .deny(Permission::Tool("code_exec".into()));

AccessControl

let ac = AccessControl::builder()
    .role(admin)
    .role(analyst)
    .assign("alice@company.com", "admin")
    .assign("bob@company.com", "analyst")
    .build()?;

// Vérifier la permission
ac.check("bob@company.com", &Permission::Tool("search".into()))?;

ProtectedTool

Enveloppe un Tool avec une vérification de Permission automatique :

use adk_auth::ToolExt;

let protected = my_tool.with_access_control(Arc::new(ac));

// Lors de l'exécution, vérifie la permission avant de s'exécuter
protected.execute(ctx, args).await?;

AuthMiddleware

Protège en lot plusieurs Tool :

let middleware = AuthMiddleware::new(ac);
let protected_tools = middleware.protect_all(tools);

Intégration SSO

Fournisseurs pris en charge

FournisseurConstructeurÉmetteur
GoogleGoogleProvider::new(client_id)accounts.google.com
Azure ADAzureADProvider::new(tenant, client)login.microsoftonline.com
OktaOktaProvider::new(domain, client){domain}/oauth2/default
Auth0Auth0Provider::new(domain, audience){domain}/
GénériqueOidcProvider::from_discovery(issuer, client)Tout fournisseur OIDC

TokenClaims

Revendications extraites des JWT validés :

pub struct TokenClaims {
    pub sub: String,              // Sujet (ID utilisateur)
    pub email: Option<String>,    // E-mail
    pub name: Option<String>,     // Nom d'affichage
    pub groups: Vec<String>,      // Groupes IdP
    pub roles: Vec<String>,       // Rôles IdP
    pub hd: Option<String>,       // Domaine hébergé Google
    pub tid: Option<String>,      // ID de locataire Azure
    // ... plus de revendications OIDC standard
}

ClaimsMapper

Associe les groupes IdP aux rôles adk-auth :

let mapper = ClaimsMapper::builder()
    .map_group("AdminGroup", "admin")
    .map_group("Users", "viewer")
    .default_role("guest")
    .user_id_from_email()
    .build();

SsoAccessControl

Combine la validation SSO avec le RBAC en un seul appel :

let sso = SsoAccessControl::builder()
    .validator(GoogleProvider::new("client-id"))
    .mapper(mapper)
    .access_control(ac)
    .audit_sink(audit)
    .build()?;

// Valider le jeton + vérifier la permission + journal d'audit
let claims = sso.check_token(token, &Permission::Tool("search".into())).await?;

Journalisation d'Audit

FileAuditSink

let audit = FileAuditSink::new("/var/log/adk/audit.jsonl")?;
let middleware = AuthMiddleware::with_audit(ac, audit);

Format de Sortie (JSONL)

{"timestamp":"2025-01-01T10:30:00Z","user":"bob","session_id":"sess-123","event_type":"tool_access","resource":"search","outcome":"allowed"}
{"timestamp":"2025-01-01T10:30:01Z","user":"bob","session_id":"sess-123","event_type":"tool_access","resource":"code_exec","outcome":"denied"}

Récepteur d'Audit Personnalisé

use adk_auth::{AuditSink, AuditEvent, AuthError};
use async_trait::async_trait;

pub struct DatabaseAuditSink { /* ... */ }

#[async_trait]
impl AuditSink for DatabaseAuditSink {
    async fn log(&self, event: AuditEvent) -> Result<(), AuthError> {
        // Insérer dans la base de données
        sqlx::query("INSERT INTO audit_log ...")
            .bind(event.user)
            .bind(event.resource)
            .execute(&self.pool)
            .await?;
        Ok(())
    }
}

Exemples

# Core RBAC
cargo run --example auth_basic          # Role-based access control
cargo run --example auth_audit          # Audit logging

# SSO (requires --features sso)
cargo run --example auth_sso --features sso     # Complete SSO flow
cargo run --example auth_jwt --features sso     # JWT validation
cargo run --example auth_oidc --features sso    # OIDC discovery
cargo run --example auth_google --features sso  # Google Identity

Bonnes Pratiques de Sécurité

PratiqueDescription
Refuser par défautN'accorder que les permissions explicitement nécessaires
Refus explicitesAjouter des règles de refus pour les opérations dangereuses
Auditer toutActiver la journalisation pour la conformité
Valider côté serveurToujours valider les JWT sur le serveur
Utiliser HTTPSLes points d'accès JWKS nécessitent des connexions sécurisées
Faire pivoter les clésLe cache JWKS se rafraîchit automatiquement toutes les heures
Limiter la durée de vie du jetonUtiliser des jetons d'accès de courte durée

Gestion des Erreurs

use adk_auth::{AccessDenied, AuthError};
use adk_auth::sso::TokenError;

// RBAC errors
match ac.check("user", &Permission::Tool("admin".into())) {
    Ok(()) => { /* access granted */ }
    Err(AccessDenied { user, permission }) => {
        eprintln!("Denied: {} cannot access {}", user, permission);
    }
}

// SSO errors
match provider.validate(token).await {
    Ok(claims) => { /* token valid */ }
    Err(TokenError::Expired) => { /* token expired */ }
    Err(TokenError::InvalidSignature) => { /* signature invalid */ }
    Err(TokenError::InvalidIssuer { expected, actual }) => { /* wrong issuer */ }
    Err(e) => { /* other error */ }
}

Précédent: ← Évaluation | Suivant: Lignes directrices de développement →