पहुँच नियंत्रण

adk-auth का उपयोग करके AI एजेंटों के लिए एंटरप्राइज़-ग्रेड पहुँच नियंत्रण।

अवलोकन

adk-auth भूमिका-आधारित पहुँच नियंत्रण (RBAC), स्कोप-आधारित प्राधिकरण, ऑडिट लॉगिंग, और SSO समर्थन को ADK एजेंटों के लिए प्रदान करता है। यह इस पर सुरक्षित, सूक्ष्म-स्तरीय नियंत्रण सक्षम करता है कि कौन-से उपयोगकर्ता किन टूल्स तक पहुँच सकते हैं।

आर्किटेक्चर

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

डिज़ाइन सिद्धांत

1. निषेध की प्राथमिकता

जब किसी भूमिका में अनुमति और निषेध दोनों नियम हों, तो निषेध हमेशा जीतता है:

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

// Result: Can access any tool EXCEPT "admin"

2. बहु-भूमिका संघ

कई भूमिकाओं वाले उपयोगकर्ताओं को अनुमतियों का संघ मिलता है, लेकिन किसी भी भूमिका के निषेध नियम फिर भी लागू होते हैं:

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

// Alice can access both "search" AND "write"

3. निहित के बजाय स्पष्ट

अनुमतियाँ स्पष्ट होती हैं - डिफ़ॉल्ट रूप से कोई पहुँच नहीं दी जाती:

let role = Role::new("empty");
// This role grants NO permissions

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

4. प्रमाणीकरण और प्राधिकरण का पृथक्करण

  • प्रमाणीकरण (SSO): सत्यापित करता है कि उपयोगकर्ता कौन है (JWT के माध्यम से)
  • प्राधिकरण (RBAC): निर्धारित करता है कि वे किस चीज़ तक पहुँच सकते हैं
// Authentication: validate JWT, extract claims
let claims = provider.validate(token).await?;

// Authorization: check specific permission
ac.check(&claims.sub, &Permission::Tool("search"))?;

// Combined: SsoAccessControl does both
sso.check_token(token, &permission).await?;

स्थापना

[dependencies]
adk-auth = "2.0.0"

# For SSO/OAuth support
adk-auth = { version = "2.0.0", features = ["sso"] }

मुख्य घटक

अनुमति

pub enum Permission {
    Tool(String),     // Specific tool by name
    AllTools,         // Wildcard: all tools
    Agent(String),    // Specific agent by name  
    AllAgents,        // Wildcard: all agents
}

भूमिका

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

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

ProtectedTool

स्वचालित अनुमति जाँच के साथ एक टूल को लपेटता है:

use adk_auth::ToolExt;

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

// When executed, checks permission before running
protected.execute(ctx, args).await?;

AuthMiddleware

कई टूल्स को बैच-रूप से सुरक्षित करें:

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

ScopeGuard

JWT दावों या सत्र स्थिति से आने वाले अनुरोध-स्तरीय प्राधिकरण के लिए स्कोप्स का उपयोग करें:

use adk_auth::{ContextScopeResolver, ScopeGuard};

let guard = ScopeGuard::new(ContextScopeResolver);
let protected = guard.protect(my_tool);

RBAC + स्कोप्स का संयोजन

RBAC यह उत्तर देता है: "क्या यह उपयोगकर्ता इस टूल तक बिल्कुल पहुँच सकता है?" स्कोप्स यह उत्तर देते हैं: "क्या यह विशिष्ट अनुरोध अभी अधिकृत है?"

use std::sync::Arc;
use adk_auth::{AuthMiddleware, ContextScopeResolver, ScopeGuard};

let rbac = AuthMiddleware::new(ac);
let scoped = ScopeGuard::new(ContextScopeResolver);

let protected = scoped.protect(rbac.protect(transfer_tool));

SSO एकीकरण

समर्थित प्रदाता

प्रदाताकन्स्ट्रक्टरजारीकर्ता
GoogleGoogleProvider::new(client_id)accounts.google.com
Azure ADAzureADProvider::new(tenant, client) या AzureADProvider::multi_tenant(client).with_allowed_tenants(["tenant-id"])login.microsoftonline.com
OktaOktaProvider::new(domain, client){domain}/oauth2/default
Auth0Auth0Provider::new(domain, audience){domain}/
GenericOidcProvider::from_discovery(issuer, client)कोई भी OIDC प्रदाता

AzureADProvider::multi_tenant() कॉन्फ़िगर किए गए audience के लिए किसी भी tenant को स्वीकार करता है, जब तक कि आप इसे with_allowed_tenants(...) के साथ स्पष्ट रूप से प्रतिबंधित न करें।

TokenClaims

मान्य JWTs से निकाले गए claims:

pub struct TokenClaims {
    pub sub: String,              // Subject (user ID)
    pub email: Option<String>,    // Email
    pub name: Option<String>,     // Display name
    pub groups: Vec<String>,      // IdP groups
    pub roles: Vec<String>,       // IdP roles
    pub hd: Option<String>,       // Google hosted domain
    pub tid: Option<String>,      // Azure tenant ID
    // ... more standard OIDC claims
}

ClaimsMapper

IdP groups को adk-auth roles से मैप करता है:

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

user_id_from_email() केवल email claim का उपयोग तब करता है जब email_verified == true; अन्यथा यह sub पर वापस जाता है।

SsoAccessControl

SSO validation को RBAC के साथ एक ही call में जोड़ता है:

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

// Validate token + check permission + audit log
let claims = sso.check_token(token, &Permission::Tool("search".into())).await?;

Audit Logging

FileAuditSink

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

Output Format (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> {
        // Insert into database
        sqlx::query("INSERT INTO audit_log ...")
            .bind(event.user)
            .bind(event.resource)
            .execute(&self.pool)
            .await?;
        Ok(())
    }
}

उदाहरण

cargo check -p adk-auth
cargo check -p adk-auth --features sso

सुरक्षा की सर्वोत्तम प्रथाएँ

अभ्यासविवरण
डिफ़ॉल्ट रूप से अस्वीकार करेंकेवल स्पष्ट रूप से आवश्यक अनुमतियाँ ही दें
स्पष्ट अस्वीकृतियाँखतरनाक संचालन के लिए अस्वीकार नियम जोड़ें
सब कुछ ऑडिट करेंअनुपालन के लिए लॉगिंग सक्षम करें
सर्वर-साइड पर सत्यापित करेंहमेशा सर्वर पर JWTs को सत्यापित करें
HTTPS का उपयोग करेंJWKS एंडपॉइंट्स को सुरक्षित कनेक्शनों की आवश्यकता होती है
कुंजियों को घुमाएँJWKS कैश हर घंटे स्वतः रिफ्रेश होता है
टोकन जीवनकाल सीमित करेंअल्पकालिक एक्सेस टोकन का उपयोग करें
Azure टेनेंट्स को प्रतिबंधित करेंमल्टी-टेनेंट Azure ऐप्स के लिए, with_allowed_tenants(...) कॉन्फ़िगर करें
पहचान मैपिंग से पहले ईमेल सत्यापित करेंजब ईमेल असत्यापित होता है, तो user_id_from_email() अब sub पर वापस जाता है
रद्दीकरण की योजना बनाएंटोकन रद्दीकरण अंतर्निहित नहीं है; यदि आपको तुरंत कट-ऑफ चाहिए, तो इसे एक कस्टम वैलिडेटर में लागू करें
महंगे scope lookups को कैश करेंयदि आपका ScopeResolver बाहरी सिस्टमों को कॉल करता है, तो परिणाम को प्रति request/session कैश करें

Auth Bridge

जब आप चाहते हैं कि adk-auth adk-server के लिए JWT-आधारित पुन: प्रयोज्य request extractor प्रदान करे, तब auth-bridge सक्षम करें:

use adk_auth::auth_bridge::JwtRequestContextExtractor;
use adk_auth::sso::{ClaimsMapper, GoogleProvider};

let extractor = JwtRequestContextExtractor::builder()
    .validator(GoogleProvider::new("client-id"))
    .mapper(ClaimsMapper::builder().user_id_from_email().build())
    .build()?;

यह extractor Bearer token को मान्य करता है, user_id को ClaimsMapper के साथ मैप करता है, और JWT scope / scp दावों को RequestContext.scopes में अग्रेषित करता है।

Secret Providers

Tools ToolContext::get_secret और InvocationContext::get_secret के माध्यम से runtime secrets तक पहुँचते हैं। इनके पीछे adk_auth::secrets::SecretProvider है, जिसके cloud implementations feature flags के पीछे हैं:

प्रदाताविशेषता
AWS Secrets Manageraws-secrets
Azure Key Vault (कुंजी वॉल्ट)azure-keyvault
GCP Secret Managergcp-secrets

एक को रन से जोड़ने के लिए उसे SecretService में लपेटें:

use adk_auth::secrets::{CachedSecretProvider, SecretProvider, SecretServiceAdapter};
use std::sync::Arc;
use std::time::Duration;

// Any SecretProvider — here wrapped in the cache
let cached = Arc::new(CachedSecretProvider::new(provider, Duration::from_secs(300)));
let service = Arc::new(SecretServiceAdapter::new(cached));

प्रति-टूल प्राधिकरण

डिफ़ॉल्ट रूप से, संदर्भ रखने वाला टूल किसी भी सीक्रेट का नाम ले सकता है, और प्रदाता केवल वह नाम देखता है — कोई भी चीज़ इस बात में अंतर नहीं करती कि एक मौसम टूल अपने स्वयं के API कुंजी के लिए पूछ रहा है या वही टूल किसी भुगतान क्रेडेंशियल के लिए। AuthorizingSecretService प्रदाता से परामर्श करने से पहले प्रत्येक टूल के लिए निर्णय लेता है:

use adk_auth::secrets::authorizing::{AuthorizingSecretService, SecretGrant};
use std::sync::Arc;

let service = Arc::new(
    AuthorizingSecretService::new(inner)
        .grant("weather_lookup", SecretGrant::none().name("weather-api-key"))
        .grant("charge_card", SecretGrant::none().prefix("billing/"))
        .with_audit_sink(audit_sink),
);
नियमव्यवहार
टूल के पास नाम को कवर करने वाला grant हैअनुमति है
टूल के पास ऐसा grant है जो नाम को कवर नहीं करताअस्वीकृत; provider को कभी कॉल नहीं किया जाता
टूल के पास कोई अनुदान नहीं हैअस्वीकृत
अनुरोध में कोई टूल पहचान नहीं हैअस्वीकृत, जब तक grant_untooled इसे खोल न दे

सब कुछ तब तक अस्वीकृत रहता है जब तक उसे अनुमति न मिल जाए, और अस्वीकृति एक Unauthorized त्रुटि लौटाती है। अस्वीकृत नाम की कभी खोज नहीं की जाती, इसलिए वह प्रदाता-पक्ष एक्सेस लॉग में अनुरोधित पढ़ाई के रूप में दिखाई नहीं देता।

पहचान ऐसी चीज़ नहीं है जिसे कोई tool घोषित करता है। LlmAgent प्रेषित किए गए tool के नाम को अनुरोध पर अंकित करता है, app, user, session, और invocation के साथ, इसलिए एक tool दूसरे tool की पहचान प्रस्तुत नहीं कर सकता। एक tool केवल एक purpose जोड़ सकता है:

// inside a tool
let key = ctx.get_secret_for_purpose("weather-api-key", "call the forecast endpoint").await?;

Note: एक agent जो tool के रूप में invoke किया गया है, एक ToolContext को पार करता है, जिसके पास अपनी कोई identity नहीं होती, इसलिए उस agent के अंदर किए गए accesses inner tool की बजाय outer agent की identity प्रस्तुत करते हैं। उसी अनुसार grant करें।

एक्सेस का ऑडिटिंग

SecretAuditSink निर्णय प्रति एक SecretAccessDecision प्राप्त करता है, जिसमें outcome, secret name, tool, user, invocation, और reason शामिल होते हैं — और कभी भी secret value नहीं। Allows को भी info पर और denials को warn पर log किया जाता है।

कैशिंग

CachedSecretProvider अपने TTL के लिए एक value serve करता है, फिर refetch करता है। यह bounded और revocable है:

नियंत्रणव्यवहार
with_max_entries(n)अधिकतम n नाम कैश किए जाते हैं; भर जाने पर सबसे कम हाल में उपयोग किया गया नाम हटा दिया जाता है। डिफ़ॉल्ट 128 है; 0 कैशिंग को निष्क्रिय करता है
invalidate(name)तुरंत एक secret हटा देता है — इसका उपयोग तब करें जब कोई secret rotate किया गया हो ताकि उसका पुराना मान उसके TTL के बाकी समय के लिए सर्व न किया जाए
invalidate_all()सब कुछ हटा देता है
purge_expired()समय-सीमा समाप्त प्रविष्टियों को फिर से पढ़े जाने की प्रतीक्षा किए बिना हटा देता है

इनपुट से गुप्त नाम निकाले जाने पर एक बाउंड मायने रखता है: इसके बिना, कैश प्रक्रिया के जीवनकाल तक बढ़ सकता है।

कैश क्या गारंटी देता है और क्या नहीं

एक TTL यह नियंत्रित करता है कि कैश क्या लौटाता है, न कि कोई मान प्रक्रिया की मेमोरी में कितनी देर तक रहता है। प्रविष्टियाँ उनके समाप्त होने, हटाए जाने, या अमान्य किए जाने पर शून्यीकृत कर दी जाती हैं, जिससे निवास समय लगभग TTL तक कम हो जाता है। यह कमी है, मिटाना नहीं — एक String पहले ही पुनः आवंटित हो चुका हो सकता है, allocator द्वारा कॉपी किया गया हो सकता है, disk पर swap किया गया हो सकता है, या core dump में कैप्चर किया गया हो सकता है। कैश के लिए debug output redacted किया जाता है ताकि diagnostic print किसी मान को leak न कर सके।

महत्वपूर्ण: एक साधारण SecretProvider अपनी कोई policy लागू नहीं करता — context रखने वाला कोई भी tool backing credentials द्वारा पढ़े जा सकने वाले किसी भी नाम का अनुरोध कर सकता है। इसे AuthorizingSecretService में लपेटें ताकि प्रति-tool boundary मिले, और फिर cloud credentials को भी scoped रखें: प्रत्येक deployment के लिए एक IAM identity, जिसे केवल उसी deployment को आवश्यक secrets तक पहुँच हो। महत्वपूर्ण: provider interface केवल एक secret name लेता है। ADK layer पर per-tool grant, namespace, या access audit नहीं है, इसलिए context रखने वाला कोई भी tool backing credentials द्वारा पढ़े जा सकने वाले किसी भी नाम का अनुरोध कर सकता है। cloud credentials को स्वयं scoped रखें — प्रत्येक deployment के लिए एक IAM identity, जिसे केवल उसी deployment को आवश्यक secrets तक पहुँच हो — और provider-side audit logs को access का record मानें।

extractor क्या संरक्षित करता है

एक extractor को configure करने से हर non-public route के लिए authentication चालू हो जाता है:

मार्गएक्सट्रैक्टर कॉन्फ़िगर होने पर व्यवहार
/api/sessions/*, /api/apps/*, आर्टिफैक्ट्स, डिबगमान्य टोकन के बिना 401
/api/ui/* — ब्रिज, सूचनाएँ, संसाधनमान्य टोकन के बिना 401; प्रमाणित उपयोगकर्ता अनुरोध बॉडी में नामित किसी भी उपयोगकर्ता को प्रतिस्थापित करता है
/api/run*मान्य टोकन के बिना 401; प्रमाणित उपयोगकर्ता दिए गए उपयोगकर्ता को अधिलेखित करता है
/healthसार्वजनिक

UI bridge state को अनुरोध body से लिए गए (app_name, user_id, session_id) द्वारा key किया जाता है, इसलिए authenticated user को body value के बजाय substitute किया जाता है, उस पर भरोसा नहीं किया जाता। एक registered UI resource उस user को रिकॉर्ड करता है जिसने उसे register किया; केवल वही user उसे read या replace कर सकता है, और किसी और के resource को read करने पर 404 मिलता है ताकि URI का अस्तित्व disclose न हो।

कोई extractor configured न होने पर bind करने के लिए कोई authenticated identity नहीं होती, इसलिए routes खुले रहते हैं और resources globally visible रहते हैं। Authentication opt-in है — किसी भी deployment के लिए extractor configure करें जो single trusted user नहीं है।

त्रुटि प्रबंधन

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 */ }
}

पिछला: ← मूल्यांकन | अगला: टूल प्राधिकरण →

A2A एंडपॉइंट्स

मार्गप्रमाणीकरण
GET /.well-known/agent.jsonसार्वजनिक — साथियों के पास credential होने से पहले वे card प्राप्त करते हैं
POST /a2aआवश्यक, जब एक RequestContextExtractor कॉन्फ़िगर किया गया हो
POST /a2a/streamअनिवार्य, जब एक RequestContextExtractor कॉन्फ़िगर किया गया हो

JSON-RPC रूट्स agent और tool का काम निष्पादित करते हैं, इसलिए वे session, artifact, और debug राउटर्स के समान layer लेते हैं। जब कोई extractor कॉन्फ़िगर नहीं होता, तो माँगने के लिए कोई credential नहीं होता और रूट्स खुले रहते हैं, इसलिए gate जोड़ने से मौजूदा deployment नहीं टूटता।

महत्वपूर्ण: ये रूट्स पहले router root पर, /api पर लागू layer के बाहर, merge किए जाते थे। जिस deployment ने हर दूसरे mutation surface को authenticate किया था, वह फिर भी किसी भी ऐसे client को, जो port तक पहुँच सकता था, agent चलाने और उसका cost उठाने देता था। यह create_app_with_a2a और ServerBuilder::build दोनों पर लागू था।

A2aServer::builder() डिफ़ॉल्ट रूप से 127.0.0.1:8080 से bind होता है। इसे expose करने के लिए bind_addr कॉल करें, और ऐसा करने से पहले एक extractor configure करें। generated a2a-server scaffold भी यही नियम अपनाता है और wider bind के लिए opt in करने हेतु BIND_HOST पढ़ता है।