पहुँच नियंत्रण
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 एकीकरण
समर्थित प्रदाता
| प्रदाता | कन्स्ट्रक्टर | जारीकर्ता |
|---|---|---|
GoogleProvider::new(client_id) | accounts.google.com | |
| Azure AD | AzureADProvider::new(tenant, client) या AzureADProvider::multi_tenant(client).with_allowed_tenants(["tenant-id"]) | login.microsoftonline.com |
| Okta | OktaProvider::new(domain, client) | {domain}/oauth2/default |
| Auth0 | Auth0Provider::new(domain, audience) | {domain}/ |
| Generic | OidcProvider::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 Manager | aws-secrets |
| Azure Key Vault (कुंजी वॉल्ट) | azure-keyvault |
| GCP Secret Manager | gcp-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 पढ़ता है।