Files
telegram-bot-workers/KV_MIGRATION_GUIDE.md
kappa 4eb5bbd3d3 feat(security): API 키 보호, CORS 강화, Rate Limiting KV 전환
보안 개선:
- API 키 하드코딩 제거 (NAMECHEAP_API_KEY_INTERNAL)
- CORS 정책: * → hosting.anvil.it.com 제한
- /health 엔드포인트 DB 정보 노출 방지
- Rate Limiting 인메모리 Map → Cloudflare KV 전환
  - 분산 환경 일관성 보장
  - 재시작 후에도 유지
  - 자동 만료 (TTL)

문서:
- CLAUDE.md Security 섹션 추가
- KV Namespace 설정 가이드 추가
- 배포/마이그레이션 가이드 추가

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-19 15:20:14 +09:00

8.5 KiB

Rate Limiting KV Migration Guide

변경 사항 요약

Rate Limiting 시스템을 인메모리 Map에서 Cloudflare KV로 마이그레이션했습니다.

기존 문제점

  • Workers 인스턴스 간 공유되지 않음
  • 재시작 시 초기화됨
  • 분산 환경에서 Rate Limit 우회 가능

개선 사항

인스턴스 간 공유 (KV 기반) 재시작 후에도 유지 분산 환경에서 일관된 Rate Limiting 자동 만료 (TTL)


배포 절차

1. KV Namespace 생성

# Production용 Namespace 생성
wrangler kv:namespace create RATE_LIMIT_KV

출력 예시:

⛅️ wrangler 3.x.x
-------------------
🌀 Creating namespace with title "telegram-summary-bot-RATE_LIMIT_KV"
✨ Success!
Add the following to your configuration file in your kv_namespaces array:
{ binding = "RATE_LIMIT_KV", id = "abc123def456ghi789jkl012mno345pq" }

2. wrangler.toml 업데이트

위에서 출력된 id 값을 복사하여 wrangler.toml 파일의 20-22번째 줄을 수정합니다:

[[kv_namespaces]]
binding = "RATE_LIMIT_KV"
id = "abc123def456ghi789jkl012mno345pq"  # ← 실제 ID로 변경

3. 로컬 개발용 Namespace 생성 (선택사항)

로컬 테스트 시 별도의 KV를 사용하려면:

wrangler kv:namespace create RATE_LIMIT_KV --preview

출력된 preview_idwrangler.toml에 추가:

[[kv_namespaces]]
binding = "RATE_LIMIT_KV"
id = "abc123def456ghi789jkl012mno345pq"
preview_id = "xyz789abc123def456ghi012jkl345mno"  # 로컬 개발용

4. TypeScript 컴파일 확인

npx tsc --noEmit

에러가 없어야 합니다.

5. 로컬 테스트

npm run dev

다른 터미널에서 테스트 요청:

# 첫 번째 요청 (성공)
curl -X POST http://localhost:8787/webhook \
  -H "Content-Type: application/json" \
  -H "X-Telegram-Bot-Api-Secret-Token: ${WEBHOOK_SECRET}" \
  -d '{"update_id":1,"message":{"message_id":1,"from":{"id":123,"is_bot":false,"first_name":"Test"},"chat":{"id":123,"type":"private"},"date":1234567890,"text":"테스트"}}'

# 30번 연속 요청 후 31번째 요청 (Rate Limit)
# Rate Limit 메시지가 표시되어야 함

6. Production 배포

npm run deploy

7. 배포 확인

# 로그 스트리밍
npm run tail

# Health Check
curl https://telegram-summary-bot.kappa-d8e.workers.dev/health

변경된 파일

/Users/kaffa/telegram-bot-workers/wrangler.toml

[[d1_databases]]
binding = "DB"
database_name = "telegram-conversations"
database_id = "c285bb5b-888b-405d-b36f-475ae5aed20e"

+[[kv_namespaces]]
+binding = "RATE_LIMIT_KV"
+id = "YOUR_KV_NAMESPACE_ID"  # Run: wrangler kv:namespace create RATE_LIMIT_KV

/Users/kaffa/telegram-bot-workers/src/types.ts

export interface Env {
  DB: D1Database;
  AI: Ai;
  BOT_TOKEN: string;
  WEBHOOK_SECRET: string;
  // ... 기타 환경변수
+ RATE_LIMIT_KV: KVNamespace;
}

/Users/kaffa/telegram-bot-workers/src/security.ts

-// Rate Limiting
-const rateLimitMap = new Map<string, { count: number; resetAt: number }>();
-
-export function checkRateLimit(
-  userId: string,
-  maxRequests: number = 30,
-  windowMs: number = 60000
-): boolean {
-  // 인메모리 Map 기반 로직
-}

+// Rate Limiting (Cloudflare KV 기반)
+interface RateLimitData {
+  count: number;
+  resetAt: number;
+}
+
+export async function checkRateLimit(
+  kv: KVNamespace,
+  userId: string,
+  maxRequests: number = 30,
+  windowMs: number = 60000
+): Promise<boolean> {
+  // KV 기반 로직 (자동 TTL)
+}

/Users/kaffa/telegram-bot-workers/src/index.ts

-  if (!checkRateLimit(telegramUserId)) {
+  if (!(await checkRateLimit(env.RATE_LIMIT_KV, telegramUserId))) {
     await sendMessage(
       env.BOT_TOKEN,
       chatId,
       '⚠️ 너무 많은 요청입니다. 잠시 후 다시 시도해주세요.'
     );
     return;
   }

기술 상세

Rate Limiting 알고리즘

Key 형식: ratelimit:{userId}

데이터 구조:

{
  count: number,      // 현재 윈도우 내 요청 수
  resetAt: number     // 윈도우 만료 시각 (Unix timestamp)
}

처리 로직:

  1. KV에서 사용자별 카운터 조회
  2. 윈도우 만료 확인 (현재 시각 > resetAt)
    • 만료 시: 새 윈도우 시작 (count=1)
    • 유효 시: count 확인
  3. count >= maxRequests (기본 30) → Rate Limit 초과
  4. count < maxRequests → count 증가 후 KV 업데이트

자동 만료:

  • KV의 expirationTtl 사용 (초 단위)
  • 윈도우 종료 시 자동 삭제 (메모리 효율)

에러 처리:

  • KV 오류 시 Rate Limit 통과 (서비스 가용성 우선)
  • 로그 기록: [RateLimit] KV 오류: ...

모니터링

KV Namespace 확인

# KV Namespace 목록
wrangler kv:namespace list

# 특정 키 조회
wrangler kv:key get "ratelimit:821596605" --namespace-id=YOUR_KV_ID

# 모든 키 목록
wrangler kv:key list --namespace-id=YOUR_KV_ID

Cloudflare Dashboard

  1. https://dash.cloudflare.com → Workers & Pages
  2. KV → telegram-summary-bot-RATE_LIMIT_KV 클릭
  3. Key-Value 쌍 확인 (실시간)

로그 확인

wrangler tail

# Rate Limit 오류만 필터링
wrangler tail --format json | jq 'select(.message | contains("RateLimit"))'

트러블슈팅

KV Namespace ID를 잊었을 때

wrangler kv:namespace list

KV 데이터 초기화 (테스트용)

# 특정 사용자 Rate Limit 초기화
wrangler kv:key delete "ratelimit:821596605" --namespace-id=YOUR_KV_ID

# 전체 초기화 (주의!)
wrangler kv:key list --namespace-id=YOUR_KV_ID | jq -r '.[] | .name' | xargs -I {} wrangler kv:key delete "{}" --namespace-id=YOUR_KV_ID

Rate Limit 테스트 스크립트

#!/bin/bash
# rate-limit-test.sh
TOKEN="YOUR_WEBHOOK_SECRET"

for i in {1..35}; do
  echo "Request $i:"
  curl -s -X POST http://localhost:8787/webhook \
    -H "Content-Type: application/json" \
    -H "X-Telegram-Bot-Api-Secret-Token: $TOKEN" \
    -d '{"update_id":'$i',"message":{"message_id":'$i',"from":{"id":123,"is_bot":false,"first_name":"Test"},"chat":{"id":123,"type":"private"},"date":1234567890,"text":"테스트 '$i'"}}' \
    | jq -r '.message // "OK"'
  sleep 0.5
done

성능 영향

Before (인메모리 Map)

  • 응답 시간: ~1ms (동기)
  • 메모리: 인스턴스별 독립
  • 분산 환경: 비효율적

After (KV)

  • 응답 시간: ~20-50ms (KV read/write 포함)
  • 메모리: 0 (KV로 오프로드)
  • 분산 환경: 일관성 보장

권장 사항: KV 호출을 줄이기 위해 windowMs를 늘리지 말 것 (현재 60초 최적)


롤백 절차 (문제 발생 시)

1. 이전 버전 복구

git revert HEAD
npm run deploy

2. wrangler.toml에서 KV Namespace 제거

# 아래 3줄 주석 처리 또는 삭제
# [[kv_namespaces]]
# binding = "RATE_LIMIT_KV"
# id = "..."

3. 재배포

npm run deploy

FAQ

Q: 로컬 개발 시 KV를 사용하지 않으려면? A: --remote 플래그 사용하여 Production KV 직접 사용 (권장하지 않음)

wrangler dev --remote

Q: KV 비용은 얼마나 드나요? A: Cloudflare Workers Free Plan: 100,000 read/day, 1,000 write/day 무료

  • Rate Limiting: 1 read + 1 write per request
  • 하루 1,000명 사용자까지 무료

Q: KV가 느린 경우? A: KV는 글로벌 분산 스토리지이므로 20-50ms latency 정상

  • 대안: Durable Objects (더 빠르지만 비용 높음)

Q: Rate Limit 설정 변경하려면? A: security.ts:checkRateLimit() 파라미터 수정

// 기본값: 30 requests / 60초
export async function checkRateLimit(
  kv: KVNamespace,
  userId: string,
  maxRequests: number = 50,  // ← 변경
  windowMs: number = 120000   // ← 2분으로 변경
): Promise<boolean>

문서 업데이트

이 마이그레이션으로 인해 다음 문서도 업데이트 필요:

  • CLAUDE.md - Rate Limiting 섹션 수정
  • README.md - 설치/배포 절차 업데이트 (KV Namespace 생성 추가)
  • schema.sql - 변경 없음 (KV는 별도 저장소)

체크리스트

배포 전 확인:

  • wrangler kv:namespace create RATE_LIMIT_KV 실행 완료
  • wrangler.toml에 실제 KV Namespace ID 입력
  • npx tsc --noEmit 타입 에러 없음
  • 로컬 테스트 (npm run dev) 정상 동작
  • Rate Limit 테스트 (30회 연속 요청) 성공
  • Production 배포 (npm run deploy) 완료
  • Health Check 확인
  • 실제 Telegram 메시지 테스트 완료

배포 완료!