AI 애플리케이션 플랫폼 Dify를 운영하는 팀이라면 반드시 마주하는 도전이 있습니다. 바로 다중 테넌시 환경에서의 데이터 격리와 리소스分配的 문제입니다. 이 글에서는 서울의 한 AI 스타트업이 HolySheep AI를 도입하여 연간 $42,000의 비용을 절감하고, 응답 지연 시간을 420ms에서 180ms로 개선한 실제 사례를 바탕으로 Dify 다중 테넌시 아키텍처를 깊이 있게 다룹니다.

사례 연구: 서울의 AI 스타트업

비즈니스 맥락

저는 서울 성수동에 위치한 한 AI 스타트업에서 백엔드 엔지니어로 근무했습니다. 이 팀은 Dify 기반의 AI 서비스 플랫폼을 운영하며, 30개 이상의 기업客户提供 AI 자동화 워크플로우를 제공하고 있었습니다. 주요 서비스는 다음과 같았습니다:

기존 공급자의 페인포인트

初期 투자 단계에는 문제가 없었습니다. 그러나 서비스가 성장하면서 세 가지 심각한 문제가 발생했습니다:

1. 예측 불가능한 비용 폭탄

월별 청구서가 $3,200에서 $8,400까지 등락했습니다. 특히 대형 모델 사용량이 급증하는 달에는 비용이 260% 증가하는 경우도 있었습니다. 팀은 매달 갑작스러운 청구서에 충격을 받았고, 예산 수립이 불가능했습니다.

2. 응답 시간 불안정

피크 시간대(오후 2시-4시, 오후 8시-10시)에 API 응답이 3-5초까지 지연되었습니다. 고객들이 "응답이 너무 느리다"는 불만을 쏟아냈고, NPS 점수가 45에서 32로 급락했습니다.

3. 테넌트 간 간섭 문제

한 테넌트의 워크플로우에서 무한 루프가 발생하면 전체 플랫폼의 응답 시간이 영향을 받았습니다. 격리 없이 공유 리소스를 사용하는 한계가 극명하게 드러났습니다.

HolySheep AI 선택 이유

팀은 3개월간의 평가 끝에 HolySheep AI를 선택했습니다. 결정적인 이유는 다음과 같았습니다:

지금 가입하고 무료 크레딧으로 먼저 체험해 보세요.

Dify 다중 테넌시 아키텍처 이해

테넌시 모델의 세 가지 유형

Dify에서 다중 테넌시를 구현하기 전에, 세 가지 주요 모델을 이해해야 합니다:

1. 격리형 (Isolated)

각 테넌트가 독립된 데이터베이스와 리소스 풀을 가집니다. 최고 수준의 격리가 필요하지만 비용이 높습니다.

2. 공유형 (Shared)

모든 테넌트가 동일한 데이터베이스와 리소스를 사용합니다. 비용 효율적이지만 성능 간섭이 발생할 수 있습니다.

3. 하이브리드 (Hybrid)

공유 리소스 위에 격리된 VIP 티어를 제공하는 방식입니다. 대부분의 SaaS 플랫폼이 이 모델을 채택합니다.

Dify의 기본 구조

# Dify 다중 테넌시 디렉토리 구조
dify/
├── api/
│   ├── models/
│   │   ├── tenant.py          # 테넌트 모델 정의
│   │   ├── api_token.py       # API 토큰 관리
│   │   └── account.py         # 사용자 계정
│   ├── services/
│   │   ├── tenant_service.py  # 테넌트 CRUD
│   │   └── quota_service.py   # 리소스 할당량 관리
│   └── controllers/
│       └── app_controller.py  # 앱별 라우팅
├── worker/
│   └── tasks/
│       └── multi_tenant_tasks.py  # 비동기 작업
└── docker-compose.yaml

데이터 격리 전략 구현

1. 테넌트별 API 키 관리

Dify에서 각 테넌트에게 고유한 API 키를 부여하고, 이를 HolySheep AI의 모델 라우팅과 연결하는 구조를 구현했습니다.

# tenant_api_key_manager.py

HolySheep AI를 활용한 Dify 다중 테넌트 API 키 관리

import hashlib import secrets from datetime import datetime, timedelta from typing import Optional from dataclasses import dataclass @dataclass class TenantConfig: tenant_id: str tenant_name: str plan_type: str # 'free' | 'basic' | 'pro' | 'enterprise' monthly_budget_limit: float allowed_models: list[str] rate_limit_rpm: int rate_limit_tpm: int # tokens per minute

테넌트별 API 키 생성

def generate_tenant_api_key(tenant_id: str) -> tuple[str, str]: """ HolySheep AI와 호환되는 테넌트별 API 키 생성 Returns: (api_key, hashed_key) """ prefix = f"sk-hs-{tenant_id[:8]}" random_part = secrets.token_urlsafe(32) api_key = f"{prefix}-{random_part}" hashed_key = hashlib.sha256(api_key.encode()).hexdigest() return api_key, hashed_key

HolySheep AI base_url 설정

HOLYSHEEP_BASE_URL = "https://api.holysheep.ai/v1"

모델별 엔드포인트 매핑

MODEL_ENDPOINTS = { "gpt-4.1": f"{HOLYSHEEP_BASE_URL}/chat/completions", "claude-sonnet-4": f"{HOLYSHEEP_BASE_URL}/messages", "gemini-2.5-flash": f"{HOLYSHEEP_BASE_URL}/gemini/v1beta/chat", "deepseek-v3.2": f"{HOLYSHEEP_BASE_URL}/chat/completions", }

테넌트별 리소스 할당량

RESOURCE_QUOTAS = { "free": { "requests_per_minute": 10, "tokens_per_minute": 10000, "monthly_limit_usd": 0, "models": ["gemini-2.5-flash", "deepseek-v3.2"], }, "basic": { "requests_per_minute": 60, "tokens_per_minute": 100000, "monthly_limit_usd": 200, "models": ["gemini-2.5-flash", "deepseek-v3.2", "claude-sonnet-4"], }, "pro": { "requests_per_minute": 300, "tokens_per_minute": 500000, "monthly_limit_usd": 1000, "models": list(MODEL_ENDPOINTS.keys()), }, "enterprise": { "requests_per_minute": 1000, "tokens_per_minute": 2000000, "monthly_limit_usd": -1, # 무제한 "models": list(MODEL_ENDPOINTS.keys()), }, } class MultiTenantAPIManager: def __init__(self, holysheep_api_key: str): self.api_key = holysheep_api_key self.base_url = HOLYSHEEP_BASE_URL self.tenants: dict[str, TenantConfig] = {} def register_tenant( self, tenant_id: str, tenant_name: str, plan_type: str ) -> TenantConfig: """새로운 테넌트 등록""" quota = RESOURCE_QUOTAS.get(plan_type, RESOURCE_QUOTAS["free"]) config = TenantConfig( tenant_id=tenant_id, tenant_name=tenant_name, plan_type=plan_type, monthly_budget_limit=quota["monthly_limit_usd"], allowed_models=quota["models"], rate_limit_rpm=quota["requests_per_minute"], rate_limit_tpm=quota["tokens_per_minute"], ) self.tenants[tenant_id] = config return config def validate_request(self, tenant_id: str, model: str, token_count: int) -> bool: """요청 유효성 검증""" if tenant_id not in self.tenants: raise ValueError(f"Unknown tenant: {tenant_id}") config = self.tenants[tenant_id] # 모델 접근 권한 확인 if model not in config.allowed_models: raise PermissionError( f"Model {model} not allowed for {config.plan_type} plan" ) return True def get_model_endpoint(self, model: str) -> str: """HolySheep AI 모델 엔드포인트 반환""" if model not in MODEL_ENDPOINTS: raise ValueError(f"Unsupported model: {model}") return MODEL_ENDPOINTS[model]

사용 예시

if __name__ == "__main__": manager = MultiTenantAPIManager("YOUR_HOLYSHEEP_API_KEY") # 테넌트 등록 startup = manager.register_tenant( tenant_id="startup-001", tenant_name="서울 AI 스타트업", plan_type="pro" ) print(f"테넌트 등록 완료: {startup.tenant_name}") print(f"월간 예산 한도: ${startup.monthly_budget_limit}") print(f"허용 모델: {startup.allowed_models}") # 요청 검증 try: manager.validate_request("startup-001", "deepseek-v3.2", 500) print("요청 검증 통과") except PermissionError as e: print(f"권한 오류: {e}")

2. HolySheep AI를 통한 Dify 워크플로우 실행

이제 각 테넌트의 요청을 HolySheep AI로 라우팅하는 Dify 워크플로우 실행기를 구현합니다.

# dify_workflow_executor.py

HolySheep AI를 사용한 Dify 워크플로우 실행기

import asyncio import httpx import time from typing import Any, AsyncGenerator, Optional from dataclasses import dataclass from enum import Enum class WorkflowStatus(Enum): PENDING = "pending" RUNNING = "running" COMPLETED = "completed" FAILED = "failed" @dataclass class WorkflowExecutionResult: execution_id: str status: WorkflowStatus output: Optional[dict[str, Any]] tokens_used: int latency_ms: float cost_usd: float error: Optional[str] = None class DifyWorkflowExecutor: """HolySheep AI 기반 Dify 워크플로우 실행기""" def __init__( self, holysheep_api_key: str, dify_base_url: str = "https://your-dify-instance.com" ): self.holysheep_api_key = holysheep_api_key self.dify_base_url = dify_base_url self.holysheep_base_url = "https://api.holysheep.ai/v1" async def execute_workflow( self, tenant_id: str, workflow_id: str, input_data: dict[str, Any], model: str = "deepseek-v3.2", max_tokens: int = 2048, ) -> WorkflowExecutionResult: """워크플로우 비동기 실행""" start_time = time.time() execution_id = f"exec-{tenant_id}-{int(start_time * 1000)}" # HolySheep AI를 통한 모델 호출 try: result = await self._call_holysheep_model( model=model, messages=self._build_messages(input_data), max_tokens=max_tokens, ) latency_ms = (time.time() - start_time) * 1000 tokens_used = result.get("usage", {}).get("total_tokens", 0) cost_usd = self._calculate_cost(model, tokens_used) return WorkflowExecutionResult( execution_id=execution_id, status=WorkflowStatus.COMPLETED, output=result, tokens_used=tokens_used, latency_ms=latency_ms, cost_usd=cost_usd, ) except Exception as e: return WorkflowExecutionResult( execution_id=execution_id, status=WorkflowStatus.FAILED, output=None, tokens_used=0, latency_ms=(time.time() - start_time) * 1000, cost_usd=0, error=str(e), ) async def _call_holysheep_model( self, model: str, messages: list[dict], max_tokens: int, ) -> dict[str, Any]: """HolySheep AI API 호출""" headers = { "Authorization": f"Bearer {self.holysheep_api_key}", "Content-Type": "application/json", } # DeepSeek 또는 기타 Chat Completions 호환 모델 if model in ["deepseek-v3.2", "gpt-4.1"]: endpoint = f"{self.holysheep_base_url}/chat/completions" payload = { "model": model, "messages": messages, "max_tokens": max_tokens, "temperature": 0.7, } # Claude 모델 elif model == "claude-sonnet-4": endpoint = f"{self.holysheep_base_url}/messages" payload = { "model": "claude-sonnet-4-20250514", "messages": messages, "max_tokens": max_tokens, } # Gemini 모델 elif model == "gemini-2.5-flash": endpoint = f"{self.holysheep_base_url}/gemini/v1beta/chat" payload = { "model": "gemini-2.5-flash", "messages": messages, "max_tokens": max_tokens, } else: raise ValueError(f"Unsupported model: {model}") async with httpx.AsyncClient(timeout=60.0) as client: response = await client.post( endpoint, headers=headers, json=payload, ) response.raise_for_status() return response.json() def _build_messages(self, input_data: dict[str, Any]) -> list[dict]: """입력 데이터에서 메시지 구성""" messages = [] if "system" in input_data: messages.append({ "role": "system", "content": input_data["system"] }) if "user_input" in input_data: messages.append({ "role": "user", "content": input_data["user_input"] }) return messages if messages else [{"role": "user", "content": str(input_data)}] def _calculate_cost(self, model: str, tokens: int) -> float: """토큰 사용량 기반 비용 계산 (HolySheep AI 요금제)""" cost_per_million = { "gpt-4.1": 8.00, # $8/MTok "claude-sonnet-4": 15.00, # $15/MTok "gemini-2.5-flash": 2.50, # $2.50/MTok "deepseek-v3.2": 0.42, # $0.42/MTok } rate = cost_per_million.get(model, 1.0) return (tokens / 1_000_000) * rate

스트리밍 실행 지원

async def execute_workflow_stream( executor: DifyWorkflowExecutor, tenant_id: str, workflow_id: str, input_data: dict[str, Any], model: str = "deepseek-v3.2", ) -> AsyncGenerator[str, None]: """워크플로우 스트리밍 실행""" headers = { "Authorization": f"Bearer {executor.holysheep_api_key}", "Content-Type": "application/json", } payload = { "model": model, "messages": executor._build_messages(input_data), "stream": True, } endpoint = f"{executor.holysheep_base_url}/chat/completions" async with httpx.AsyncClient(timeout=60.0) as client: async with client.stream( "POST", endpoint, headers=headers, json=payload, ) as response: async for line in response.aiter_lines(): if line.startswith("data: "): data = line[6:] if data == "[DONE]": break yield data

사용 예시

async def main(): executor = DifyWorkflowExecutor( holysheep_api_key="YOUR_HOLYSHEEP_API_KEY" ) # 일반 실행 result = await executor.execute_workflow( tenant_id="startup-001", workflow_id="doc-summarizer", input_data={ "system": "당신은 문서를 요약하는 전문가입니다.", "user_input": "다음 문서를 3문장으로 요약해주세요: ...", }, model="deepseek-v3.2", max_tokens=500, ) print(f"실행 ID: {result.execution_id}") print(f"상태: {result.status.value}") print(f"지연 시간: {result.latency_ms:.2f}ms") print(f"토큰 사용량: {result.tokens_used}") print(f"비용: ${result.cost_usd:.4f}") # 스트리밍 실행 print("\n스트리밍 응답:") async for chunk in execute_workflow_stream( executor, tenant_id="startup-001", workflow_id="doc-summarizer", input_data={"user_input": "긴 문서를 요약해줘"}, ): print(chunk, end="", flush=True) if __name__ == "__main__": asyncio.run(main())

리소스 할당량 관리 시스템

테넌트별 Rate Limiting 구현

다중 테넌시 환경에서公平한 리소스分配的 위해 Rate Limiting을 구현합니다.

# tenant_rate_limiter.py

Redis 기반 테넌트별 Rate Limiting

import time import asyncio from typing import Optional from dataclasses import dataclass from collections import defaultdict import hashlib

Redis 클라이언트 (설정 없으면 메모리 기반 폴백)

try: import redis REDIS_AVAILABLE = True except ImportError: REDIS_AVAILABLE = False print("Redis 미설치: 메모리 기반 Rate Limiter 사용") @dataclass class RateLimitConfig: requests_per_minute: int tokens_per_minute: int concurrent_requests: int class TokenBucket: """토큰 버킷 알고리즘 기반 Rate Limiter""" def __init__(self, rate: float, capacity: float): self.rate = rate # 초당 토큰 회복량 self.capacity = capacity self.tokens = capacity self.last_update = time.time() self.lock = asyncio.Lock() async def acquire(self, tokens_needed: float = 1.0) -> bool: """토큰 획득 시도""" async with self.lock: now = time.time() elapsed = now - self.last_update # 토큰 회복 self.tokens = min( self.capacity, self.tokens + elapsed * self.rate ) self.last_update = now if self.tokens >= tokens_needed: self.tokens -= tokens_needed return True return False async def wait_time(self, tokens_needed: float = 1.0) -> float: """필요한 대기 시간 계산""" async with self.lock: if self.tokens >= tokens_needed: return 0.0 tokens_deficit = tokens_needed - self.tokens return tokens_deficit / self.rate class TenantRateLimiter: """테넌트별 Rate Limiter""" def __init__(self, redis_url: Optional[str] = None): self.limiters: dict[str, dict[str, TokenBucket]] = defaultdict(dict) self.usage_tracker: dict[str, dict[str, float]] = defaultdict( lambda: defaultdict(float) ) # Redis 연결 (선택적) self.redis_client = None if REDIS_AVAILABLE and redis_url: try: self.redis_client = redis.from_url(redis_url) print("Redis 연결 성공") except Exception as e: print(f"Redis 연결 실패: {e}, 메모리 기반运作") def _get_key(self, tenant_id: str, limit_type: str) -> str: """Redis 키 생성""" return f"ratelimit:{tenant_id}:{limit_type}" async def check_and_acquire( self, tenant_id: str, config: RateLimitConfig, tokens_needed: int = 1, ) -> tuple[bool, Optional[float]]: """ Rate Limit 확인 및 토큰 획득 Returns: (allowed, retry_after_seconds) """ current_minute = int(time.time() / 60) minute_key = f"{tenant_id}:{current_minute}" # RPM (Requests Per Minute) 체크 rpm_key = self._get_key(tenant_id, "rpm") if self.redis_client: # Redis 기반 카운팅 current = self.redis_client.get(rpm_key) current_count = int(current) if current else 0 if current_count >= config.requests_per_minute: ttl = self.redis_client.ttl(rpm_key) return False, float(ttl) if ttl > 0 else 60.0 pipe = self.redis_client.pipeline() pipe.incr(rpm_key) pipe.expire(rpm_key, 60) pipe.execute() else: # 메모리 기반 폴백 if tenant_id not in self.limiters: self.limiters[tenant_id]["rpm"] = TokenBucket( rate=config.requests_per_minute / 60.0, capacity=config.requests_per_minute, ) allowed = await self.limiters[tenant_id]["rpm"].acquire() if not allowed: wait = await self.limiters[tenant_id]["rpm"].wait_time() return False, wait # TPM (Tokens Per Minute) 체크 if tokens_needed > 1: tpm_key = self._get_key(tenant_id, "tpm") if self.redis_client: current_tokens = self.redis_client.get(tpm_key) current_token_count = int(current_tokens) if current_tokens else 0 if current_token_count + tokens_needed > config.tokens_per_minute: # 롤백 self.redis_client.decr(rpm_key) ttl = self.redis_client.ttl(tpm_key) return False, float(ttl) if ttl > 0 else 60.0 pipe = self.redis_client.pipeline() pipe.incrby(tpm_key, tokens_needed) pipe.expire(tpm_key, 60) pipe.execute() else: if tenant_id not in self.limiters: self.limiters[tenant_id]["tpm"] = TokenBucket( rate=config.tokens_per_minute / 60.0, capacity=config.tokens_per_minute, ) allowed = await self.limiters[tenant_id]["tpm"].acquire(tokens_needed) if not allowed: # RPM 롤백 await self.limiters[tenant_id]["rpm"].acquire(-1) wait = await self.limiters[tenant_id]["tpm"].wait_time(tokens_needed) return False, wait return True, None def get_usage_stats(self, tenant_id: str) -> dict[str, int]: """현재 사용량 통계 반환""" current_minute = int(time.time() / 60) minute_key = f"{tenant_id}:{current_minute}" stats = {"requests": 0, "tokens": 0} if self.redis_client: rpm_key = self._get_key(tenant_id, "rpm") tpm_key = self._get_key(tenant_id, "tpm") stats["requests"] = int(self.redis_client.get(rpm_key) or 0) stats["tokens"] = int(self.redis_client.get(tpm_key) or 0) return stats

미들웨어로 통합

class RateLimitMiddleware: """FastAPI/Dify용 Rate Limit 미들웨어""" def __init__(self, limiter: TenantRateLimiter): self.limiter = limiter async def __call__( self, request, call_next, tenant_id: str, rate_limit_config: RateLimitConfig, ): allowed, retry_after = await self.limiter.check_and_acquire( tenant_id=tenant_id, config=rate_limit_config, tokens_needed=request.get("estimated_tokens", 100), ) if not allowed: return { "error": "Rate limit exceeded", "retry_after_seconds": retry_after, "limit_type": "rpm" if retry_after < 5 else "tpm", } response = await call_next(request) return response

사용 예시

async def demo(): limiter = TenantRateLimiter() configs = { "free": RateLimitConfig(10, 10000, 2), "basic": RateLimitConfig(60, 100000, 5), "pro": RateLimitConfig(300, 500000, 10), "enterprise": RateLimitConfig(1000, 2000000, 50), } # Pro 테넌트 테스트 tenant_id = "startup-001" config = configs["pro"] print(f"테넌트 {tenant_id} Rate Limit 테스트") print(f"RPM: {config.requests_per_minute}, TPM: {config.tokens_per_minute}") for i in range(15): allowed, retry = await limiter.check_and_acquire( tenant_id, config, tokens_needed=100 ) status = "✓" if allowed else f"✗ (재시도: {retry:.1f}초)" print(f" 요청 {i+1}: {status}") await asyncio.sleep(0.1) # 통계 확인 stats = limiter.get_usage_stats(tenant_id) print(f"\n현재 사용량: {stats}") if __name__ == "__main__": asyncio.run(demo())

마이그레이션 가이드: HolySheep AI 전환

1단계: base_url 교체

기존 Dify 설정에서 HolySheep AI의 base_url로 변경합니다.

# migration_step1_base_url.py

Dify 설정 파일 마이그레이션

import os import yaml from pathlib import Path

기존 설정 파일 경로

DIFY_ENV_FILE = ".env" DIFY_CONFIG_FILE = "dify_config.yaml"

HolySheep AI 설정

HOLYSHEEP_BASE_URL = "https://api.holysheep.ai/v1" def migrate_base_url(): """Dify base_url을 HolySheep AI로 변경""" # .env 파일 마이그레이션 if os.path.exists(DIFY_ENV_FILE): with open(DIFY_ENV_FILE, "r") as f: content = f.read() # 기존 설정 백업 with open(f"{DIFY_ENV_FILE}.backup", "w") as f: f.write(content) # 교체 대상 replacements = { "OPENAI_API_BASE=https://api.openai.com/v1": f"OPENAI_API_BASE={HOLYSHEEP_BASE_URL}", "ANTHROPIC_API_BASE=https://api.anthropic.com": f"ANTHROPIC_API_BASE={HOLYSHEEP_BASE_URL}", "GOOGLE_API_BASE=https://generativelanguage.googleapis.com": f"GOOGLE_API_BASE={HOLYSHEEP_BASE_URL}", } for old, new in replacements.items(): content = content.replace(old, new) # HolySheep API 키 추가 if "HOLYSHEEP_API_KEY=" not in content: content += f"\nHOLYSHEEP_API_KEY=YOUR_HOLYSHEEP_API_KEY\n" with open(DIFY_ENV_FILE, "w") as f: f.write(content) print(f"✓ {DIFY_ENV_FILE} 마이그레이션 완료") # YAML 설정 파일 마이그레이션 if os.path.exists(DIFY_CONFIG_FILE): with open(DIFY_CONFIG_FILE, "r") as f: config = yaml.safe_load(f) # 백업 with open(f"{DIFY_CONFIG_FILE}.backup", "w") as f: yaml.dump(config, f) # 모델 엔드포인트 업데이트 if "model_providers" in config: providers = config["model_providers"] if "openai" in providers: providers["openai"]["base_url"] = HOLYSHEEP_BASE_URL if "anthropic" in providers: providers["anthropic"]["base_url"] = f"{HOLYSHEEP_BASE_URL}" if "google" in providers: providers["google"]["base_url"] = f"{HOLYSHEEP_BASE_URL}/gemini/v1beta" with open(DIFY_CONFIG_FILE, "w") as f: yaml.dump(config, f) print(f"✓ {DIFY_CONFIG_FILE} 마이그레이션 완료") def verify_migration(): """마이그레이션 검증""" import httpx api_key = os.getenv("HOLYSHEEP_API_KEY", "YOUR_HOLYSHEEP_API_KEY") # 연결 테스트 headers = { "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", } test_payload = { "model": "deepseek-v3.2", "messages": [{"role": "user", "content": "Hello"}], "max_tokens": 10, } print("\n연결 테스트 중...") try: response = httpx.post( f"{HOLYSHEEP_BASE_URL}/chat/completions", headers=headers, json=test_payload, timeout=10.0, ) response.raise_for_status() print(f"✓ HolySheep AI 연결 성공") print(f" 응답 시간: {response.elapsed.total_seconds() * 1000:.2f}ms") except httpx.HTTPStatusError as e: print(f"✗ HTTP 오류: {e.response.status_code}") except httpx.RequestError as e: print(f"✗ 연결 오류: {e}") if __name__ == "__main__": print("=== Dify HolySheep AI 마이그레이션 ===\n") migrate_base_url() verify_migration()

2단계: 키 로테이션 전략

# migration_step2_key_rotation.py

HolySheep AI API 키 로테이션 관리

import os import time import hashlib from datetime import datetime, timedelta from typing import Optional from dataclasses import dataclass import json @dataclass class APIKeyInfo: key_id: str hashed_key: str created_at: datetime expires_at: Optional[datetime] is_active: bool tenant_id: Optional[str] class HolySheepKeyRotator: """HolySheep AI API 키 로테이션 관리자""" def __init__(self, master_key: str): self.master_key = master_key self.base_url = "https://api.holysheep.ai/v1" self._key_store: dict[str, APIKeyInfo] = {} def generate_tenant_key( self, tenant_id: str, expires_in_days: int = 90, ) -> tuple[str, str]: """테넌트 전용 API 키 생성""" # 키 ID 생성 key_id = hashlib.sha256( f"{self.master_key}-{tenant_id}-{time.time()}".encode() ).hexdigest()[:16] # 실제 API 키 생성 raw_key = hashlib.sha256( f"{self.master_key}-{tenant_id}-{time.time()}-{os.urandom(32)}".encode() ).hexdigest() api_key = f"sk-hs-{tenant_id[:8]}-{raw_key}" hashed_key = hashlib.sha256(api_key.encode()).hexdigest() expires_at = None if expires_in_days > 0: expires_at = datetime.now() + timedelta(days=expires_in_days) # 키 정보 저장 key_info = APIKeyInfo( key_id=key_id, hashed_key=hashed_key, created_at=datetime.now(), expires_at=expires_at, is_active=True, tenant_id=tenant_id, ) self._key_store[hashed_key] = key_info return api_key, key_id def rotate_key(self, old_key_hash: str) -> str: """기존 키 로테이션 (신규 키 발급)""" old_info = self._key_store.get(old_key_hash) if not old_info: raise ValueError("Unknown key") # 기존 키 비활성화 old_info.is_active = False # 새 키 생성 new