[카테고리:] 미분류

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


    주제: 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 최적화, 라우트 핸들러 운영 팁)**로 갑니다.