Bläddra i källkod

fix: 阻止 Docker 使用空生产密钥启动

wangkangyjy 2 dagar sedan
förälder
incheckning
3b40eca879

+ 3 - 3
compose.yaml

@@ -14,9 +14,9 @@ services:
       SPRING_DATASOURCE_URL: jdbc:sqlite:/app/data/okr-performance.db
       APP_DATA_DIR: /app/data
       APP_BACKUP_DIR: /app/data/backups
-      APP_JWT_SECRET: ${APP_JWT_SECRET}
-      APP_SUPER_ADMIN_PASSWORD: ${APP_SUPER_ADMIN_PASSWORD}
-      APP_CORS_ORIGINS: ${APP_CORS_ORIGINS}
+      APP_JWT_SECRET: ${APP_JWT_SECRET:?请先复制 .env.example 为 .env,并配置至少 32 字符的 APP_JWT_SECRET}
+      APP_SUPER_ADMIN_PASSWORD: ${APP_SUPER_ADMIN_PASSWORD:?请在 .env 中配置至少 12 字符的 APP_SUPER_ADMIN_PASSWORD}
+      APP_CORS_ORIGINS: ${APP_CORS_ORIGINS:?请在 .env 中配置实际访问域名 APP_CORS_ORIGINS}
       TZ: Asia/Shanghai
     volumes:
       - ./data:/app/data

+ 12 - 0
docker/entrypoint.sh

@@ -6,6 +6,18 @@ BACKUP_DIR=${APP_BACKUP_DIR:-$DATA_DIR/backups}
 DATABASE_FILE=${APP_DATABASE_FILE:-$DATA_DIR/okr-performance.db}
 
 umask 027
+
+case ",${SPRING_PROFILES_ACTIVE:-}," in
+  *,prod,*)
+    JWT_SECRET=${APP_JWT_SECRET:-}
+    if [ "${#JWT_SECRET}" -lt 32 ]; then
+      echo "错误:生产环境 APP_JWT_SECRET 长度必须至少 32 字符。" >&2
+      echo "请复制 .env.example 为 .env,设置随机密钥后重新启动。" >&2
+      exit 1
+    fi
+    ;;
+esac
+
 mkdir -p "$DATA_DIR" "$BACKUP_DIR"
 
 WRITE_PROBE="$DATA_DIR/.write-probe-$$"

+ 13 - 0
docs/docker-deployment.md

@@ -62,3 +62,16 @@ docker compose ps
 docker compose logs --tail=200 okr-performance
 sqlite3 data/okr-performance.db "PRAGMA integrity_check;"
 ```
+
+## 启动时报 JWT `WeakKeyException`
+
+这表示容器收到的 `APP_JWT_SECRET` 为空或不足 32 字符,和 SQLite 数据无关。先确认项目根目录存在 `.env`:
+
+```bash
+cp .env.example .env
+# 将全部 CHANGE_ME 替换为真实值
+docker compose config
+docker compose up -d --build
+```
+
+新版 Compose 配置会在 `.env` 缺失或关键变量为空时直接终止,并指出需要配置的变量;不会再把空密钥传给 Spring Boot 后才抛出深层 JWT 异常。

+ 4 - 0
scripts/test-docker-config.sh

@@ -34,9 +34,13 @@ assert_contains compose.yaml "./data:/app/data"
 assert_contains compose.yaml 'image: emoon-okr:${APP_IMAGE_TAG:-latest}'
 assert_contains compose.yaml "SPRING_DATASOURCE_URL: jdbc:sqlite:/app/data/okr-performance.db"
 assert_contains compose.yaml "APP_BACKUP_DIR: /app/data/backups"
+assert_contains compose.yaml 'APP_JWT_SECRET: ${APP_JWT_SECRET:?'
+assert_contains compose.yaml 'APP_SUPER_ADMIN_PASSWORD: ${APP_SUPER_ADMIN_PASSWORD:?'
+assert_contains compose.yaml 'APP_CORS_ORIGINS: ${APP_CORS_ORIGINS:?'
 
 assert_contains docker/entrypoint.sh 'mkdir -p "$DATA_DIR" "$BACKUP_DIR"'
 assert_contains docker/entrypoint.sh '数据库目录不可写'
+assert_contains docker/entrypoint.sh 'APP_JWT_SECRET 长度必须至少 32 字符'
 
 assert_contains .dockerignore "data/"
 assert_contains .dockerignore "**/target/"

+ 51 - 0
scripts/test-docker-env-validation.sh

@@ -0,0 +1,51 @@
+#!/bin/sh
+set -eu
+
+ROOT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
+cd "$ROOT_DIR"
+
+EMPTY_ENV=$(mktemp "${TMPDIR:-/tmp}/okr-empty-env.XXXXXX")
+ENTRYPOINT_DATA=$(mktemp -d "${TMPDIR:-/tmp}/okr-entrypoint-data.XXXXXX")
+trap 'rm -f "$EMPTY_ENV"; rm -rf "$ENTRYPOINT_DATA"' EXIT INT TERM
+
+assert_compose_requires() {
+  missing_name=$1
+  shift
+  output_file="/tmp/okr-compose-missing-${missing_name}.out"
+
+  if env -u "$missing_name" "$@" docker compose --env-file "$EMPTY_ENV" config >"$output_file" 2>&1; then
+    echo "缺少 $missing_name 时,Compose 不应生成配置" >&2
+    exit 1
+  fi
+  if ! grep -q "$missing_name" "$output_file"; then
+    echo "Compose 缺失变量错误未指出 $missing_name" >&2
+    exit 1
+  fi
+}
+
+assert_compose_requires APP_JWT_SECRET \
+  APP_SUPER_ADMIN_PASSWORD=verification-password APP_CORS_ORIGINS=https://okr.example.com
+assert_compose_requires APP_SUPER_ADMIN_PASSWORD \
+  APP_JWT_SECRET=verification-secret-key-at-least-32-characters APP_CORS_ORIGINS=https://okr.example.com
+assert_compose_requires APP_CORS_ORIGINS \
+  APP_JWT_SECRET=verification-secret-key-at-least-32-characters APP_SUPER_ADMIN_PASSWORD=verification-password
+
+if SPRING_PROFILES_ACTIVE=prod APP_JWT_SECRET=short \
+  APP_DATA_DIR="$ENTRYPOINT_DATA" \
+  APP_BACKUP_DIR= docker/entrypoint.sh true >/tmp/okr-entrypoint-weak-secret.out 2>&1; then
+  echo "JWT 密钥不足 32 字符时,容器入口不应继续启动" >&2
+  exit 1
+fi
+
+if ! grep -q "至少 32" /tmp/okr-entrypoint-weak-secret.out; then
+  echo "容器入口未给出明确的 JWT 密钥长度提示" >&2
+  exit 1
+fi
+
+SPRING_PROFILES_ACTIVE=prod \
+  APP_JWT_SECRET=verification-secret-key-at-least-32-characters \
+  APP_DATA_DIR="$ENTRYPOINT_DATA" \
+  APP_BACKUP_DIR="$ENTRYPOINT_DATA/backups" \
+  docker/entrypoint.sh true >/tmp/okr-entrypoint-valid-secret.out 2>&1
+
+echo "Docker 生产环境变量校验通过"