feat(auth): support per-deploy login password key override
Deployment Config CI / Deployment Config (push) Successful in 27s
Backend CI / Backend (push) Successful in 15m4s

Multi-replica deployments need every Pod/container to share one RSA private key, otherwise public keys handed to the browser may not match the key that decrypts the resulting ciphertext. Introduce a `*.local.yaml` override layer that ops-config and tenant-config both load on top of the base config, ship example overrides plus compose/k3s wiring, and tolerate PEM bodies that arrive folded or escaped from Secrets.
This commit is contained in:
2026-05-14 11:54:40 +08:00
parent 2bc192b3c7
commit b939cfa2fa
16 changed files with 310 additions and 10 deletions
+12
View File
@@ -40,3 +40,15 @@ Default ports are NAS-friendly:
- MinIO console: `19001`
Edit `.env` before rerunning `bash deploy.sh` if you need real API keys, stronger secrets, or different ports.
For multi-container / multi-Pod login password encryption, put the same RSA private key in every replica's config override:
- tenant API: copy `config.local.yaml.example` to `config.local.yaml`, then fill `auth.password_cipher.private_key_pem`
- ops API: copy `ops-config.local.yaml.example` to `ops-config.local.yaml`, then fill `auth.password_cipher.private_key_pem`
Generate keys with:
```bash
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out login-password-rsa.pem
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out ops-login-password-rsa.pem
```
+11
View File
@@ -0,0 +1,11 @@
auth:
password_cipher:
enabled: true
key_id: login-password-rsa-oaep-v1
# 多 Pod / 多容器部署必须使用同一把私钥。复制本文件为 config.local.yaml
# 再粘贴完整 PEM;不要提交生产私钥。留空时应用会生成单进程临时私钥,
# 只适合本机单副本试跑,不适合多副本生产。
#
# 生成示例:
# openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out login-password-rsa.pem
private_key_pem: ""
+7
View File
@@ -169,6 +169,13 @@ jwt:
access_ttl: 15m
refresh_ttl: 720h
auth:
password_cipher:
# 生产多实例请在 config.local.yaml 中配置同一把 private_key_pem。
enabled: true
key_id: login-password-rsa-oaep-v1
private_key_pem: ""
log:
level: info,warn,error
format: json
+3
View File
@@ -22,6 +22,7 @@ x-app-env: &app-env
x-app-volumes: &app-volumes
- ./config.yaml:/app/configs/config.yaml:ro
- ./config.local.yaml:/app/configs/config.local.yaml:ro
- ./prompts.yml:/app/configs/prompts.yml:ro
x-app-depends: &app-depends
@@ -226,6 +227,7 @@ services:
CONFIG_PATH: /app/configs/config.yaml
volumes:
- ./config.yaml:/app/configs/config.yaml:ro
- ./config.local.yaml:/app/configs/config.local.yaml:ro
- ./prompts.yml:/app/configs/prompts.yml:ro
entrypoint: >
/bin/sh -c "
@@ -355,6 +357,7 @@ services:
OPS_DEFAULT_ADMIN_EMAIL: ${OPS_DEFAULT_ADMIN_EMAIL:-}
volumes:
- ./ops-config.yaml:/app/configs/ops-config.yaml:ro
- ./ops-config.local.yaml:/app/configs/ops-config.local.yaml:ro
depends_on:
pgbouncer:
condition: service_healthy
+11
View File
@@ -36,11 +36,22 @@ sudo k3s ctr images import images.tar
- `SCHEDULER_INTERNAL_METRICS_TOKEN`
- `BROWSER_FETCH_TOKEN`
- `POSTGRES_PASSWORD` / `RABBITMQ_DEFAULT_PASS` / `MINIO_ROOT_*`
- `config.local.yaml` 里的 `auth.password_cipher.private_key_pem`
- `ops-config.local.yaml` 里的 `auth.password_cipher.private_key_pem`
- `config/config.yaml` 里的 LLM / SiliconFlow API key
- `config.local.yaml` 里对应的数据库、RabbitMQ、MinIO、JWT 配置
注意:应用运行时通过 `pgbouncer` / `monitoring-pgbouncer` 访问数据库;`migrate` 任务仍然直连 `postgres` / `monitoring-postgres`。数据库密码统一来自 `POSTGRES_PASSWORD` secret,应用配置和 PgBouncer 都会跟随这个 secret。
登录密码加密使用 RSA-OAEP-SHA256。多 Pod 部署时,同一类服务的所有副本必须读取同一把私钥和同一个 `key_id`,否则某个 Pod 下发的公钥对应密文可能被另一个 Pod 解不开。建议租户端和 ops 端分别生成不同私钥:
```bash
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out login-password-rsa.pem
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out ops-login-password-rsa.pem
```
然后把完整 PEM 分别粘贴到 `deploy/k3s/secrets.yaml``config.local.yaml``ops-config.local.yaml`
## 3. 部署
```bash
+5
View File
@@ -556,6 +556,11 @@ spec:
items:
- key: ops-config.yaml
path: ops-config.yaml
- secret:
name: geo-rankly-app-secret
items:
- key: ops-config.local.yaml
path: ops-config.local.yaml
---
apiVersion: v1
kind: Service
+62 -2
View File
@@ -1,6 +1,11 @@
server:
port: 8080
mode: release
# 生产环境请配置为真实前端域名,例如 https://app.example.com。
# 留空时沿用开发环境 CORS 放开策略。
allowed_origins: []
security_headers:
enabled: true
trusted_proxies:
- 127.0.0.1
- ::1
@@ -30,6 +35,47 @@ monitoring_database:
rabbitmq:
url: amqp://geo:geo_dev@rabbitmq:5672/geo
monitor_result_exchange: monitor.result
monitor_result_routing_key: monitor.result.ingest
monitor_result_queue: monitor.result.ingest
monitor_result_dlx: monitor.result.dlx
monitor_result_dlq: monitor.result.ingest.dlq
monitor_result_dlq_routing_key: monitor.result.ingest.dlq
monitor_projection_exchange: monitor.projection
monitor_projection_routing_key: monitor.projection.rebuild
monitor_projection_queue: monitor.projection.rebuild
monitor_projection_dlx: monitor.projection.dlx
monitor_projection_dlq: monitor.projection.rebuild.dlq
monitor_projection_dlq_routing_key: monitor.projection.rebuild.dlq
generation_exchange: generation.task
generation_routing_key: generation.task.run
generation_queue: generation.task.run
generation_dlx: generation.task.dlx
generation_dlq: generation.task.run.dlq
generation_dlq_routing_key: generation.task.run.dlq
generation_event_exchange: generation.event
desktop_task_event_exchange: desktop.task.event
desktop_dispatch_exchange: desktop.task.dispatch
desktop_dispatch_publish_prefix: publish
desktop_dispatch_monitor_prefix: monitor
desktop_account_health_exchange: desktop.account.health
desktop_account_health_routing_key: desktop.account.health.report
desktop_account_health_queue: desktop.account.health.report
desktop_account_health_dlx: desktop.account.health.dlx
desktop_account_health_dlq: desktop.account.health.report.dlq
desktop_account_health_dlq_routing_key: desktop.account.health.report.dlq
template_assist_exchange: template.assist
template_assist_routing_key: template.assist.run
template_assist_queue: template.assist.run
template_assist_dlx: template.assist.dlx
template_assist_dlq: template.assist.run.dlq
template_assist_dlq_routing_key: template.assist.run.dlq
kol_assist_exchange: kol.assist
kol_assist_routing_key: kol.assist.run
kol_assist_queue: kol.assist.run
kol_assist_dlx: kol.assist.dlx
kol_assist_dlq: kol.assist.run.dlq
kol_assist_dlq_routing_key: kol.assist.run.dlq
compliance_review_exchange: compliance.review
compliance_review_routing_key: compliance.review.run
compliance_review_queue: compliance.review.run
@@ -53,6 +99,9 @@ monitoring_workers:
result_ingest_concurrency: 4
projection_rebuild_concurrency: 2
monitoring_dispatch:
desktop_tasks_rollout_percentage: 5
membership:
default_plan_code: free
plans:
@@ -96,7 +145,6 @@ brand_library:
max_keywords: 5
max_questions_per_keyword: 5
redis:
addr: redis:6379
db: 0
@@ -126,6 +174,16 @@ jwt:
access_ttl: 15m
refresh_ttl: 720h
auth:
login_guard:
enabled: true
password_cipher:
# 留空 private_key_pem 时会在单进程内生成临时密钥;生产多 Pod 请在
# geo-rankly-app-secret/config.local.yaml 中配置同一把私钥。
enabled: true
key_id: login-password-rsa-oaep-v1
private_key_pem: ""
log:
level: info,warn,error
format: json
@@ -182,14 +240,16 @@ browser_fetch:
enabled: true
provider: lightpanda
service_url: http://browser-fetch:8082
token: dev-browser-fetch-token
queue_size: 64
worker_concurrency: 2
worker_concurrency: 3
request_timeout: 45s
cache_ttl: 6h
allowed_domains:
- zhuanlan.zhihu.com
- zhihu.com
- baidu.com
- weixin.sogou.com
trigger_status:
- 403
- 429
+13
View File
@@ -1,6 +1,11 @@
server:
port: 8090
mode: release
# 生产环境请配置为真实运维后台域名,例如 https://ops.example.com。
# 留空时沿用开发环境 CORS 放开策略。
allowed_origins: []
security_headers:
enabled: true
trusted_proxies:
- 127.0.0.1
- ::1
@@ -43,6 +48,14 @@ jwt:
secret: "geo-ops-15645415841"
access_ttl: 8h
auth:
password_cipher:
# 留空 private_key_pem 时会在单进程内生成临时密钥;生产多 Pod 请在
# geo-rankly-app-secret/ops-config.local.yaml 中配置同一把私钥。
enabled: true
key_id: ops-password-rsa-oaep-v1
private_key_pem: ""
default_admin:
username: admin
display_name: system-admin
+22
View File
@@ -35,3 +35,25 @@ stringData:
browser_fetch:
token: ${BROWSER_FETCH_TOKEN:geo-browser-fetch-change-me}
auth:
password_cipher:
enabled: true
key_id: login-password-rsa-oaep-v1
# 多 Pod 必须使用同一把私钥;上线前用 `openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048`
# 生成后粘贴完整 PEM。不要把生产私钥提交到代码仓库。
private_key_pem: |
-----BEGIN PRIVATE KEY-----
REPLACE_WITH_TENANT_LOGIN_PASSWORD_RSA_PRIVATE_KEY
-----END PRIVATE KEY-----
ops-config.local.yaml: |
auth:
password_cipher:
enabled: true
key_id: ops-password-rsa-oaep-v1
# 建议与租户端使用不同私钥;所有 ops-api Pod 必须挂载同一份 Secret。
private_key_pem: |
-----BEGIN PRIVATE KEY-----
REPLACE_WITH_OPS_LOGIN_PASSWORD_RSA_PRIVATE_KEY
-----END PRIVATE KEY-----
+8
View File
@@ -0,0 +1,8 @@
auth:
password_cipher:
enabled: true
key_id: ops-password-rsa-oaep-v1
# 建议与租户端使用不同私钥。复制本文件为 ops-config.local.yaml
# 所有 ops-api 副本必须读取同一份私钥;不要提交生产私钥。
# 留空时应用会生成单进程临时私钥,只适合本机单副本试跑。
private_key_pem: ""
+7
View File
@@ -43,6 +43,13 @@ jwt:
secret: "geo-ops-15645415841" # 必须通过 OPS_JWT_SECRET 环境变量提供
access_ttl: 8h
auth:
password_cipher:
# 生产多实例请在 ops-config.local.yaml 中配置同一把 private_key_pem。
enabled: true
key_id: ops-password-rsa-oaep-v1
private_key_pem: ""
default_admin:
username: admin
display_name: 系统管理员
+15
View File
@@ -25,6 +25,18 @@ if [[ ! -f "${SCRIPT_DIR}/.env" ]]; then
echo " 如需填写真实 API Key 或修改端口,可编辑 ${SCRIPT_DIR}/.env 后再次执行 bash deploy.sh"
fi
ensure_local_config() {
local file="$1" example="$2"
if [[ -f "${SCRIPT_DIR}/${file}" ]]; then
return
fi
if [[ -f "${SCRIPT_DIR}/${example}" ]]; then
cp "${SCRIPT_DIR}/${example}" "${SCRIPT_DIR}/${file}"
echo "==> 已从 ${example} 创建 ${file}"
echo " 生产多副本部署前请把其中的 password_cipher.private_key_pem 替换为同一把 RSA 私钥。"
fi
}
env_value() {
local key="$1" default_value="$2" value
value="$(grep -E "^${key}=" "${SCRIPT_DIR}/.env" | tail -1 | cut -d= -f2- || true)"
@@ -34,6 +46,9 @@ env_value() {
}
deploy_compose() {
ensure_local_config "config.local.yaml" "config.local.yaml.example"
ensure_local_config "ops-config.local.yaml" "ops-config.local.yaml.example"
# ─── Step 1: 加载镜像 ───────────────────────────────────────────────────────
echo "==> [1/3] 加载 Docker 镜像(可能需要几分钟)..."
docker load -i "${SCRIPT_DIR}/images.tar"