Cloudflare DLP + OPA로 MCP 도구별 RBAC와 실시간 PII 차단 구현하기
이 글은 JWT 인증과 HTTP API 기본 개념에 익숙한 백엔드·플랫폼 엔지니어를 대상으로 합니다.
AI 에이전트가 외부 도구에 접근하는 시대가 빠르게 열리고 있습니다. MCP(Model Context Protocol)를 통해 LLM은 파일 시스템, 데이터베이스, 외부 API를 마치 함수처럼 호출할 수 있게 되었지만, 그 이면에는 심각한 보안 공백이 존재합니다. Knostic 조사에 따르면 공개 인터넷에 인증 없이 노출된 MCP 서버가 1,800개 이상 발견되었고(arXiv 2511.20920), AI 보안 사고는 전년 대비 56.4% 급증하여 절반 가까이가 고객 PII 유출을 동반했습니다. MCP 사양에 OAuth 2.0 인증이 추가된 것은 불과 2025년 3월의 일입니다.
이 글은 두 가지 핵심 질문에 답합니다. 첫째, "어떤 역할의 사용자가 어떤 MCP 도구를 쓸 수 있는가"를 코드로 선언하려면 어떻게 해야 하는가. 둘째, "AI 트래픽 안에 숨은 민감 데이터를 실시간으로 탐지·차단하려면 어떤 구조가 필요한가". OPA(Open Policy Agent)와 Cloudflare AI Gateway DLP를 결합하면 MCP 도구별 세밀한 RBAC 정책을 Rego 코드로 작성·테스트·배포하고, 프롬프트와 응답에서 PII를 실시간 차단하는 전체 흐름을 직접 구현할 수 있습니다.
핵심 개념
MCP 보안의 현주소
MCP는 Anthropic이 설계한 오픈 표준으로, AI 에이전트가 file_read, db_write, secret_fetch처럼 명명된 도구(tool)를 통해 외부 시스템과 상호작용하는 표준 인터페이스입니다. 2025년 3월에야 사양에 OAuth 2.0 인증이 추가되었고, 같은 해 6월에는 MCP 서버가 OAuth 2.0 리소스 서버로 공식 분류되어 도구 단위 스코프 제어가 표준화되었습니다. 사양이 빠르게 성숙하고 있지만, 구현체 간 호환성과 보안 검증은 여전히 진행 중입니다.
MCP 보안의 핵심 과제는 두 가지입니다. 서버 접근 제어(누가 어떤 도구를 쓸 수 있는가)와 데이터 유출 방지(민감 정보가 LLM에 입력되거나 응답으로 나가는 것을 막는 것)입니다. 이 두 과제를 각각 OPA와 Cloudflare DLP가 담당합니다.
OPA와 Policy-as-Code — Rego 기초 포함
OPA(Open Policy Agent)는 CNCF 졸업 프로젝트로, 애플리케이션 코드에서 정책 로직을 완전히 분리하는 범용 정책 엔진입니다. Rego라는 선언적 언어로 정책을 작성하며, HTTP API·사이드카·인라인 라이브러리 세 가지 배포 모드를 지원합니다.
Policy-as-Code란? 접근 제어 규칙을 애플리케이션 코드가 아닌 별도의 선언적 파일(Rego, YAML 등)로 관리하여, 코드 배포 없이 정책을 수정하고 Git으로 버전 관리하는 방식입니다.
Rego를 처음 접하는 분을 위해 핵심 평가 규칙 두 가지를 먼저 이해하면 이후 코드를 읽기 훨씬 쉽습니다.
Rego 평가 규칙 (꼭 알아야 할 두 가지)
- 기본 부정(Closed World Assumption):
default allow = false로 시작하면 명시적으로 허용하지 않은 모든 요청이 거부됩니다.- 다중
allow블록은 OR 관계:allow { 조건A }블록과allow { 조건B }블록이 모두 있을 때, 둘 중 하나라도 참이면 전체allow가true가 됩니다.
Python 서버에서 OPA에 정책 평가를 요청할 때는 REST API의 /v1/data/<패키지_경로> 엔드포인트를 사용합니다. 예를 들어 package mcp.authz로 선언된 정책은 http://opa:8181/v1/data/mcp/authz로 조회하며, 응답의 result.allow 필드가 true/false를 담습니다.
Cloudflare AI Gateway DLP
Cloudflare AI Gateway는 AI 모델 프로바이더(OpenAI, Anthropic, Gemini 등)와 클라이언트 사이에 위치하는 인텔리전트 프록시입니다. 내장된 DLP 엔진은 LLM에 입력되는 프롬프트와 출력되는 응답 모두를 실시간으로 스캔하며, 2025년 8월 대규모 업데이트에서 AI 컨텍스트 분석 기반 탐지 기능이 추가되어 기존 정규식 방식의 오탐 문제를 보완하고 있습니다.
| 카테고리 | 탐지 항목 예시 |
|---|---|
| 금융·PII | 신용카드 번호, SSN, 주민등록번호 |
| 정부 식별자 | 여권 번호, 운전면허 번호 |
| 의료 정보 | 환자 ID, 진단 코드 (HIPAA 대상) |
| 개발자 자산 | API 키, 소스 코드, 시크릿 |
| AI 프롬프트 보호 | ChatGPT·Claude·Gemini 프롬프트 인젝션 패턴 |
통합 아키텍처 3-레이어
아래 다이어그램은 트래픽 흐름 기준으로 레이어를 나타냅니다. OAuth 신원 레이어는 가장 아래에 표시되지만, 실제로는 연결 시작 전 토큰을 발급하는 선행 단계입니다. 요청이 도착했을 때의 처리 순서는 Cloudflare(전송) → OPA(정책) 순입니다.
┌──────────────────────────────────────────────────────┐
│ AI 에이전트 / MCP 클라이언트 │
└────────────────────┬─────────────────────────────────┘
│ HTTP/SSE (Bearer JWT 포함)
┌────────────────────▼─────────────────────────────────┐
│ 레이어 1: Cloudflare AI Gateway (전송 레이어) │
│ ├─ DLP Firewall: 프롬프트·응답 민감 데이터 탐지 │
│ ├─ Guardrails: 유해 콘텐츠 분류 │
│ └─ 레이트 리미팅, 감사 로그 │
└────────────────────┬─────────────────────────────────┘
│
┌────────────────────▼─────────────────────────────────┐
│ 레이어 2: OPA (정책 레이어) │
│ ├─ JWT 클레임 파싱 │
│ ├─ 도구 이름·사용자 역할·리소스 범위 평가 │
│ └─ allow / deny 결정 반환 │
└────────────────────┬─────────────────────────────────┘
│
┌────────────────────▼─────────────────────────────────┐
│ [선행] OAuth 2.0 신원 레이어 (연결 전 토큰 발급) │
│ ├─ Keycloak / Entra ID / Auth0 │
│ └─ 서명된 JWT 발급, 도구별 스코프 포함 │
└──────────────────────────────────────────────────────┘실전 적용
예시 1: OPA Rego로 MCP 도구별 per-tool RBAC 구현
Red Hat이 2025년 12월에 공개한 MCP Gateway 패턴을 기반으로 합니다. Keycloak에서 각 MCP 도구(file_read, db_write, secret_fetch)를 OAuth 2.0 클라이언트 롤로 등록하고, JWT의 resource_access 클레임에 허용된 도구 목록이 담깁니다.
기본 per-tool RBAC 정책:
# policies/mcp/authz.rego
package mcp.authz
default allow = false
# JWT resource_access 클레임에 요청 도구가 포함된 경우 허용
allow {
tool := input.tool_name
server_id := input.server_id
roles := input.token.resource_access[server_id].roles
roles[_] == tool
}민감도 레벨과 시간 기반 접근을 포함한 ABAC 확장 정책:
# policies/mcp/authz_extended.rego
package mcp.authz
import future.keywords.in
default allow = false
# 관리자는 모든 도구 허용
allow {
"admin" in input.token.realm_access.roles
}
# analyst 역할은 민감도 레벨 2 이하 도구만 허용
allow {
"analyst" in input.token.realm_access.roles
tool_sensitivity[input.tool_name] <= 2
}
# power_user는 민감도 3 이하 도구를 업무 시간(09~18시)에만 허용
# 참고: 테스트 가능성을 위해 현재 시각을 input.current_hour로 주입합니다.
allow {
"power_user" in input.token.realm_access.roles
tool_sensitivity[input.tool_name] <= 3
input.current_hour >= 9
input.current_hour < 18
}
# 도구별 민감도 레벨 매핑
tool_sensitivity := {
"file_read": 1,
"db_read": 2,
"db_write": 3,
"secret_fetch": 4,
"admin_exec": 5,
}OPA 정책 단위 테스트 (시간 기반 조건 포함):
# policies/mcp/authz_test.rego
package mcp.authz_test
import data.mcp.authz
# analyst가 file_read(민감도 1) 호출 → 허용
test_analyst_can_read_files {
authz.allow with input as {
"tool_name": "file_read",
"server_id": "mcp-server-files",
"current_hour": 10,
"token": {
"realm_access": {"roles": ["analyst"]},
"resource_access": {}
}
}
}
# analyst가 secret_fetch(민감도 4) 호출 → 거부
test_analyst_cannot_fetch_secrets {
not authz.allow with input as {
"tool_name": "secret_fetch",
"server_id": "mcp-server-secrets",
"current_hour": 10,
"token": {
"realm_access": {"roles": ["analyst"]},
"resource_access": {}
}
}
}
# power_user가 업무 시간(10시)에 db_write(민감도 3) 호출 → 허용
test_power_user_allowed_in_business_hours {
authz.allow with input as {
"tool_name": "db_write",
"server_id": "mcp-server-db",
"current_hour": 10,
"token": {
"realm_access": {"roles": ["power_user"]},
"resource_access": {}
}
}
}
# power_user가 업무 시간 외(22시)에 db_write 호출 → 거부
test_power_user_blocked_outside_hours {
not authz.allow with input as {
"tool_name": "db_write",
"server_id": "mcp-server-db",
"current_hour": 22,
"token": {
"realm_access": {"roles": ["power_user"]},
"resource_access": {}
}
}
}# 정책 테스트 실행
opa test policies/ -v예시 2: Python MCP 서버에 OPA 인터셉터 통합
아래 코드는 실제 MCP SDK의 도구 디스패치 레이어 앞에 OPA 정책 평가를 삽입하는 패턴입니다. 데코레이터 방식 대신 명시적인 미들웨어 함수로 구성하여 MCP 서버 핸들러 시그니처와의 충돌을 피했습니다.
# mcp_server/auth_interceptor.py
import httpx
from typing import Any
OPA_URL = "http://opa:8181/v1/data/mcp/authz"
async def check_opa_policy(
tool_name: str,
token_claims: dict,
server_id: str,
current_hour: int,
) -> bool:
"""
OPA REST API(/v1/data/mcp/authz)에 정책 평가를 요청합니다.
- 로컬 사이드카: 네트워크 홉 없이 sub-ms 수준
- 원격 HTTP API: 약 5~15ms (timeout=0.5는 원격 호출 기준의 여유값)
"""
payload = {
"input": {
"tool_name": tool_name,
"server_id": server_id,
"current_hour": current_hour,
"token": token_claims,
}
}
async with httpx.AsyncClient() as client:
response = await client.post(OPA_URL, json=payload, timeout=0.5)
result = response.json()
# OPA 응답 구조: {"result": {"allow": true/false}}
return result.get("result", {}).get("allow", False)
async def authorized_tool_call(
tool_name: str,
arguments: dict,
token_claims: dict,
server_id: str,
current_hour: int,
) -> Any:
"""
OPA 정책 평가 후 허용된 경우에만 도구를 실행합니다.
MCP SDK 핸들러에서 직접 호출하는 방식으로 시그니처 충돌을 방지합니다.
"""
allowed = await check_opa_policy(tool_name, token_claims, server_id, current_hour)
if not allowed:
raise PermissionError(
f"도구 '{tool_name}' 호출이 정책에 의해 거부되었습니다."
)
return await dispatch_tool(tool_name, arguments)
async def dispatch_tool(tool_name: str, arguments: dict) -> Any:
"""실제 도구 구현을 디스패치합니다."""
if tool_name == "file_read":
return read_file(arguments["path"])
elif tool_name == "db_write":
return write_to_db(arguments["query"], arguments["data"])
else:
raise ValueError(f"알 수 없는 도구: {tool_name}")# mcp_server/server.py — FastMCP 또는 공식 Python SDK와 연동 예시
from datetime import datetime, timezone
from mcp.server import Server
from mcp.types import Tool, TextContent
from auth_interceptor import authorized_tool_call
server = Server("mcp-server-files")
@server.list_tools()
async def list_tools() -> list[Tool]:
return [
Tool(name="file_read", description="파일을 읽습니다", inputSchema={...}),
Tool(name="db_write", description="DB에 씁니다", inputSchema={...}),
]
@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
# JWT 파싱은 별도 미들웨어(Bearer 토큰 검증)에서 처리 후 context로 전달
token_claims = server.get_context().token_claims
current_hour = datetime.now(timezone.utc).hour
result = await authorized_tool_call(
tool_name=name,
arguments=arguments,
token_claims=token_claims,
server_id="mcp-server-files",
current_hour=current_hour,
)
return [TextContent(type="text", text=str(result))]예시 3: Cloudflare AI Gateway DLP 설정 (Terraform IaC)
DLP 프로파일과 Gateway 정책을 Terraform으로 관리하면 코드 리뷰와 Git 이력을 통한 정책 감사가 가능합니다.
주의: 아래 예시는 Cloudflare Terraform 프로바이더 v4.x 기준입니다.
cloudflare_gateway_policy의rule_settings블록 구조는 프로바이더 버전마다 달라질 수 있으므로, 적용 전 공식 Terraform Registry 문서에서 현재 버전의 스키마를 확인하는 것을 권장합니다.
# cloudflare_dlp.tf
terraform {
required_providers {
cloudflare = {
source = "cloudflare/cloudflare"
version = "~> 4.0"
}
}
}
# MCP 트래픽용 커스텀 DLP 프로파일
resource "cloudflare_dlp_profile" "mcp_sensitive_data" {
account_id = var.cloudflare_account_id
name = "MCP Sensitive Data Profile"
type = "custom"
description = "MCP AI 트래픽에서 PII 및 자격증명 탐지"
# 신용카드 번호 탐지 (사전 정의 패턴)
entry {
name = "Credit Card Numbers"
enabled = true
type = "predefined"
}
# API 키 커스텀 패턴 (sk-로 시작하는 OpenAI 키)
entry {
name = "API Keys and Secrets"
enabled = true
type = "custom"
pattern {
regex = "sk-[a-zA-Z0-9]{48}"
# context 필드로 주변 키워드를 조건에 추가하면 오탐을 줄일 수 있습니다
context = "(?i)(api[_-]?key|secret|token)"
operator = "matchCount"
count = 1
}
}
# 소스 코드 탐지
entry {
name = "Source Code"
enabled = true
type = "predefined"
}
}
# AI Gateway에 DLP 정책 연결
resource "cloudflare_gateway_policy" "mcp_dlp_policy" {
account_id = var.cloudflare_account_id
name = "Block MCP Sensitive Data"
description = "AI 프롬프트/응답의 민감 데이터 차단"
enabled = true
precedence = 10
filters = ["http"]
action = "block"
rule_settings {
block_page_enabled = true
block_page_reason = "민감한 데이터가 감지되어 요청이 차단되었습니다."
dlp_profile {
id = cloudflare_dlp_profile.mcp_sensitive_data.id
}
}
# AI Gateway 엔드포인트만 대상으로 지정
traffic = "http.request.uri matches \".*gateway.ai.cloudflare.com.*\""
}프롬프트와 응답 양방향 스캔 흐름:
[사용자 → SSN 포함 프롬프트 전송]
│
▼
Cloudflare AI Gateway
┌──────────────────────────────────┐
│ DLP Firewall │
│ → SSN 패턴 감지 (###-##-####) │
│ → Action: BLOCK │
│ → Logpush: S3/Splunk로 이벤트 │
└──────────┬───────────────────────┘
│ (차단 — LLM에 도달하지 않음)
▼
[사용자에게 차단 응답 반환]
[정상 프롬프트]
AI Gateway → LLM → 응답 반환
│
[응답에 소스 코드 포함 시]
DLP Firewall → BLOCK
[클라이언트 전달 차단]장단점 분석
장점
| 항목 | 내용 |
|---|---|
| Policy-as-Code | OPA Rego 정책을 Git으로 버전 관리하고 opa test로 테스트·배포할 수 있어, 코드 변경 없이 권한 수정이 가능합니다 |
| 세밀한 도구별 RBAC | MCP 서버 레벨이 아닌 개별 도구(tool) 단위로 역할·속성·시간·환경 기반 접근 제어를 적용할 수 있습니다 |
| 실시간 DLP | 프롬프트·응답 스캔으로 데이터 유출을 사전 차단하여 GDPR, HIPAA 등 규정 준수 요건 충족에 유리합니다 |
| 중앙화된 거버넌스 | 모든 AI 트래픽이 단일 게이트웨이를 통과하므로 일관된 정책 적용과 감사 로그 확보가 가능합니다 |
| 프레임워크 독립성 | OPA는 특정 언어나 프레임워크에 종속되지 않아 Python, TypeScript, Go 등 다양한 스택에 동일하게 통합됩니다 |
단점 및 주의사항
| 항목 | 내용 | 대응 방안 |
|---|---|---|
| 통합 복잡도 | Cloudflare DLP와 OPA 간 직접 통합이 제공되지 않아 커스텀 미들웨어 구현이 필요합니다 | Envoy ext_authz 필터 + OPA 사이드카 패턴, 또는 agentgateway 오픈소스 검토를 권장합니다 |
| Rego 학습 곡선 | Rego는 선언적 언어로 명령형 언어 배경의 개발자에게 초기 학습 비용이 높습니다 | YAML 기반의 Cerbos를 대안으로 고려하거나 OPA Playground에서 시작하는 것을 권장합니다 |
| DLP 오탐(False Positive) | 정규식 기반 탐지는 맥락을 고려하지 않아 합법적인 데이터도 차단할 수 있습니다 | AI 컨텍스트 분석 기능을 활성화하고, context 필드로 주변 키워드를 조건에 추가하는 것을 권장합니다 |
| 지연 시간 추가 | OPA 평가(로컬 사이드카 sub-ms, 원격 HTTP 5~15ms)와 DLP 스캔이 모든 요청에 추가됩니다 | OPA 번들 캐싱을 활성화하고, DLP 탐지 범위를 민감 데이터를 다루는 엔드포인트에만 적용하는 것을 권장합니다 |
| 정책 폭발(Policy Sprawl) | 도구 수가 증가할수록 Rego 정책 파일이 복잡해집니다 | 패키지 계층 구조(mcp.authz, mcp.tools.*)로 모듈화하고 opa test를 CI에 통합하는 것을 권장합니다 |
| Cloudflare 벤더 종속 | DLP·Gateway 기능은 Cloudflare 플랫폼에 종속됩니다 | 멀티클라우드 환경이라면 LLM Guard 같은 플랫폼 독립적인 오픈소스 DLP 레이어 병행을 검토해보시면 좋습니다 |
ext_authz란? Envoy Proxy의 External Authorization 필터로, 인바운드 요청을 외부 정책 서버(OPA 등)에 위임하여 allow/deny를 결정받는 메커니즘입니다. Red Hat MCP Gateway 아키텍처의 핵심 통합 포인트입니다.
실무에서 가장 흔한 실수
- OPA를 각 MCP 서버에 인라인으로 임베딩하는 경우 — 정책이 서버별로 분산되어 일관성이 깨집니다. 중앙화된 OPA 클러스터 또는 사이드카 패턴으로 정책 소스를 단일화하는 것이 권장됩니다.
- DLP 탐지 범위를 모든 HTTP 트래픽에 적용하는 경우 — 오탐이 늘고 지연이 증가합니다. AI Gateway 엔드포인트와 민감 데이터를 다루는 특정 MCP 도구 호출에만 집중적으로 적용하는 것을 권장합니다.
- JWT 클레임 구조를 Rego 정책에 하드코딩하는 경우 — IdP가 변경되면 모든 정책 파일을 수정해야 합니다. JWT 클레임 파싱 로직을 별도의
token.rego헬퍼 패키지로 분리하면 IdP 교체 시 한 곳만 수정하면 됩니다.
마치며
지금까지 OPA Rego로 MCP 도구별 RBAC 정책을 선언하고 테스트하는 방법, Python MCP 서버에 OPA 인터셉터를 연결하는 방법, Terraform으로 Cloudflare DLP 프로파일을 코드로 관리하는 방법을 살펴보았습니다. 이 세 가지 레이어를 조합하면, 여러분은 지금 바로 MCP 도구별 세밀한 RBAC 정책을 코드로 선언하고 AI 트래픽에서 PII를 실시간으로 차단하는 보안 인프라를 직접 구축할 수 있습니다.
지금 바로 시작해볼 수 있는 3단계입니다.
- OPA 로컬 환경 구성 및 첫 정책 작성 (~10분) —
brew install opa또는docker run openpolicyagent/opa로 OPA를 실행한 뒤, OPA Playground에서 예시 1의authz.rego를 붙여넣고input값을 바꿔가며 allow/deny 동작을 직접 확인해보실 수 있습니다. - Cloudflare AI Gateway 무료 티어에서 DLP 프로파일 생성 (~15분) — Cloudflare Zero Trust 대시보드 →
Gateway→DLP Profiles에서 "Credit Card Numbers" 사전 정의 프로파일을 활성화하고, AI Gateway 엔드포인트에 연결하여 테스트 프롬프트에 카드 번호를 포함했을 때 차단되는지 확인해보실 수 있습니다. - 기존 MCP 서버에 OPA 인터셉터 추가 (~30분) — 예시 2의
authorized_tool_call함수를 참고하여 가장 민감한 도구(secret_fetch,db_write) 핸들러에 먼저 적용해보실 수 있습니다.opa test policies/ -v로 정책 단위 테스트를 CI 파이프라인에 포함하면 정책 변경의 안전망이 확보됩니다.
다음 글: OPA 번들 서버를 Kubernetes에 배포하고 GitOps 워크플로로 Rego 정책을 자동 배포하는 방법 — MCP 도구 정책의 CI/CD 파이프라인 구축 가이드
참고 자료
- Cloudflare AI Gateway DLP 공식 문서
- Cloudflare AI Gateway DLP 설정 가이드
- Cloudflare AI Gateway Guardrails 공식 문서
- Cloudflare AI Gateway 2025년 8월 대규모 업데이트 블로그
- Cloudflare AI Prompt Protection 발표 블로그
- Cloudflare DLP AI 컨텍스트 분석 정확도 개선
- Cloudflare DLP 프로파일 공식 문서
- OPA 공식 홈페이지
- OPA 공식 문서
- CNCF OPA 보안 배포 모범 사례
- Red Hat: MCP Gateway 고급 인증·인가 (OPA Rego 통합)
- Red Hat: MCP 보안 인증·인가 구현 가이드
- MCP 공식 보안 모범 사례
- MCP 공식 인가(Authorization) 사양 (2025-06-18)
- Cerbos: MCP 서버 Fine-Grained 인가
- Cerbos: AI 에이전트 동적 인가 가이드
- Microsoft Agent Governance Toolkit GitHub
- MCP 보안 위험·제어·거버넌스 논문 (arXiv)
- agentgateway: MCP Authorization 사양 준수 구현
- LLM 프롬프트 인젝션 공격 모니터링 (Datadog)