From 9093611c9629c0de3db760878ec9929f50add5ed Mon Sep 17 00:00:00 2001 From: hyungi Date: Fri, 25 Jul 2025 07:02:39 +0900 Subject: [PATCH] =?UTF-8?q?feat(security):=20=EA=B4=80=EB=A6=AC=EC=9E=90?= =?UTF-8?q?=20API=EC=97=90=20API=20=ED=82=A4=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 민감한 API(모델 재시작, 캐시 정리)를 보호하기 위해 API 키 기반 인증 추가 - 모듈을 생성하여 API 키 검증 로직 중앙화 - 에 API 키를 설정하여 관리 용이성 확보 - FastAPI의 를 사용하여 엔드포인트에 보안 규칙 적용 - CODING_CONVENTIONS.md에 API 키 사용법 및 cURL 예제 문서화 --- CODING_CONVENTIONS.md | 14 +++++++ config/settings.json | 3 ++ logs/service.log | 2 + src/fastapi_with_dashboard.py | 11 +++--- src/security.py | 73 +++++++++++++++++++++++++++++++++++ 5 files changed, 98 insertions(+), 5 deletions(-) create mode 100644 src/security.py diff --git a/CODING_CONVENTIONS.md b/CODING_CONVENTIONS.md index cb5ed35..530969d 100644 --- a/CODING_CONVENTIONS.md +++ b/CODING_CONVENTIONS.md @@ -67,6 +67,20 @@ - **서버 포트**: `8080` (FastAPI 웹 서비스) - **대시보드 접속**: `http://192.168.1.122:8080/dashboard` +### 6.1. API 키 인증 + +관리자용 API(`/api/restart-models`, `/api/clear-cache` 등)를 호출하려면 HTTP 요청 헤더에 API 키를 포함해야 합니다. + +- **헤더 이름**: `X-API-KEY` +- **API 키 값**: `config/settings.json` 파일의 `security.api_key` 값을 사용합니다. + +**cURL 예시:** + +```bash +curl -X POST http://192.168.1.122:20080/api/restart-models \ +-H "X-API-KEY: nllb-secret-key-!@#$%" +``` + ## 7. 서버 운영 및 배포 (macOS) 이 서버는 24/7 무중단 운영을 위해 macOS의 `launchd` 서비스를 통해 관리됩니다. 이를 통해 시스템 재부팅 시 자동 시작 및 예기치 않은 종료 시 자동 재시작이 보장됩니다. diff --git a/config/settings.json b/config/settings.json index 85709e2..e098750 100644 --- a/config/settings.json +++ b/config/settings.json @@ -38,5 +38,8 @@ "static_hosting": "static-hosting", "metadata": "metadata", "local_work_path": "~/Scripts/nllb-translation-system" + }, + "security": { + "api_key": "nllb-secret-key-!@#$%" } } diff --git a/logs/service.log b/logs/service.log index e41e1aa..b66119b 100644 --- a/logs/service.log +++ b/logs/service.log @@ -7,3 +7,5 @@ INFO: 192.168.1.122:51436 - "GET /api/dashboard HTTP/1.1" 200 OK KoBART 모델 로드 완료 모델 로딩 완료! INFO: 192.168.1.122:51443 - "GET /api/dashboard HTTP/1.1" 200 OK +INFO: 192.168.1.122:51518 - "GET /api/dashboard HTTP/1.1" 200 OK +INFO: 192.168.1.122:51563 - "GET /api/dashboard HTTP/1.1" 200 OK diff --git a/src/fastapi_with_dashboard.py b/src/fastapi_with_dashboard.py index 8dff7b4..0aa2572 100644 --- a/src/fastapi_with_dashboard.py +++ b/src/fastapi_with_dashboard.py @@ -5,7 +5,7 @@ Mac Mini FastAPI + DS1525+ 연동 """ from contextlib import asynccontextmanager -from fastapi import FastAPI, File, UploadFile, HTTPException, BackgroundTasks, Request +from fastapi import FastAPI, File, UploadFile, HTTPException, BackgroundTasks, Request, Depends from fastapi.templating import Jinja2Templates from fastapi.staticfiles import StaticFiles from fastapi.responses import HTMLResponse, JSONResponse @@ -23,6 +23,7 @@ import logging # 중앙 설정 로더 및 AI 서비스 임포트 from config_loader import settings from background_ai_service import ai_service +from security import get_api_key # 로깅 설정 logging.basicConfig( @@ -334,8 +335,8 @@ async def get_dashboard_data(): return ai_service.get_dashboard_data() @app.post("/api/restart-models") -async def restart_models(): - """AI 모델 재시작 API""" +async def restart_models(api_key: str = Depends(get_api_key)): + """AI 모델 재시작 API (보안)""" try: await ai_service.restart_models() return {"message": "AI 모델 재시작이 완료되었습니다."} @@ -343,8 +344,8 @@ async def restart_models(): raise HTTPException(status_code=500, detail=f"모델 재시작 실패: {str(e)}") @app.post("/api/clear-cache") -async def clear_cache(): - """시스템 캐시 정리 API""" +async def clear_cache(api_key: str = Depends(get_api_key)): + """시스템 캐시 정리 API (보안)""" try: # 여기서 실제 캐시 정리 로직 구현 # 예: 임시 파일 삭제, 메모리 정리 등 diff --git a/src/security.py b/src/security.py new file mode 100644 index 0000000..86779ab --- /dev/null +++ b/src/security.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +""" +API 보안 및 인증 모듈 +API 키 기반의 인증을 처리합니다. +""" + +from fastapi import Security, HTTPException +from fastapi.security import APIKeyHeader +from starlette import status + +from config_loader import settings + +# 설정에서 API 키 가져오기 +API_KEY = settings.get_section("security").get("api_key") +API_KEY_NAME = "X-API-KEY" + +# API 키 헤더 정의 +api_key_header = APIKeyHeader(name=API_KEY_NAME, auto_error=False) + +async def get_api_key(header_value: str = Security(api_key_header)): + """ + 요청 헤더에서 API 키를 추출하고 유효성을 검증합니다. + 유효하지 않은 경우, HTTPException을 발생시킵니다. + """ + if not header_value: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="API 키가 필요합니다." + ) + if header_value != API_KEY: + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="제공된 API 키가 유효하지 않습니다." + ) + return header_value + +if __name__ == "__main__": + # 보안 모듈 테스트 + print("✅ 보안 모듈 테스트") + print("-" * 30) + + print(f" - 설정된 API 키 이름: {API_KEY_NAME}") + print(f" - 설정된 API 키 값: {API_KEY}") + + # 예제: get_api_key 함수 테스트 (비동기 함수라 직접 실행은 어려움) + async def test_key_validation(): + print("\n[테스트 시나리오]") + + # 1. 유효한 키 + try: + await get_api_key(API_KEY) + print(" - 유효한 키 검증: 성공 ✅") + except HTTPException as e: + print(f" - 유효한 키 검증: 실패 ❌ ({e.detail})") + + # 2. 유효하지 않은 키 + try: + await get_api_key("invalid-key") + print(" - 유효하지 않은 키 검증: 성공 ✅") + except HTTPException as e: + print(f" - 유효하지 않은 키 검증: 실패 ❌ ({e.detail})") + + # 3. 키 없음 + try: + await get_api_key(None) + print(" - 키 없음 검증: 성공 ✅") + except HTTPException as e: + print(f" - 키 없음 검증: 실패 ❌ ({e.detail})") + + import asyncio + asyncio.run(test_key_validation()) + + print("-" * 30) \ No newline at end of file