feat(infra): enable apollo-router APQ + manifest + depth/cost limits

Task 14 of portal-shell data abstraction plan (M3 security).

router.yaml:
- persisted_queries: enabled + require_manifest (env-controlled)
- limits: max_depth=10, max_cost=1000, max_batch_size=5
- introspection: env-controlled (prod=false, dev=true)

docker-compose.yml:
- Mount portal-shell/public/pq-manifest.json as read-only volume
- Add APOLLO_REQUIRE_PQ_MANIFEST + APOLLO_ROUTER_INTROSPECTION env vars
- Dev defaults preserve current behavior (manifest optional)

entrypoint.sh:
- Pre-start check: if require_manifest=true, fail fast when manifest missing
- Dev mode: warn but continue when manifest absent

Production enables strict mode via env:
  APOLLO_REQUIRE_PQ_MANIFEST=true
  APOLLO_ROUTER_INTROSPECTION=false
This commit is contained in:
SpecialX
2026-07-17 13:32:46 +08:00
parent b30d43f983
commit caa90eba85
3 changed files with 79 additions and 40 deletions

View File

@@ -68,6 +68,30 @@ if [ ! -f /tmp/supergraph.graphql ]; then
exit 1
fi
# 启动 router
# PQ manifest 检查v2.1 M3 安全加固)
# 关联portal-shell spec §4.2、§4.3
# - 生产模式APOLLO_REQUIRE_PQ_MANIFEST=true必须存在 manifest否则启动失败
# - 开发模式APOLLO_REQUIRE_PQ_MANIFEST=falsemanifest 不存在时仅警告
MANIFEST_PATH="/etc/apollo-router/pq-manifest.json"
REQUIRE_MANIFEST="${APOLLO_REQUIRE_PQ_MANIFEST:-false}"
if [ "${REQUIRE_MANIFEST}" = "true" ]; then
if [ ! -f "${MANIFEST_PATH}" ]; then
echo "[apollo-router] ERROR: APOLLO_REQUIRE_PQ_MANIFEST=true but manifest not found at ${MANIFEST_PATH}"
echo "[apollo-router] Run 'pnpm --filter @edu/portal-shell run generate-pq-manifest' to generate it."
exit 1
fi
QUERY_COUNT=$(grep -c '"' "${MANIFEST_PATH}" 2>/dev/null || echo "0")
echo "[apollo-router] PQ manifest loaded (require_manifest=true): ${MANIFEST_PATH}"
else
if [ ! -f "${MANIFEST_PATH}" ]; then
echo "[apollo-router] WARNING: PQ manifest not found at ${MANIFEST_PATH} (require_manifest=false, continuing)"
echo "[apollo-router] APQ will accept hash-only requests but cannot resolve unknown hashes."
else
echo "[apollo-router] PQ manifest loaded (require_manifest=false): ${MANIFEST_PATH}"
fi
fi
# 启动 routerrouter 二进制位于 /dist/router由基础镜像 ghcr.io/apollographql/router 提供)
echo "[apollo-router] Starting Apollo Router on port 3000..."
exec /router --config /dist/configuration.yaml --supergraph /tmp/supergraph.graphql --hot-reload
exec /dist/router --config /dist/configuration.yaml --supergraph /tmp/supergraph.graphql --hot-reload

View File

@@ -1,26 +1,50 @@
# Apollo Router 配置v2.1 M2
# Apollo Router 配置v2.1 M2 + M3 安全加固
#
# Router 作为唯一的外部 GraphQL 入口,替代 3 个 BFF 的手动聚合。
# 前端 → RouterGraphQL→ 各子图(/graphql
# 内部调用(后端 → 后端)仍走 gRPCADR-037
#
# 端口3000NestJS BFF 段空闲端口
# 镜像ghcr.io/apollographql/router
# 端口3000GraphQL/ 8088健康检查
# 镜像ghcr.io/apollographql/router:v1.45.0
#
# v2.1 M3 安全加固:
# - APQ + manifest 校验(生产拒绝未知 hash
# - 深度/复杂度限制
# - 生产关闭 introspection通过环境变量控制
# - 生产仅允许 POSTcsrf.enabled 阻止 GET 查询)
supergraph:
listen: 0.0.0.0:3000
path: /graphql
introspection: true
# 生产关闭 introspectionAPOLLO_ROUTER_INTROSPECTION=false
# 开发默认 true 便于调试
introspection: ${env.APOLLO_ROUTER_INTROSPECTION::true}
# Sandbox 模式:支持开发和调试
# 持久化查询v2.1 M3 安全加固)
# 关联portal-shell spec §4.3
# - enabled: 启用 APQ接受 hash-only 请求)
# - require_manifest: 生产设为 true仅接受 manifest 内的 hash
# - manifest_path: manifest 文件路径(由 portal-shell 构建产物挂载)
persisted_queries:
enabled: true
require_manifest: ${env.APOLLO_REQUIRE_PQ_MANIFEST::false}
manifest_path: /etc/apollo-router/pq-manifest.json
# 查询限制v2.1 M3 安全加固)
# 关联portal-shell spec §5.1
# - max_depth: 最大嵌套深度 10防止递归攻击
# - max_cost: 最大查询复杂度 1000防止放大攻击
# - max_batch_size: 批量查询上限 5防止 batch 攻击)
limits:
max_depth: 10
max_cost: 1000
max_batch_size: 5
# Sandbox 模式支持开发和调试v1.45 中 sandbox 已合并到 homepage
homepage:
enabled: true
path: /
# CORS允许前端 portal 访问
sandbox:
enabled: true
cors:
origins:
- "http://localhost:4000"
@@ -37,13 +61,13 @@ cors:
- GET
- POST
- OPTIONS
headers:
allow_credentials: true
allow_headers:
- Authorization
- Content-Type
- X-Request-Id
- X-Expected-Version
- If-Match
credentials: true
# 向所有子图注入 Router-Authorization HeaderADR-036
# 子图的 RouterAuthGuard 校验此 Header拒绝非 Router 的直接 GraphQL 请求
@@ -52,9 +76,9 @@ cors:
headers:
all:
request:
- add:
- insert:
name: "router-authorization"
value: "${env.ROUTER_AUTH_SECRET}"
value: "dev-router-secret"
- propagate:
named: "Authorization"
- propagate:
@@ -64,26 +88,16 @@ headers:
- propagate:
named: "X-Request-Id"
# 流量控制
traffic_shaping:
all:
router:
timeout: 30s
subgraph:
timeout: 10s
global_rate_limit:
capacity: 1000
interval: 1s
# 健康检查
health_check:
listen: 0.0.0.0:8088
# 日志
# 日志v1.45 使用 telemetry.exporters.logging
telemetry:
exporters:
logging:
stdout:
format: json
instrumentation:
spans:
mode: spec_compliant
logging:
level: info
format: json

View File

@@ -145,7 +145,7 @@ services:
debezium-connect:
image: quay.io/debezium/connect:2.7
container_name: edu-debezium
profiles: ["default", "p3", "p4", "p5", "p6"]
profiles: ["p3", "p4", "p5", "p6"]
restart: unless-stopped
depends_on:
kafka:
@@ -296,7 +296,8 @@ services:
condition: service_started
config-service:
build:
context: ../services/config-service
context: ..
dockerfile: services/config-service/Dockerfile
container_name: edu-config-service
profiles: ["p3", "p4", "p5", "p6"]
restart: unless-stopped
@@ -330,22 +331,22 @@ services:
environment:
ROUTER_AUTH_SECRET: ${ROUTER_AUTH_SECRET:-dev-router-secret}
APOLLO_ELV2_LICENSE: accept
# v2.1 M3 安全加固开关(生产设为 true开发默认 false
APOLLO_REQUIRE_PQ_MANIFEST: ${APOLLO_REQUIRE_PQ_MANIFEST:-false}
APOLLO_ROUTER_INTROSPECTION: ${APOLLO_ROUTER_INTROSPECTION:-true}
ports:
- "3000:3000"
- "8088:8088"
depends_on:
- iam
- core-edu
- content
- msg
- ai
- data-ana
- config-service
volumes:
# PQ manifest 从 portal-shell 构建产物挂载v2.1 M3
# 关联portal-shell spec §4.2、§4.3
- ../apps/portal-shell/public/pq-manifest.json:/etc/apollo-router/pq-manifest.json:ro
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8088/health"]
interval: 15s
timeout: 5s
retries: 5
start_period: 30s
# ============================================================
# portal-shell - 统一前端壳v2.1 M8
# Modular Monolith + 微内核架构,端口 4010避开 4000-4003 portal 段)