merge: co-gahusb DNS-rebinding 421 핫픽스
This commit is contained in:
@@ -3,6 +3,7 @@ import logging
|
|||||||
|
|
||||||
import redis.asyncio as aioredis
|
import redis.asyncio as aioredis
|
||||||
from mcp.server.fastmcp import FastMCP
|
from mcp.server.fastmcp import FastMCP
|
||||||
|
from mcp.server.transport_security import TransportSecuritySettings
|
||||||
from starlette.applications import Starlette
|
from starlette.applications import Starlette
|
||||||
from starlette.middleware import Middleware
|
from starlette.middleware import Middleware
|
||||||
from starlette.middleware.base import BaseHTTPMiddleware
|
from starlette.middleware.base import BaseHTTPMiddleware
|
||||||
@@ -16,7 +17,12 @@ _auth_failed_logged = False
|
|||||||
|
|
||||||
_redis = aioredis.from_url(config.REDIS_URL, decode_responses=True)
|
_redis = aioredis.from_url(config.REDIS_URL, decode_responses=True)
|
||||||
|
|
||||||
mcp = FastMCP("co-gahusb")
|
# DNS-rebinding 보호 비활성화: 실 보안은 nginx 앞단 Bearer 인증(MCP 도달 전 401)이다.
|
||||||
|
# 원격 HTTPS + 정적키 모델이라 Host 화이트리스트는 보안가치 ~0이고, 도메인 변경 시 또 깨진다.
|
||||||
|
mcp = FastMCP(
|
||||||
|
"co-gahusb",
|
||||||
|
transport_security=TransportSecuritySettings(enable_dns_rebinding_protection=False),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# ---- 메시지 ----
|
# ---- 메시지 ----
|
||||||
|
|||||||
@@ -2,6 +2,11 @@
|
|||||||
import os
|
import os
|
||||||
os.environ["CO_BUS_KEY"] = "test-key"
|
os.environ["CO_BUS_KEY"] = "test-key"
|
||||||
|
|
||||||
|
# config.CO_BUS_KEY는 import 시점에 한 번 읽히므로, 다른 테스트 모듈이 app.config를
|
||||||
|
# 먼저 import하면 빈 값으로 굳는다. import 순서와 무관하게 모듈 속성을 직접 강제한다.
|
||||||
|
from app import config
|
||||||
|
config.CO_BUS_KEY = "test-key"
|
||||||
|
|
||||||
from starlette.testclient import TestClient
|
from starlette.testclient import TestClient
|
||||||
from app.server import app
|
from app.server import app
|
||||||
|
|
||||||
@@ -23,3 +28,27 @@ def test_mcp_wrong_key_rejected():
|
|||||||
client = TestClient(app)
|
client = TestClient(app)
|
||||||
res = client.post("/mcp", json={}, headers={"Authorization": "Bearer wrong"})
|
res = client.post("/mcp", json={}, headers={"Authorization": "Bearer wrong"})
|
||||||
assert res.status_code == 401
|
assert res.status_code == 401
|
||||||
|
|
||||||
|
|
||||||
|
def test_mcp_valid_auth_passes_dns_host_check():
|
||||||
|
# 유효한 키는 인증 게이트를 통과하고, MCP DNS-rebinding Host 검증에 막혀선 안 된다.
|
||||||
|
# TestClient 기본 Host="testserver"는 localhost가 아니므로, 보호가 켜져 있으면 421.
|
||||||
|
# 컨텍스트 매니저로 써야 lifespan(세션 매니저 task group)이 기동되어 MCP 핸들러까지 도달.
|
||||||
|
with TestClient(app) as client:
|
||||||
|
res = client.post(
|
||||||
|
"/mcp",
|
||||||
|
headers={
|
||||||
|
"Authorization": "Bearer test-key",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept": "application/json, text/event-stream",
|
||||||
|
},
|
||||||
|
json={
|
||||||
|
"jsonrpc": "2.0", "id": 1, "method": "initialize",
|
||||||
|
"params": {
|
||||||
|
"protocolVersion": "2024-11-05", "capabilities": {},
|
||||||
|
"clientInfo": {"name": "smoke", "version": "0"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert res.status_code != 401 # 인증 통과
|
||||||
|
assert res.status_code != 421 # Host 검증에 막히면 안 됨
|
||||||
|
|||||||
Reference in New Issue
Block a user