feat/security-hardening #5

Merged
gahusb merged 3 commits from feat/security-hardening into main 2026-05-17 14:00:04 +09:00
3 changed files with 66 additions and 3 deletions
Showing only changes of commit 6053e69afc - Show all commits

View File

@@ -47,13 +47,30 @@ scheduler = BackgroundScheduler(timezone=os.getenv("TZ", "Asia/Seoul"))
# Windows AI Server URL (NAS .env에서 설정)
WINDOWS_AI_SERVER_URL = os.getenv("WINDOWS_AI_SERVER_URL", "http://192.168.0.5:8000")
# Admin API Key 인증
# Admin API Key 인증 — /api/trade/* 보호 (CODE_REVIEW F2)
# 빈 키 + 명시적 dev flag 없으면 503으로 거부. 운영 .env에 ADMIN_API_KEY 누락 시
# 무인증 통과되던 버그 차단.
ADMIN_API_KEY = os.getenv("ADMIN_API_KEY", "")
def verify_admin(x_admin_key: str = Header(None)):
"""admin/trade 엔드포인트 보호용 API 키 검증"""
"""admin/trade 엔드포인트 보호용 API 키 검증.
- ADMIN_API_KEY 설정됨 + 키 일치 → 통과
- ADMIN_API_KEY 설정됨 + 키 불일치 → 401 Unauthorized
- ADMIN_API_KEY 미설정 + ALLOW_UNAUTHENTICATED_ADMIN=true → 통과 (개발 모드)
- ADMIN_API_KEY 미설정 + dev flag 없음 → 503 (보호 강화, 운영 .env 누락 차단)
"""
if not ADMIN_API_KEY:
return # 키 미설정 시 인증 비활성화 (개발 환경)
if os.getenv("ALLOW_UNAUTHENTICATED_ADMIN", "false").lower() == "true":
return # 개발 환경 명시적 허용
raise HTTPException(
status_code=503,
detail=(
"admin endpoint protected — ADMIN_API_KEY not configured. "
"Set ADMIN_API_KEY in .env, or set ALLOW_UNAUTHENTICATED_ADMIN=true "
"for development only."
),
)
if x_admin_key != ADMIN_API_KEY:
raise HTTPException(status_code=401, detail="Unauthorized")

3
stock/pytest.ini Normal file
View File

@@ -0,0 +1,3 @@
[pytest]
pythonpath = .
asyncio_mode = auto

View File

@@ -0,0 +1,43 @@
"""verify_admin 보안 강화 회귀 테스트 (CODE_REVIEW F2).
운영 .env에서 ADMIN_API_KEY가 누락되면 /api/trade/balance, /api/trade/order
인증이 무력화되는 버그를 막기 위한 가드.
"""
import os
from unittest.mock import patch
import pytest
from fastapi import HTTPException
from app import main as stock_main
def test_verify_admin_rejects_when_key_missing_and_no_dev_flag(monkeypatch):
"""ADMIN_API_KEY 미설정 + ALLOW_UNAUTHENTICATED_ADMIN 미설정 → 503."""
monkeypatch.setattr(stock_main, "ADMIN_API_KEY", "")
monkeypatch.delenv("ALLOW_UNAUTHENTICATED_ADMIN", raising=False)
with pytest.raises(HTTPException) as exc_info:
stock_main.verify_admin(x_admin_key=None)
assert exc_info.value.status_code == 503
assert "ADMIN_API_KEY" in exc_info.value.detail
def test_verify_admin_allows_when_key_missing_with_dev_flag(monkeypatch):
"""ADMIN_API_KEY 미설정 + ALLOW_UNAUTHENTICATED_ADMIN=true → 통과 (개발 모드)."""
monkeypatch.setattr(stock_main, "ADMIN_API_KEY", "")
monkeypatch.setenv("ALLOW_UNAUTHENTICATED_ADMIN", "true")
stock_main.verify_admin(x_admin_key=None) # 예외 없으면 통과
def test_verify_admin_rejects_wrong_key(monkeypatch):
"""ADMIN_API_KEY 설정 + 잘못된 키 → 401 (regression)."""
monkeypatch.setattr(stock_main, "ADMIN_API_KEY", "secret123")
with pytest.raises(HTTPException) as exc_info:
stock_main.verify_admin(x_admin_key="wrong")
assert exc_info.value.status_code == 401
def test_verify_admin_allows_correct_key(monkeypatch):
"""ADMIN_API_KEY 설정 + 올바른 키 → 통과 (regression)."""
monkeypatch.setattr(stock_main, "ADMIN_API_KEY", "secret123")
stock_main.verify_admin(x_admin_key="secret123") # 예외 없으면 통과