“빠르고 안전하게, 엣지에서 걸러라”


주제: API 게이트웨이·레이트리밋·엣지(BFF 포함)


TL;DR (체크리스트)

  • CDN/엣지(WAF) → BFF(Next.js Route Handlers) → 내부 API(FastAPI) 3단 구조
  • 레이트리밋은 엣지(거친 필터) + 게이트웨이(세밀 정책) 이중화
  • API는 GET만 캐시, ETag/Cache-Control·키 정규화, 인증 헤더는 캐시 키에서 제거
  • WAF: 메서드 화이트리스트, 바디/헤더 크기 제한, Bot/OWASP 룰, GraphQL 인트로스펙션 차단(필요 시만)
  • BFF→APImTLS 또는 서비스 토큰+HMAC, IP allowlist, traceparent 전파
  • 관측성: X-Request-ID/traceparent/리미트 헤더(RFC 9230) 표준화
  • 롤아웃: 게이트웨이에서 가중치 라우팅/카나리/헤더 기반 분기

1) 기준 아키텍처(대규모용)

[Browser]
   ↓  (TLS, HSTS)
[CDN/Edge + WAF] — Bot 방어/1차 리밋/캐시/이미지옵트
   ↓  (X-Forwarded-For, traceparent)
[Next.js BFF (Route Handlers/Edge)]  ←→  KV/Cache
   ↓  (mTLS/HMAC, service token)
[API Gateway/Ingress]  →  [FastAPI services]  →  [Redis/Postgres/Queues]
                 ↘ outlier/ejection, retries, timeouts
  • 엣지: 정적/ISR 캐시, 1차 리밋·WAF, 이미지 최적화
  • BFF: 쿠키 세션·CSRF 처리, 응답 집계/형식화, API 키 비노출
  • 게이트웨이: 라우팅·세밀한 리밋·서킷브레이커·카나리

2) 게이트웨이 선택 가이드(요약)

  • 관리형: AWS API Gateway(+CloudFront/WAF), Cloudflare API Shield
    • 장점: 운영 부하↓, 글로벌 엣지, WAF 통합 / 단점: 세부 튜닝 한계, 비용
  • 오픈소스: Envoy(강추), Kong, NGINX(+Lua/ModSecurity), Traefik
    • 장점: 미세제어·고성능 / 단점: 운영 복잡도↑

초반엔 CloudFront/WAF + Vercel(또는 Node 풀) + Envoy 조합이 밸런스 좋음.


3) 레이트리밋 설계(정책 & 구현)

정책 레이어

  • 엣지(IP 기반 거친 리밋): 예) 60 req/10s/IP (정적/공용 엔드포인트)
  • 게이트웨이(세밀): 사용자/테넌트/플랜별 token bucket or sliding window
  • 엔드포인트 가중치: 쓰기/비싼 API는 더 엄격(예: 결제, 업로드)

Redis 슬라이딩 윈도우(실전 스니펫)

# rate_limit_sw.py
import time, json, asyncio, redis.asyncio as redis
r = redis.from_url("redis://localhost:6379/0")

async def allow(key: str, limit: int, window_ms: int):
    now = int(time.time()*1000)
    async with r.pipeline(transaction=True) as p:
        p.zremrangebyscore(key, 0, now-window_ms)
        p.zadd(key, {str(now): now})
        p.zcard(key)
        p.pexpire(key, window_ms)
        _, _, count, _ = await p.execute()
    return count <= limit, limit - count

# FastAPI 사용 예
from fastapi import Request, HTTPException
async def guard_rl(request: Request, key, limit=100, window_ms=60_000):
    ok, remain = await allow(key, limit, window_ms)
    if not ok:
        raise HTTPException(429, detail="Too Many Requests")
    # RFC 9230 헤더
    request.state.rl = {"limit":limit, "remain":max(0,remain)}

응답 헤더 예:

RateLimit-Limit: 100;w=60
RateLimit-Remaining: 42
RateLimit-Reset: 23

NGINX (엣지/프록시) 간단 리밋

limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_conn_zone $binary_remote_addr zone=conn:10m;

server {
  location /api/ {
    limit_req zone=api burst=50 nodelay;
    limit_conn conn 100;
    proxy_connect_timeout 3s;
    proxy_read_timeout 15s;
    proxy_send_timeout 5s;
    proxy_next_upstream error timeout http_500 http_502 http_503 http_504;
  }
}

Envoy 로컬 리밋 + 아웃라이어 제거(핵심만)

http_filters:
- name: envoy.filters.http.local_ratelimit
  typed_config:
    "@type": type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
    stat_prefix: local_rate_limit
    token_bucket: { max_tokens: 100, tokens_per_fill: 100, fill_interval: 60s }
    filter_enabled: { default_value: { numerator: 100, denominator: HUNDRED } }
- name: envoy.filters.http.router
outlier_detection:
  consecutive_5xx: 5
  interval: 10s
  base_ejection_time: 30s
  max_ejection_percent: 50

4) 엣지 캐싱 & 키 전략

  • API 캐시 원칙: GET만, Authorization이 있으면 기본 비캐시
  • 캐시 키에서 쿼리 순서 정규화, 파라미터 화이트리스트만 포함
  • 헤더: ETag/If-None-Match, Cache-Control: public, max-age=60, stale-while-revalidate=120
  • Next.js: ISR/revalidateTag 적극 활용(콘텐츠성 페이지는 엣지로 밀기)

CloudFront(개념)

  • Behavior A: /assets/* → S3, max-age=31536000, immutable
  • Behavior B: /api/public/* → BFF, 캐시 활성(쿼리 화이트리스트)
  • Behavior C: /api/* → BFF, 캐시 비활성, WAF 엄격 룰

5) BFF ↔ API 보안(내부 호출)

선택지

  • mTLS(양단 인증서) + 사설 CA
  • 서비스 토큰(HMAC): 요청 바디 서명

HMAC 서명 예시

// BFF 서명
import crypto from 'crypto';
function sign(method:string, path:string, body:string, ts:number, secret:string){
  const msg = `${method}\n${path}\n${ts}\n${body}`;
  return crypto.createHmac('sha256', secret).update(msg).digest('hex');
}
# FastAPI 검증
from fastapi import Request, HTTPException
import hmac, hashlib, time, json
def verify(req: Request, secret: str):
    ts = int(req.headers.get("X-Timestamp","0"))
    if abs(time.time()-ts) > 300: raise HTTPException(401,"stale")
    sig = req.headers.get("X-Signature","")
    body = (await req.body()).decode() if req.method in ("POST","PUT","PATCH") else ""
    msg = f"{req.method}\n{req.url.path}\n{ts}\n{body}".encode()
    mac = hmac.new(secret.encode(), msg, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(sig, mac):
        raise HTTPException(401,"bad signature")

네트워크 레벨: API는 사설 서브넷/프라이빗 링크, BFF IP만 허용.


6) WAF 권장 룰 세트

  • 메서드 화이트리스트: GET, POST, PUT, PATCH, DELETE, OPTIONS 외 차단
  • 바디/헤더 크기 상한(예: 바디 5MB, 헤더 16KB)
  • Content-Type 화이트리스트: application/json, multipart/form-data 등만
  • OWASP Core: SQLi/XSS/RCE 시그니처
  • GraphQL: 인트로스펙션, 대형 쿼리/배치 차단(미사용 시 전면 차단)
  • Geo/IP: 관리 콘솔·어드민 IP allowlist
  • Bot: 유명 크롤러만 허용·나머지 챌린지

7) 엣지 보안 헤더(Next.js 설정 예)

// next.config.js
module.exports = {
  async headers() {
    return [
      {
        source: "/(.*)",
        headers: [
          { key: "Strict-Transport-Security", value: "max-age=31536000; includeSubDomains; preload" },
          { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
          { key: "X-Frame-Options", value: "DENY" },
          { key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=()" },
          // CSP는 서비스별로 조정 필수(예시는 최소치)
          { key: "Content-Security-Policy", value: "default-src 'self'; img-src 'self' data: https:; script-src 'self'; style-src 'self' 'unsafe-inline'" },
        ],
      },
    ];
  },
};

8) 타임아웃·리트라이·서킷브레이커(게이트웨이 표준)

  • 타임아웃: 연결 2s / 읽기 5–10s (워크로드별로)
  • 리트라이: 멱등 요청만(GET/PUT 일부), 3회 백오프
  • 서킷: 5xx·타임아웃 증가 시 일시 차단, 반개방 탐침

9) 카나리·블루/그린·A/B

  • 게이트웨이에서 가중치 라우팅(예: 90/10)
  • 헤더/쿠키 기반 트래픽 분할(X-Exp: v2)
  • BFF는 버전 태그로 응답·캐시 분리 (cacheTag: "api-v2")

10) 관측성(엣지~API 종단)

  • 모든 레이어에서 traceparent 전달, 누락 시 BFF가 생성
  • 실시간 대시보드:
    • 엣지: 캐시 히트율, 4xx/5xx, 리퀘스트당 바이트
    • 게이트웨이: 레이트리밋 히트, 아웃라이어 이젝션 수, 재시도율
    • API: p95, 에러율, 리밋 헤더, WAF 차단 건수

11) 운영 안티패턴

  • 엣지에서 모든 쿠키를 캐시 키에 포함 → 캐시 파편화
  • 인증된 GET까지 무분별 캐시 OR 전면 비캐시
  • 레이트리밋을 애플리케이션 한 군데만 적용(우회 가능)
  • BFF→API에 공유 시크릿 하드코딩·IP 오픈
  • 게이트웨이 타임아웃이 백엔드보다 길게 설정(커넥션 고갈)

12) 2주 액션 플랜(템플릿)

  1. 엣지/WAF 룰 배치: 메서드·사이즈·콘텐츠타입 화이트리스트
  2. BFF→API 보안: 서비스 토큰(HMAC) 적용 + API 원점 IP 제한
  3. 레이트리밋 이중화: 엣지(IP), 게이트웨이(사용자/테넌트)
  4. GET 캐시 표준: ETag/Cache-Control, 쿼리 화이트리스트, 키 정규화
  5. 관측성 연결: traceparent 전파·리미트 헤더·WAF 로그 수집
  6. 카나리 라우팅: 게이트웨이에 90/10 가중치 규칙 추가, 롤백 버튼 확인
  7. 보안 헤더 Next.js에 고정, HSTS 프리로드 등록 검토

13) “복붙” 모음

A. FastAPI 리밋 헤더 주입 미들웨어

@app.middleware("http")
async def add_rl_headers(request, call_next):
    response = await call_next(request)
    rl = getattr(request.state, "rl", None)
    if rl:
        response.headers["RateLimit-Limit"] = f'{rl["limit"]};w=60'
        response.headers["RateLimit-Remaining"] = str(max(0, rl["remain"]))
    return response

B. NGINX 캐시 키 정규화(쿼리 화이트리스트)

map $request_uri $cache_key {
  "~^/api/public/items\?(id|page|size|q)=.*" $request_uri;
  default "";
}
proxy_cache_key $scheme$request_method$host$cache_key;

C. Envoy 헤더 기반 카나리

route_config:
  virtual_hosts:
  - routes:
    - match: { prefix: "/api" }
      route:
        weighted_clusters:
          clusters:
          - name: api-v1
            weight: 90
          - name: api-v2
            weight: 10
        request_headers_to_add:
        - header: { key: "X-Canary", value: "v2" }
          append_action: APPEND_IF_EXISTS_OR_ADD
    - match:
        prefix: "/api"
        headers: [{ name: "X-Exp", exact_match: "v2" }]
      route: { cluster: api-v2 }

마무리

엣지에서 거칠게 걸러 트래픽을 가볍게 하고, 게이트웨이에서 정밀 제어, BFF에서 보안·세션·집계를 담당시키면 대규모 성장에서도 속도와 안전을 동시에 잡을 수 있습니다.

다음은 **7편. Next.js at Scale(SSR/ISR/RSC/이미지·CDN 최적화, 라우트 핸들러 운영 팁)**로 갑니다.

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다