Control de Acceso

Control de acceso de nivel empresarial para agentes de IA utilizando adk-auth.

Resumen

adk-auth proporciona control de acceso basado en roles (RBAC) con registro de auditoría y soporte SSO para agentes ADK. Permite un control seguro y granular sobre qué usuarios pueden acceder a qué herramientas.

Arquitectura

┌─────────────────────────────────────────────────────────────────┐
│                        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)                          │
└─────────────────────────────────────────────────────────────────┘

Principios de Diseño

1. Precedencia de Denegación

Cuando un rol tiene reglas tanto de permitir como de denegar, denegar siempre prevalece:

let role = Role::new("limited")
    .allow(Permission::AllTools)      // Permitir todo...
    .deny(Permission::Tool("admin")); // ...excepto admin

// Resultado: Puede acceder a cualquier herramienta EXCEPTO "admin"

2. Unión de Múltiples Roles

Los usuarios con múltiples roles obtienen la unión de permisos, pero las reglas de denegación de cualquier rol siguen aplicándose:

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

// Alice puede acceder tanto a "search" COMO a "write"

3. Explícito sobre Implícito

Los permisos son explícitos; no se concede acceso por defecto:

let role = Role::new("empty");
// Este rol NO concede permisos

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

4. Separación de Authentication y Authorization

  • Authentication (SSO): Verifica QUIÉN es el usuario (mediante JWT)
  • Authorization (RBAC): Determina A QUÉ pueden acceder
// Authentication: validar JWT, extraer claims
let claims = provider.validate(token).await?;

// Authorization: comprobar permiso específico
ac.check(&claims.sub, &Permission::Tool("search"))?;

// Combinado: SsoAccessControl hace ambas cosas
sso.check_token(token, &permission).await?;

Instalación

[dependencies]
adk-auth = "0.2.0"

# Para soporte de SSO/OAuth
adk-auth = { version = "0.2.0", features = ["sso"] }

Componentes Principales

Permission

pub enum Permission {
    Tool(String),     // Herramienta específica por nombre
    AllTools,         // Comodín: todas las herramientas
    Agent(String),    // Agente específico por nombre
    AllAgents,        // Comodín: todos los agentes
}

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()?;

// Comprobar permiso
ac.check("bob@company.com", &Permission::Tool("search".into()))?;

ProtectedTool

Envuelve una Tool con verificación automática de permisos:

use adk_auth::ToolExt;

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

// Cuando se ejecuta, comprueba el permiso antes de funcionar
protected.execute(ctx, args).await?;

AuthMiddleware

Protege por lotes múltiples Tools:

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

Integración SSO

Proveedores compatibles

ProveedorConstructorEmisor
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}/
GenéricoOidcProvider::from_discovery(issuer, client)Cualquier proveedor OIDC

TokenClaims

Claims extraídos de JWTs validados:

pub struct TokenClaims {
    pub sub: String,              // Sujeto (ID de usuario)
    pub email: Option<String>,    // Correo electrónico
    pub name: Option<String>,     // Nombre visible
    pub groups: Vec<String>,      // Grupos IdP
    pub roles: Vec<String>,       // Roles IdP
    pub hd: Option<String>,       // Dominio alojado de Google
    pub tid: Option<String>,      // ID de inquilino de Azure
    // ... más claims OIDC estándar
}

ClaimsMapper

Mapea grupos IdP a roles adk-auth:

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

SsoAccessControl

Combina la validación SSO con RBAC en una sola llamada:

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

// Validar token + verificar permiso + registro de auditoría
let claims = sso.check_token(token, &Permission::Tool("search".into())).await?;

Registro de Auditoría

FileAuditSink

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

Formato de Salida (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"}

Custom Audit Sink

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> {
        // Insertar en la base de datos
        sqlx::query("INSERT INTO audit_log ...")
            .bind(event.user)
            .bind(event.resource)
            .execute(&self.pool)
            .await?;
        Ok(())
    }
}

Ejemplos

# RBAC Básico
cargo run --example auth_basic          # Control de acceso basado en roles
cargo run --example auth_audit          # Registro de auditoría

# SSO (requiere --features sso)
cargo run --example auth_sso --features sso     # Flujo SSO completo
cargo run --example auth_jwt --features sso     # Validación JWT
cargo run --example auth_oidc --features sso    # Descubrimiento OIDC
cargo run --example auth_google --features sso  # Identidad de Google

Mejores Prácticas de Seguridad

PrácticaDescripción
Denegar por defectoSolo conceder los permisos explícitamente necesarios
Denegaciones explícitasAñadir reglas de denegación para operaciones peligrosas
Auditar todoHabilitar el registro para cumplimiento
Validar en el servidorSiempre validar los JWTs en el servidor
Usar HTTPSLos endpoints JWKS requieren conexiones seguras
Rotar clavesLa caché de JWKS se auto-refresca cada hora
Limitar la vida útil del tokenUsar tokens de acceso de corta duración

Manejo de Errores

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

// Errores de RBAC
match ac.check("user", &Permission::Tool("admin".into())) {
    Ok(()) => { /* acceso concedido */ }
    Err(AccessDenied { user, permission }) => {
        eprintln!("Denegado: {} no puede acceder a {}", user, permission);
    }
}

// Errores de SSO
match provider.validate(token).await {
    Ok(claims) => { /* token válido */ }
    Err(TokenError::Expired) => { /* token expirado */ }
    Err(TokenError::InvalidSignature) => { /* firma inválida */ }
    Err(TokenError::InvalidIssuer { expected, actual }) => { /* emisor incorrecto */ }
    Err(e) => { /* otro error */ }
}

Anterior: ← Evaluación | Siguiente: Directrices de Desarrollo →