무료 챕터

7. 보안과 성능 최적화

by leancle2025. 8. 9.

🔒 MCP 환경의 보안 강화

MCP는 강력하지만, 잘못 사용하면 보안 위험이 될 수 있습니다. 안전한 MCP 환경을 구축하는 방법을 알아봅니다.

🛡️ 보안 설정

읽기 전용 모드 설정

{
  "mcpServers": {
    "supabase-prod": {
      "command": "npx",
      "args": [
        "-y",
        "@supabase/mcp-server-supabase@latest",
        "--read-only",  // 중요: 프로덕션은 항상 읽기 전용
        "--project-ref=prod-project-ref"
      ],
      "env": {
        "SUPABASE_ACCESS_TOKEN": "prod-token"
      }
    }
  }
}

RLS (Row Level Security) 정책

You: "모든 테이블에 적절한 RLS 정책을 설정해줘"

Claude: 각 테이블에 보안 정책을 설정하겠습니다.

[MCP 실행 중...]

```sql
-- users 테이블 RLS
ALTER TABLE users ENABLE ROW LEVEL SECURITY;

CREATE POLICY "Users can view own profile" ON users
  FOR SELECT USING (auth.uid() = id);

CREATE POLICY "Users can update own profile" ON users
  FOR UPDATE USING (auth.uid() = id);

-- orders 테이블 RLS  
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;

CREATE POLICY "Users can view own orders" ON orders
  FOR SELECT USING (auth.uid() = user_id);

CREATE POLICY "Users can create own orders" ON orders
  FOR INSERT WITH CHECK (auth.uid() = user_id);

-- products 테이블 RLS (공개 읽기)
ALTER TABLE products ENABLE ROW LEVEL SECURITY;

CREATE POLICY "Anyone can view products" ON products
  FOR SELECT USING (true);

CREATE POLICY "Only admins can modify products" ON products
  FOR ALL USING (
    EXISTS (
      SELECT 1 FROM users
      WHERE id = auth.uid() AND role = 'admin'
    )
  );

✅ RLS 정책이 적용되었습니다.


## ⚡ 성능 최적화

### 인덱스 최적화

You: "쿼리 성능을 분석하고 필요한 인덱스를 추가해줘"

Claude: 쿼리 패턴을 분석하고 인덱스를 최적화하겠습니다.

[MCP로 쿼리 분석 중...]

📊 분석 결과:

  • 느린 쿼리 발견: 5개
  • 누락된 인덱스: 8개
  • 중복 인덱스: 2개
-- 자주 사용되는 검색 필드에 인덱스 추가
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_created_at ON users(created_at DESC);

-- 복합 인덱스 (JOIN 최적화)
CREATE INDEX idx_orders_user_id_created_at 
  ON orders(user_id, created_at DESC);

-- 부분 인덱스 (조건부 쿼리 최적화)
CREATE INDEX idx_orders_pending 
  ON orders(created_at DESC) 
  WHERE status = 'pending';

-- 텍스트 검색 인덱스
CREATE INDEX idx_products_search 
  ON products USING gin(to_tsvector('english', name || ' ' || description));

-- 중복 인덱스 제거
DROP INDEX IF EXISTS redundant_index_1;
DROP INDEX IF EXISTS redundant_index_2;

✅ 예상 성능 향상: 평균 85%


### 쿼리 최적화

You: "N+1 문제가 있는 쿼리를 찾아서 최적화해줘"

Claude: N+1 쿼리 패턴을 감지하고 최적화하겠습니다.

[MCP로 쿼리 로그 분석 중...]

⚠️ N+1 문제 발견:

Before (N+1 문제):

// 사용자별로 주문을 개별 조회 (비효율적)
const users = await supabase.from('users').select('*');
for (const user of users.data) {
  const orders = await supabase
    .from('orders')
    .select('*')
    .eq('user_id', user.id);
  user.orders = orders.data;
}
// 총 쿼리: 1 + N개 (N = 사용자 수)

After (최적화):

// JOIN으로 한 번에 조회
const { data } = await supabase
  .from('users')
  .select(`
    *,
    orders (*)
  `);
// 총 쿼리: 1개

성능 개선: 쿼리 수 95% 감소, 응답 시간 80% 단축


## 🔐 환경 변수 관리

### 안전한 토큰 관리

You: "환경별로 안전하게 토큰을 관리하는 방법을 보여줘"

Claude: 환경별 보안 설정 가이드를 제공하겠습니다.

.env.local (개발):

# 개발 환경 (Git 제외)
SUPABASE_URL=https://dev-project.supabase.co
SUPABASE_ANON_KEY=eyJ...dev...
SUPABASE_SERVICE_KEY=eyJ...dev-service...
SUPABASE_ACCESS_TOKEN=sbp_dev_token
MCP_READ_ONLY=false

.env.production (프로덕션):

# 프로덕션 환경 (환경 변수로 주입)
SUPABASE_URL=${PROD_SUPABASE_URL}
SUPABASE_ANON_KEY=${PROD_SUPABASE_ANON_KEY}
SUPABASE_SERVICE_KEY=${PROD_SUPABASE_SERVICE_KEY}
SUPABASE_ACCESS_TOKEN=${PROD_SUPABASE_ACCESS_TOKEN}
MCP_READ_ONLY=true  # 항상 true

토큰 로테이션 스크립트:

// scripts/rotate-tokens.js
const { createClient } = require('@supabase/supabase-js');

async function rotateTokens() {
  // 1. 새 토큰 생성
  const newToken = await generateNewToken();
  
  // 2. 환경 변수 업데이트
  await updateEnvironmentVariable('SUPABASE_ACCESS_TOKEN', newToken);
  
  // 3. 이전 토큰 무효화 (24시간 후)
  setTimeout(() => revokeOldToken(oldToken), 24 * 60 * 60 * 1000);
  
  console.log('✅ 토큰 로테이션 완료');
}

// 매월 실행
cron.schedule('0 0 1 * *', rotateTokens);

📈 모니터링과 로깅

MCP 활동 모니터링

You: "MCP 서버의 모든 활동을 로깅하고 모니터링하는 시스템을 만들어줘"

Claude: MCP 활동 모니터링 시스템을 구현하겠습니다.

**lib/mcp-monitor.ts:**
```typescript
import { createClient } from '@supabase/supabase-js';

interface MCPActivity {
  timestamp: Date;
  action: string;
  query?: string;
  result?: any;
  error?: string;
  duration: number;
  userId?: string;
}

class MCPMonitor {
  private activities: MCPActivity[] = [];
  
  async logActivity(activity: MCPActivity) {
    // 메모리에 저장
    this.activities.push(activity);
    
    // 데이터베이스에 기록
    await supabase
      .from('mcp_audit_log')
      .insert([{
        ...activity,
        timestamp: activity.timestamp.toISOString()
      }]);
    
    // 이상 감지
    this.detectAnomalies(activity);
  }
  
  private detectAnomalies(activity: MCPActivity) {
    // 위험한 쿼리 패턴 감지
    const dangerousPatterns = [
      /DELETEs+FROM/i,
      /DROPs+TABLE/i,
      /TRUNCATE/i,
      /UPDATE.*SET.*WHEREs+1=1/i
    ];
    
    if (activity.query) {
      for (const pattern of dangerousPatterns) {
        if (pattern.test(activity.query)) {
          this.sendAlert({
            level: 'critical',
            message: `위험한 쿼리 감지: ${activity.query}`,
            timestamp: activity.timestamp
          });
        }
      }
    }
    
    // 비정상적인 활동량 감지
    const recentActivities = this.activities.filter(
      a => a.timestamp > new Date(Date.now() - 60000) // 1분
    );
    
    if (recentActivities.length > 100) {
      this.sendAlert({
        level: 'warning',
        message: `비정상적인 활동량: 1분간 ${recentActivities.length}개 쿼리`,
        timestamp: new Date()
      });
    }
  }
  
  private async sendAlert(alert: any) {
    // Slack, Discord, 이메일 등으로 알림
    console.error('🚨 보안 알림:', alert);
    
    // 위험 수준에 따라 MCP 서버 중지
    if (alert.level === 'critical') {
      await this.emergencyShutdown();
    }
  }
  
  private async emergencyShutdown() {
    console.error('⛔ 긴급 MCP 서버 중지');
    process.exit(1);
  }
}

🎉 완료!

이제 안전하고 빠른 MCP 환경이 구축되었습니다!

📚 다음 챕터 예고

"프로덕션 배포와 스케일링" - MCP를 프로덕션 환경에 안전하게 배포하고 대규모 트래픽을 처리하는 방법!