security2025-02-18·9 min·193/348

Vercel 보안 설정 (Firewall)

Vercel의 보안 설정과 Firewall을 구성하여 애플리케이션을 보호하는 방법을 알아봅니다.

Vercel 보안 설정 (Firewall)

Introduction

웹 애플리케이션의 보안은 매우 중요합니다. Vercel의 보안 설정과 Firewall을 구성하여 다양한 보안 위협으로부터 애플리케이션을 보호하는 방법을 알아보겠습니다.

Environment

  • Vercel 프로젝트 (Next.js 14+)
  • Vercel Firewall
  • WAF (Web Application Firewall)
  • DDoS 보호

Problem

보안 설정이 부족할 때 발생하는 문제:

// API 엔드포인트에 대한 무차별 대입 공격
// SQL 인젝션 시도
// XSS 공격
// DDoS 공격

Analysis

Vercel 보안 기능:

  1. WAF: OWASP Top 10 보호
  2. DDoS 보호: 자동 DDoS 완화
  3. Bot 보호: 봇 트래픽 차단
  4. Rate Limiting: 요청 속도 제한
  5. IP 차단: 특정 IP 차단

Solution

1. Vercel Firewall 설정

// vercel.json
{
  "firewall": {
    "enabled": true,
    "rules": [
      {
        "action": "allow",
        "patterns": [".vercel.app"],
        "description": "Vercel 프리뷰 허용"
      },
      {
        "action": "block",
        "patterns": ["*.spam.com"],
        "description": "스팸 도메인 차단"
      }
    ]
  }
}

2. WAF 규칙 설정

// vercel.json
{
  "waf": {
    "enabled": true,
    "rules": [
      {
        "name": "SQL Injection Protection",
        "action": "block",
        "pattern": "(?i)(union\\s+select|insert\\s+into|delete\\s+from|drop\\s+table)"
      },
      {
        "name": "XSS Protection",
        "action": "block",
        "pattern": "]*>|javascript:"
      }
    ]
  }
}

3. Rate Limiting 구현

// lib/rate-limiter.js
import { Redis } from '@upstash/redis';

const redis = new Redis({
  url: process.env.UPSTASH_REDIS_REST_URL,
  token: process.env.UPSTASH_REDIS_REST_TOKEN,
});

export async function checkRateLimit(identifier, limit = 100, window = 60) {
  const key = `ratelimit:${identifier}`;
  const current = await redis.incr(key);
  
  if (current === 1) {
    await redis.expire(key, window);
  }
  
  return {
    allowed: current <= limit,
    remaining: Math.max(0, limit - current),
    reset: Date.now() + (window * 1000),
  };
}

// 미들웨어에서 사용
export async function rateLimitMiddleware(request) {
  const ip = request.headers.get('x-forwarded-for') || 'unknown';
  const rateLimit = await checkRateLimit(ip, 100, 60);
  
  if (!rateLimit.allowed) {
    return new Response('Too Many Requests', {
      status: 429,
      headers: {
        'X-RateLimit-Remaining': '0',
        'X-RateLimit-Reset': rateLimit.reset.toString(),
      },
    });
  }
  
  return null;
}

4. 보안 헤더 설정

// next.config.js
module.exports = {
  headers: async () => [
    {
      source: '/:path*',
      headers: [
        { key: 'X-Frame-Options', value: 'DENY' },
        { key: 'X-Content-Type-Options', value: 'nosniff' },
        { key: 'X-XSS-Protection', value: '1; mode=block' },
        { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
        { key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
        {
          key: 'Strict-Transport-Security',
          value: 'max-age=63072000; includeSubDomains; preload',
        },
      ],
    },
  ],
};

5. 입력 검증

// lib/input-validator.js
import DOMPurify from 'isomorphic-dompurify';

export function sanitizeInput(input) {
  if (typeof input === 'string') {
    return DOMPurify.sanitize(input);
  }
  
  if (typeof input === 'object') {
    const sanitized = {};
    for (const [key, value] of Object.entries(input)) {
      sanitized[key] = sanitizeInput(value);
    }
    return sanitized;
  }
  
  return input;
}

export function validateEmail(email) {
  const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return re.test(email);
}

export function validatePassword(password) {
  // 최소 8자, 대문자, 소문자, 숫자, 특수문자 포함
  const re = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
  return re.test(password);
}

6. Bot 보호

// lib/bot-protection.js
export function detectBot(userAgent) {
  const botPatterns = [
    /bot/i,
    /crawler/i,
    /spider/i,
    /scraper/i,
    /curl/i,
    /wget/i,
  ];
  
  return botPatterns.some(pattern => pattern.test(userAgent));
}

// 미들웨어에서 사용
export function botProtectionMiddleware(request) {
  const userAgent = request.headers.get('user-agent') || '';
  
  if (detectBot(userAgent)) {
    // 봇 트래픽 제한
    return new Response('Forbidden', { status: 403 });
  }
  
  return null;
}

7. 보안 로깅

// lib/security-logger.js
import { Redis } from '@upstash/redis';

const redis = new Redis({
  url: process.env.UPSTASH_REDIS_REST_URL,
  token: process.env.UPSTASH_REDIS_REST_TOKEN,
});

export async function logSecurityEvent(event) {
  const log = {
    id: crypto.randomUUID(),
    ...event,
    timestamp: Date.now(),
  };
  
  await redis.lpush('security-logs', JSON.stringify(log));
  await redis.ltrim('security-logs', 0, 9999);
  
  return log;
}

export async function getSecurityLogs(limit = 100) {
  const logs = await redis.lrange('security-logs', 0, limit - 1);
  return logs.map(log => JSON.parse(log));
}

8. IP 차단

// lib/ip-blocklist.js
const blockedIPs = new Set([
  // 알려진 악성 IP
]);

const blockedCIDRs = [
  // 차단할 IP 대역
];

export function isIPBlocked(ip) {
  if (blockedIPs.has(ip)) {
    return true;
  }
  
  // CIDR 매칭 확인
  for (const cidr of blockedCIDRs) {
    if (matchCIDR(ip, cidr)) {
      return true;
    }
  }
  
  return false;
}

function matchCIDR(ip, cidr) {
  // CIDR 매칭 로직 구현
  return false;
}

Lessons Learned

  1. 보안 우선: 보안은 애플리케이션 개발 초기부터 고려해야 합니다.
  2. 다중 방어: 여러 보안 계층을 적용하면 더 강력한 보호가 가능합니다.
  3. 지속적 모니터링: 보안 이벤트를 지속적으로 모니터링해야 합니다.
  4. 업데이트 유지: 보안 패치와 업데이트를 지속적으로 적용해야 합니다.
  5. 교육: 개발팀의 보안 인식 교육이 필요합니다.

This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.