접근 제어
adk-auth를 사용하는 AI Agents를 위한 엔터프라이즈급 접근 제어.
개요
adk-auth는 ADK Agents를 위한 감사 로깅 및 SSO 지원을 포함하는 역할 기반 접근 제어(RBAC)를 제공합니다. 이를 통해 어떤 사용자가 어떤 Tool에 접근할 수 있는지에 대한 보안이 강화된 세부적인 제어가 가능합니다.
아키텍처
┌─────────────────────────────────────────────────────────────────┐
│ 에이전트 요청 │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ SSO 토큰 유효성 검사 │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │
│ │ Google │ │ Azure AD │ │ OIDC 검색 │ │
│ │ 공급자 │ │ 공급자 │ │ (Okta, Auth0 등) │ │
│ └─────────────┘ └─────────────┘ └─────────────────────────┘ │
│ │ │
│ ┌──────┴──────┐ │
│ │ JWKS 캐시 │ ← 키 자동 새로고침 │
│ └─────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▼ TokenClaims
┌─────────────────────────────────────────────────────────────────┐
│ 클레임 매퍼 │
│ │
│ IdP 그룹 → adk-auth Roles │
│ ───────────────────────────────────────── │
│ "AdminGroup" → "admin" │
│ "DataAnalysts" → "analyst" │
│ (기본값) → "viewer" │
└─────────────────────────────────────────────────────────────────┘
│
▼ Roles
┌─────────────────────────────────────────────────────────────────┐
│ 접근 제어 │
│ │
│ Role: admin │
│ ├── 허용: AllTools │
│ └── 허용: AllAgents │
│ │
│ Role: analyst │
│ ├── 허용: Tool("search") │
│ ├── 허용: Tool("summarize") │
│ └── 거부: Tool("code_exec") ← 거부가 우선함 │
└─────────────────────────────────────────────────────────────────┘
│
▼ Check Result
┌─────────────────────────────────────────────────────────────────┐
│ 감사 로깅 │
│ │
│ {"user":"alice","resource":"search","outcome":"allowed"} │
│ {"user":"bob","resource":"exec","outcome":"denied"} │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Tool 실행 │
│ (접근이 허용된 경우에만) │
└─────────────────────────────────────────────────────────────────┘
설계 원칙
1. 거부 우선 (Deny Precedence)
역할에 허용(allow) 및 거부(deny) 규칙이 모두 있는 경우, 거부가 항상 우선합니다:
let role = Role::new("limited")
.allow(Permission::AllTools) // Allow everything...
.deny(Permission::Tool("admin")); // ...except admin
// Result: Can access any tool EXCEPT "admin"
2. 다중 역할 합집합 (Multi-Role Union)
여러 역할을 가진 사용자는 권한의 합집합을 얻지만, 어떤 역할에서든 거부 규칙이 적용됩니다:
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. 명시적 규칙 우선 (Explicit Over Implicit)
권한은 명시적입니다. 기본적으로는 어떤 접근 권한도 부여되지 않습니다:
let role = Role::new("empty");
// This role grants NO permissions
ac.check("user", &Permission::Tool("anything")); // → Denied
4. 인증(Authentication)과 인가(Authorization)의 분리
- 인증 (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 = "0.2.0"
# For SSO/OAuth support
adk-auth = { version = "0.2.0", features = ["sso"] }
핵심 구성 요소
Permission
pub enum Permission {
Tool(String), // Specific tool by name
AllTools, // Wildcard: all tools
Agent(String), // Specific agent by name
AllAgents, // Wildcard: all agents
}
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()?;
// Check permission
ac.check("bob@company.com", &Permission::Tool("search".into()))?;
ProtectedTool
자동 권한 확인으로 Tool을 감쌉니다:
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
여러 Tool을 일괄적으로 보호합니다:
let middleware = AuthMiddleware::new(ac);
let protected_tools = middleware.protect_all(tools);
SSO 통합
지원되는 공급자
| 공급자 | 생성자 | 발급자 |
|---|---|---|
GoogleProvider::new(client_id) | accounts.google.com | |
| Azure AD | AzureADProvider::new(tenant, client) | login.microsoftonline.com |
| Okta | OktaProvider::new(domain, client) | {domain}/oauth2/default |
| Auth0 | Auth0Provider::new(domain, audience) | {domain}/ |
| 일반 | OidcProvider::from_discovery(issuer, client) | 모든 OIDC 공급자 |
TokenClaims
검증된 JWT에서 추출된 클레임:
pub struct TokenClaims {
pub sub: String, // 주체 (사용자 ID)
pub email: Option<String>, // 이메일
pub name: Option<String>, // 표시 이름
pub groups: Vec<String>, // IdP 그룹
pub roles: Vec<String>, // IdP 역할
pub hd: Option<String>, // Google 호스팅 도메인
pub tid: Option<String>, // Azure 테넌트 ID
// ... 더 많은 표준 OIDC 클레임
}
ClaimsMapper
IdP 그룹을 adk-auth 역할에 매핑합니다:
let mapper = ClaimsMapper::builder()
.map_group("AdminGroup", "admin")
.map_group("Users", "viewer")
.default_role("guest")
.user_id_from_email()
.build();
SsoAccessControl
SSO 유효성 검사와 RBAC를 한 번의 호출로 결합합니다:
let sso = SsoAccessControl::builder()
.validator(GoogleProvider::new("client-id"))
.mapper(mapper)
.access_control(ac)
.audit_sink(audit)
.build()?;
// 토큰 유효성 검사 + 권한 확인 + 감사 로그
let claims = sso.check_token(token, &Permission::Tool("search".into())).await?;
감사 로깅
FileAuditSink
let audit = FileAuditSink::new("/var/log/adk/audit.jsonl")?;
let middleware = AuthMiddleware::with_audit(ac, audit);
출력 형식 (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"}
사용자 정의 감사 싱크
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> {
// 데이터베이스에 삽입
sqlx::query("INSERT INTO audit_log ...")
.bind(event.user)
.bind(event.resource)
.execute(&self.pool)
.await?;
Ok(())
}
}
예시
# 핵심 RBAC
cargo run --example auth_basic # 역할 기반 접근 제어
cargo run --example auth_audit # 감사 로깅
# SSO (--features sso 필요)
cargo run --example auth_sso --features sso # 완전한 SSO 흐름
cargo run --example auth_jwt --features sso # JWT 유효성 검사
cargo run --example auth_oidc --features sso # OIDC 디스커버리
cargo run --example auth_google --features sso # Google Identity
보안 모범 사례
| 관행 | 설명 |
|---|---|
| 기본적으로 거부 | 명시적으로 필요한 권한만 부여 |
| 명시적 거부 | 위험한 작업에 대한 거부 규칙 추가 |
| 모든 것 감사 | 규정 준수를 위해 로깅 활성화 |
| 서버 측 유효성 검사 | 항상 서버에서 JWT 유효성 검사 |
| HTTPS 사용 | JWKS 엔드포인트에는 보안 연결 필요 |
| 키 순환 | JWKS 캐시는 매시간 자동 새로 고침 |
| 토큰 수명 제한 | 단기 액세스 토큰 사용 |
오류 처리
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 */ }
}
이전: ← 평가 | 다음: 개발 가이드라인 →