Bach 운영 컨테이너 보안을 강화한다
백엔드는 내부 네트워크에만 두고 Docker 비밀 파일의 API 키를 프록시에서 주입한다. 프론트엔드는 루프백 포트 18102만 사용한다.
This commit is contained in:
@@ -19,6 +19,7 @@ __pycache__/
|
||||
|
||||
# ── Environment / secrets ─────────────────────────────────────────────────────
|
||||
.env
|
||||
/secrets/
|
||||
.env.local
|
||||
.env.*.local
|
||||
.env.production
|
||||
|
||||
+18
-2
@@ -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."
|
||||
|
||||
+16
-5
@@ -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
|
||||
|
||||
@@ -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;"]
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user