보안 강화: CORS 제한, Path Traversal 방어, 헬스체크 추가

- travel-proxy: get_thumb NameError 수정 및 경로 조작 방어
- stock-lab, music-lab: CORS allow_origins=* → 환경변수 기반 도메인 제한
- travel-proxy, deployer: /health 엔드포인트 추가
- 전 서비스 .dockerignore 추가 (.git, __pycache__, .env 제외)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-03 01:08:39 +09:00
parent c9737b380f
commit bc9ba3901e
9 changed files with 57 additions and 10 deletions

6
backend/.dockerignore Normal file
View File

@@ -0,0 +1,6 @@
.git
__pycache__
*.pyc
.env
.env.*
*.md

6
deployer/.dockerignore Normal file
View File

@@ -0,0 +1,6 @@
.git
__pycache__
*.pyc
.env
.env.*
*.md

View File

@@ -32,6 +32,10 @@ def run_deploy_script():
except Exception as e: except Exception as e:
logger.exception(f"Exception during deployment: {e}") logger.exception(f"Exception during deployment: {e}")
@app.get("/health")
def health():
return {"status": "healthy", "service": "deployer"}
@app.post("/webhook") @app.post("/webhook")
async def webhook(req: Request, background_tasks: BackgroundTasks): async def webhook(req: Request, background_tasks: BackgroundTasks):
body = await req.body() body = await req.body()

6
music-lab/.dockerignore Normal file
View File

@@ -0,0 +1,6 @@
.git
__pycache__
*.pyc
.env
.env.*
*.md

View File

@@ -16,11 +16,13 @@ from .db import (
app = FastAPI() app = FastAPI()
_cors_origins = os.getenv("CORS_ALLOW_ORIGINS", "http://localhost:3007,http://localhost:8080").split(",")
app.add_middleware( app.add_middleware(
CORSMiddleware, CORSMiddleware,
allow_origins=["*"], allow_origins=[o.strip() for o in _cors_origins],
allow_methods=["*"], allow_credentials=False,
allow_headers=["*"], allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers=["Content-Type"],
) )
MUSIC_AI_SERVER_URL = os.getenv("MUSIC_AI_SERVER_URL", "") MUSIC_AI_SERVER_URL = os.getenv("MUSIC_AI_SERVER_URL", "")

6
stock-lab/.dockerignore Normal file
View File

@@ -0,0 +1,6 @@
.git
__pycache__
*.pyc
.env
.env.*
*.md

View File

@@ -23,12 +23,13 @@ from .price_fetcher import get_current_prices
app = FastAPI() app = FastAPI()
# CORS 설정 (프론트엔드 접근 허용) # CORS 설정 (프론트엔드 접근 허용)
_cors_origins = os.getenv("CORS_ALLOW_ORIGINS", "http://localhost:3007,http://localhost:8080").split(",")
app.add_middleware( app.add_middleware(
CORSMiddleware, CORSMiddleware,
allow_origins=["*"], # 운영 시에는 구체적인 도메인으로 제한하는 것이 좋음 allow_origins=[o.strip() for o in _cors_origins],
allow_credentials=True, allow_credentials=False,
allow_methods=["*"], allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers=["*"], allow_headers=["Content-Type"],
) )
scheduler = BackgroundScheduler(timezone=os.getenv("TZ", "Asia/Seoul")) scheduler = BackgroundScheduler(timezone=os.getenv("TZ", "Asia/Seoul"))

View File

@@ -0,0 +1,6 @@
.git
__pycache__
*.pyc
.env
.env.*
*.md

View File

@@ -168,6 +168,10 @@ def scan_album(album: str) -> List[Dict[str, Any]]:
# ----------------------------- # -----------------------------
# Routes # Routes
# ----------------------------- # -----------------------------
@app.get("/health")
def health():
return {"status": "healthy", "service": "travel-proxy"}
@app.get("/api/travel/regions") @app.get("/api/travel/regions")
def regions(): def regions():
_meta_changed_invalidate_cache() _meta_changed_invalidate_cache()
@@ -239,12 +243,18 @@ def photos(
@app.get("/media/travel/.thumb/{album}/{filename}") @app.get("/media/travel/.thumb/{album}/{filename}")
def get_thumb(album: str, filename: str): def get_thumb(album: str, filename: str):
if ".." in album or ".." in filename:
raise HTTPException(400, "Invalid path")
src = (ROOT / album / filename).resolve() src = (ROOT / album / filename).resolve()
if not str(src).startswith(str(ROOT)):
raise HTTPException(403, "Access denied")
if not src.exists() or not src.is_file():
raise HTTPException(404, "Source not found")
p = ensure_thumb(src, album)
if not p.exists() or not p.is_file(): if not p.exists() or not p.is_file():
raise HTTPException(404, "Thumbnail not found") raise HTTPException(404, "Thumbnail not found")
# src로부터 thumb 생성/확인 (원본 확장자 유지)
p = ensure_thumb(src, album)
return FileResponse(str(p)) return FileResponse(str(p))
@app.get("/api/version") @app.get("/api/version")