From ba887cc6f9727511e908b5b13b3388c4ec90ce6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=B0=BD=ED=98=84?= Date: Sat, 3 Oct 2026 07:32:36 +0900 Subject: [PATCH] =?UTF-8?q?Bach=20=EC=9A=B4=EC=98=81=20=EC=BB=A8=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EB=84=88=20=EB=B3=B4=EC=95=88=EC=9D=84=20=EA=B0=95?= =?UTF-8?q?=ED=99=94=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 백엔드는 내부 네트워크에만 두고 Docker 비밀 파일의 API 키를 프록시에서 주입한다. 프론트엔드는 루프백 포트 18102만 사용한다. --- .gitignore | 1 + backend/main.py | 20 ++++++++++++++++-- docker-compose.yml | 21 ++++++++++++++----- frontend/Dockerfile | 2 ++ .../docker-entrypoint.d/20-backend-api-key.sh | 17 +++++++++++++++ frontend/nginx.conf | 1 + 6 files changed, 55 insertions(+), 7 deletions(-) create mode 100644 frontend/docker-entrypoint.d/20-backend-api-key.sh diff --git a/.gitignore b/.gitignore index db0d440..158185a 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ __pycache__/ # ── Environment / secrets ───────────────────────────────────────────────────── .env +/secrets/ .env.local .env.*.local .env.production diff --git a/backend/main.py b/backend/main.py index c82cbde..a92e0ea 100644 --- a/backend/main.py +++ b/backend/main.py @@ -45,9 +45,25 @@ _API_KEY_HEADER = APIKeyHeader(name="X-API-Key", auto_error=False) _auth_warned = False +def _get_backend_api_key() -> str | None: + """환경 변수 또는 Docker 비밀 파일에서 백엔드 인증 키를 읽는다.""" + direct = os.environ.get("BACKEND_API_KEY") + if direct is not None: + return direct + secret_file = os.environ.get("BACKEND_API_KEY_FILE") + if not secret_file: + return None + try: + with open(secret_file, encoding="utf-8") as handle: + return handle.read().strip() + except OSError: + logging.exception("백엔드 인증 키 파일을 읽지 못했습니다") + return "" + + def _require_api_key(key: str | None = Security(_API_KEY_HEADER)) -> None: global _auth_warned - expected = os.environ.get("BACKEND_API_KEY") + expected = _get_backend_api_key() # T1-1: only treat *absent* key (None) as "auth disabled". # An empty string means a deployment misconfiguration, not "no auth". if expected is None: @@ -72,7 +88,7 @@ def _startup_checks() -> None: """Log warnings for optional external binaries that are absent at launch.""" # M1: fail fast if auth is explicitly required but no key is configured if os.environ.get("REQUIRE_AUTH", "").strip().lower() in ("1", "true", "yes"): - if not os.environ.get("BACKEND_API_KEY"): + if not _get_backend_api_key(): raise SystemExit( "FATAL: REQUIRE_AUTH is set but BACKEND_API_KEY is empty. " "Set BACKEND_API_KEY or unset REQUIRE_AUTH." diff --git a/docker-compose.yml b/docker-compose.yml index ff4b041..f5248b2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,28 +16,39 @@ services: build: context: ./backend dockerfile: Dockerfile - container_name: bach-backend + container_name: piano-bach-backend restart: unless-stopped expose: - "8000" environment: - - ALLOWED_ORIGINS=${ALLOWED_ORIGINS:-http://localhost:5173} - - BACKEND_API_KEY=${BACKEND_API_KEY:-} + - ALLOWED_ORIGINS=${ALLOWED_ORIGINS:-https://learn.itosm.co.kr,https://learn.itosm.com} + - BACKEND_API_KEY_FILE=/run/secrets/backend_api_key + - REQUIRE_AUTH=1 - RATE_LIMIT_PER_MIN=${RATE_LIMIT_PER_MIN:-60} - MUSIC21_TIMEOUT_S=${MUSIC21_TIMEOUT_S:-60} - FINGERING_TIMEOUT_S=${FINGERING_TIMEOUT_S:-120} volumes: # Mount audiveris.jar from host (optional - PDF import is disabled if absent) - ./backend/bin:/app/bin:ro + secrets: + - backend_api_key frontend: build: # Root context so the Dockerfile can read pnpm-workspace.yaml + shared/ context: . dockerfile: frontend/Dockerfile - container_name: bach-frontend + container_name: piano-bach-frontend restart: unless-stopped ports: - - "5173:80" + - "127.0.0.1:18102:80" depends_on: - backend + environment: + - BACKEND_API_KEY_FILE=/run/secrets/backend_api_key + secrets: + - backend_api_key + +secrets: + backend_api_key: + file: ./secrets/backend_api_key diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 1a7922d..3bcd1b5 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -22,6 +22,8 @@ FROM nginx:1.27-alpine COPY --from=builder /app/frontend/dist /usr/share/nginx/html COPY frontend/nginx.conf /etc/nginx/conf.d/default.conf +COPY frontend/docker-entrypoint.d/20-backend-api-key.sh /docker-entrypoint.d/20-backend-api-key.sh +RUN chmod 755 /docker-entrypoint.d/20-backend-api-key.sh EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] diff --git a/frontend/docker-entrypoint.d/20-backend-api-key.sh b/frontend/docker-entrypoint.d/20-backend-api-key.sh new file mode 100644 index 0000000..dd3b900 --- /dev/null +++ b/frontend/docker-entrypoint.d/20-backend-api-key.sh @@ -0,0 +1,17 @@ +#!/bin/sh +set -eu + +secret_file="${BACKEND_API_KEY_FILE:-/run/secrets/backend_api_key}" +if [ ! -r "$secret_file" ]; then + echo "백엔드 인증 키 파일을 읽을 수 없습니다." >&2 + exit 1 +fi + +backend_api_key="$(cat "$secret_file")" +if [ -z "$backend_api_key" ]; then + echo "백엔드 인증 키가 비어 있습니다." >&2 + exit 1 +fi + +escaped_key="$(printf '%s' "$backend_api_key" | sed 's/[\\/&]/\\\\&/g')" +sed -i "s/__BACKEND_API_KEY__/${escaped_key}/g" /etc/nginx/conf.d/default.conf diff --git a/frontend/nginx.conf b/frontend/nginx.conf index 1d903ca..781a96c 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -34,6 +34,7 @@ server { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-API-Key "__BACKEND_API_KEY__"; # Sample / soundfont uploads can be large client_max_body_size 60M; # OMR + transcription can take a while