SSE κΈ°λ° MCP μλ² - Trivyλ₯Ό νμ©ν 보μ μ·¨μ½μ μ€μΊ λ° SBOM μμ±
Kakao PlayMCPμ νΈνλλ MCP(Model Context Protocol) μλ²λ‘, νμΌμμ€ν λ° μ»¨ν μ΄λ μ΄λ―Έμ§μ λν 보μ μ·¨μ½μ μ€μΊκ³Ό SBOM(Software Bill of Materials) μμ± κΈ°λ₯μ μ 곡ν©λλ€.
- π μ·¨μ½μ μ€μΊ: νμΌμμ€ν λ° μ»¨ν μ΄λ μ΄λ―Έμ§ 보μ μ·¨μ½μ λΆμ
- π SBOM μμ±: SPDX, CycloneDX νμ μ§μ
- π κ²°κ³Ό μμ½: μ¬κ°λλ³ λΆλ₯, Top CVE/ν¨ν€μ§, κΆμ₯ μ‘°μΉ
- π SSE μ€νΈλ¦¬λ°: μ€μκ° μ§ν μν μ μ‘
- π 보μ: Path traversal λ°©μ§, λμ μ€ν μ ν
# 1. νλ‘μ νΈ ν΄λ‘
git clone <repository-url>
cd trivy_mcp
# 2. μ€μΊν νλ‘μ νΈ μ€λΉ
mkdir -p scan-input
cp -r /path/to/your/project ./scan-input/
# 3. μ€ν
docker-compose up -d --build
# 4. λ‘κ·Έ νμΈ
docker-compose logs -f
# 5. ν
μ€νΈ
curl http://localhost:8000/mcp/tools/list | jq .# 1. Python νκ²½ μ€λΉ
python -m venv venv
source venv/bin/activate
# 2. μμ‘΄μ± μ€μΉ
pip install -r requirements.txt
# 3. Trivy μ€μΉ (macOS)
brew install trivy
# 4. νκ²½ λ³μ μ€μ
cp .env.example .env
# .env νμΌμ μ μ ν μμ
# 5. λλ ν 리 μμ±
mkdir -p /tmp/trivy-mcp/{results,logs,cache,input}
# 6. νκ²½λ³μ μ€μ ν μ€ν
export SCAN_ROOT=/tmp/trivy-mcp/input
export RESULTS_DIR=/tmp/trivy-mcp/results
export TRIVY_CACHE_DIR=/tmp/trivy-mcp/cache
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000μμΈ κ°μ΄λλ CONNECTION_GUIDE.mdλ₯Ό μ°Έκ³ νμΈμ.
- λ‘컬 μ°κ²°:
http://localhost:8000/mcp/tools/list - μΈλΆ μ°κ²°(ν°λλ§):
ssh -R 80:localhost:8000 serveo.netμ€ν ν μμ±λ URL +/mcp/tools/list
λλ Docker λ΄λΆ λ€νΈμν¬ μ¬μ© μ:
http://trivy-mcp:8000/mcp/tools/list
| λ©μλ | κ²½λ‘ | μ€λͺ |
|---|---|---|
| GET | /mcp/tools/list |
μ¬μ© κ°λ₯ν λꡬ λͺ©λ‘ λ° μ€ν€λ§ |
| POST | /mcp/tools/call |
λꡬ μ€ν (run_id λ°ν) |
| GET | /mcp/runs/{id}/events |
SSE μ§ν μ΄λ²€νΈ μ€νΈλ¦Ό |
| GET | /mcp/runs/{id}/result |
μ΅μ’ κ²°κ³Ό μ‘°ν |
| GET | /mcp/runs/{id}/download/{artifact} |
κ²°κ³Ό νμΌ λ€μ΄λ‘λ |
μλ² λ° Trivy μν νμΈ
curl -X POST http://localhost:8000/mcp/tools/call \
-H "Content-Type: application/json" \
-d '{"name": "healthcheck", "arguments": {}}'νμΌμμ€ν μ·¨μ½μ μ€μΊ
curl -X POST http://localhost:8000/mcp/tools/call \
-H "Content-Type: application/json" \
-d '{
"name": "scan_filesystem",
"arguments": {
"path": "/scan/input/my-project",
"severity": "HIGH,CRITICAL"
}
}'컨ν μ΄λ μ΄λ―Έμ§ μ·¨μ½μ μ€μΊ
# μ΄λ―Έμ§ λ νΌλ°μ€λ‘ μ€μΊ
curl -X POST http://localhost:8000/mcp/tools/call \
-H "Content-Type: application/json" \
-d '{
"name": "scan_image",
"arguments": {
"image_ref": "nginx:latest"
}
}'
# tar νμΌλ‘ μ€μΊ
curl -X POST http://localhost:8000/mcp/tools/call \
-H "Content-Type: application/json" \
-d '{
"name": "scan_image",
"arguments": {
"image_tar_path": "/scan/input/image.tar"
}
}'νμΌμμ€ν SBOM μμ±
curl -X POST http://localhost:8000/mcp/tools/call \
-H "Content-Type: application/json" \
-d '{
"name": "generate_sbom_filesystem",
"arguments": {
"path": "/scan/input/my-project",
"sbom_format": "spdx-json"
}
}'μ΄λ―Έμ§ SBOM μμ±
curl -X POST http://localhost:8000/mcp/tools/call \
-H "Content-Type: application/json" \
-d '{
"name": "generate_sbom_image",
"arguments": {
"image_ref": "python:3.11-slim",
"sbom_format": "cyclonedx"
}
}'μ€μΊ κ²°κ³Ό μμ½
curl -X POST http://localhost:8000/mcp/tools/call \
-H "Content-Type: application/json" \
-d '{
"name": "summarize_scan_result",
"arguments": {
"raw_result_path": "/data/results/{run_id}/trivy-result.json",
"top_n": 10
}
}'μ€μΊ μ€ν ν SSEλ‘ μ§ν μνλ₯Ό μ€μκ° λͺ¨λν°λ§:
# 1. μ€μΊ μμ
RESPONSE=$(curl -s -X POST http://localhost:8000/mcp/tools/call \
-H "Content-Type: application/json" \
-d '{"name": "scan_filesystem", "arguments": {"path": "/scan/input/project"}}')
RUN_ID=$(echo $RESPONSE | jq -r '.run_id')
# 2. SSE μ΄λ²€νΈ ꡬλ
curl -N http://localhost:8000/mcp/runs/$RUN_ID/events| μ΄λ²€νΈ | μ€λͺ |
|---|---|
start |
μ€μΊ μμ |
progress |
μ§ν λ¨κ³ (downloading-db, scanning, done λ±) |
log |
Trivy stdout/stderr |
result |
μ΅μ’ κ²°κ³Ό |
error |
μ€λ₯ λ°μ |
POST /mcp/tools/call + GET /mcp/runs/{id}/events λΆλ¦¬ λ°©μμ μ±ννμ΅λλ€.
- λΉμ°¨λ¨ μλ΅: POST μμ²μ΄ μ¦μ
run_idλ₯Ό λ°ννμ¬ ν΄λΌμ΄μΈνΈκ° λΈλ‘νΉλμ§ μμ - μ¬μ°κ²° μ§μ: SSE μ°κ²°μ΄ λμ΄μ Έλ λμΌν
run_idλ‘ μ¬μ°κ²° κ°λ₯ - λ€μ€ ꡬλ : μ¬λ¬ ν΄λΌμ΄μΈνΈκ° λμΌ μμ μ μ§ν μνλ₯Ό λμμ ꡬλ κ°λ₯
- PlayMCP νΈνμ±: μμ²/μλ΅κ³Ό μ΄λ²€νΈ μ€νΈλ¦Όμ΄ λΆλ¦¬λμ΄ νμ€ MCP νλ‘ν μ½κ³Ό νΈν
# DB λ€μ΄λ‘λ μ μ© μ»¨ν
μ΄λ μ€ν
docker run -it --rm \
-v trivy-db-cache:/root/.cache/trivy \
aquasec/trivy:latest \
image --download-db-only
# μΊμ λλ ν 리 μΆμΆ
docker run --rm \
-v trivy-db-cache:/cache \
-v $(pwd):/output \
alpine tar czf /output/trivy-db.tar.gz -C /cache .scp trivy-db.tar.gz internal-server:/path/to/# docker-compose.ymlμ΄ μλ λλ ν 리μμ
mkdir -p trivy-cache
tar xzf trivy-db.tar.gz -C trivy-cache/
# docker-compose.yml μμ : λ³Όλ₯¨μ λ‘컬 λλ ν λ¦¬λ‘ λ³κ²½
# - trivy-cache:/data/trivy-cache
# β - ./trivy-cache:/data/trivy-cache
# OFFLINE_MODE νμ±ν
# environment:
# - OFFLINE_MODE=true
docker-compose up -dLLMμ΄ λ€μ λꡬλ€μ μμ°¨μ μΌλ‘ νΈμΆν©λλ€:
-
scan_filesystem: μ·¨μ½μ μ€μΊ
{"name": "scan_filesystem", "arguments": {"path": "/scan/input/project"}} -
generate_sbom_filesystem: SBOM μμ±
{"name": "generate_sbom_filesystem", "arguments": {"path": "/scan/input/project"}} -
(μ ν) summarize_scan_result: κ²°κ³Ό μμ½
{"name": "summarize_scan_result", "arguments": {"raw_result_path": "/data/results/{scan_id}/trivy-result.json"}}
| λ³μ | κΈ°λ³Έκ° | μ€λͺ |
|---|---|---|
HOST |
0.0.0.0 | μλ² λ°μΈλ μ£Όμ |
PORT |
8000 | μλ² ν¬νΈ |
SCAN_ROOT |
/scan/input | μ€μΊ νμ© λ£¨νΈ λλ ν 리 |
TRIVY_CACHE_DIR |
/data/trivy-cache | Trivy DB μΊμ |
RESULTS_DIR |
/data/results | κ²°κ³Ό μ μ₯ λλ ν 리 |
MAX_CONCURRENT_SCANS |
2 | λμ μ€μΊ μ΅λ μ |
DEFAULT_TIMEOUT_SEC |
600 | μ€μΊ νμμμ |
OFFLINE_MODE |
false | DB μλ λ€μ΄λ‘λ λΉνμ±ν |
- Path Traversal λ°©μ§: λͺ¨λ κ²½λ‘λ
SCAN_ROOTνμλ‘ μ ν - λμ μ€ν μ ν:
MAX_CONCURRENT_SCANSλ‘ λ¦¬μμ€ λ³΄νΈ - μμ² ν¬κΈ° μ ν:
MAX_REQUEST_SIZE_MBλ‘ λμ©λ μμ² μ°¨λ¨ - λͺ
λ Ή μ£Όμ
λ°©μ§: λͺ¨λ subprocess νΈμΆμ
shell=False - λΉλ£¨νΈ μ€ν: Docker 컨ν
μ΄λλ μ μ©
trivyμ¬μ©μλ‘ μ€ν
MIT License