diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml index 3fc23a5..1af82a9 100644 --- a/deploy/docker-compose.yml +++ b/deploy/docker-compose.yml @@ -88,7 +88,7 @@ services: ORBIT_CONSOLE_QUEUE_ID: ${ORBIT_CONSOLE_QUEUE_ID:-} ORBIT_METRICS_ADDR: ${ORBIT_METRICS_ADDR:-} ports: - - "${ORBIT_HTTP_PORT:-8080}:8080" + - "${ORBIT_HTTP_BIND:-127.0.0.1}:${ORBIT_HTTP_PORT:-8080}:8080" stop_grace_period: 30s volumes: diff --git a/deploy/nginx/orbit.conf.example b/deploy/nginx/orbit.conf.example new file mode 100644 index 0000000..6e68a95 --- /dev/null +++ b/deploy/nginx/orbit.conf.example @@ -0,0 +1,122 @@ +# Orbit HTTPS/WSS 入口示例。 +# 将 orbit.example.com 和证书路径替换成正式值后,放到 Nginx 的 conf.d 目录。 +# 本文件位于 http 配置上下文中;map/limit_req_zone 不能放进 server 块。 + +map $http_upgrade $orbit_connection_upgrade { + default upgrade; + '' close; +} + +limit_req_zone $binary_remote_addr zone=orbit_console_login:10m rate=10r/m; + +upstream orbit_backend { + server 127.0.0.1:8080; + keepalive 64; +} + +server { + listen 80; + listen [::]:80; + server_name orbit.example.com; + + # ACME HTTP-01 或其他证书签发工具可使用此目录。 + location ^~ /.well-known/acme-challenge/ { + root /var/www/orbit-acme; + default_type text/plain; + } + + location / { + return 308 https://$host$request_uri; + } +} + +server { + listen 443 ssl; + listen [::]:443 ssl; + http2 on; + server_name orbit.example.com; + + ssl_certificate /etc/letsencrypt/live/orbit.example.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/orbit.example.com/privkey.pem; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_session_cache shared:orbit_tls:10m; + ssl_session_timeout 10m; + ssl_session_tickets off; + + client_max_body_size 1m; + proxy_connect_timeout 5s; + proxy_send_timeout 60s; + + add_header Strict-Transport-Security "max-age=31536000" always; + add_header X-Content-Type-Options "nosniff" always; + add_header Referrer-Policy "no-referrer" always; + + # 长连接只用于 Worker 和结果订阅;心跳由协议负责,代理不能自行关闭空闲会话。 + location = /v1/worker/connect { + proxy_pass http://orbit_backend; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $orbit_connection_upgrade; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 1h; + proxy_buffering off; + } + + location = /v1/events/connect { + proxy_pass http://orbit_backend; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $orbit_connection_upgrade; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 1h; + proxy_buffering off; + } + + # 控制台登录额外限制来源 IP 的尝试速率;业务任务速率仍由 Orbit 的持久化配额和授权决定。 + location = /console/auth/login { + limit_req zone=orbit_console_login burst=5 nodelay; + proxy_pass http://orbit_backend; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 30s; + } + + location = /health/live { + access_log off; + proxy_pass http://orbit_backend; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 5s; + } + + location = /health/ready { + access_log off; + proxy_pass http://orbit_backend; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 5s; + } + + location / { + proxy_pass http://orbit_backend; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_read_timeout 60s; + } +} diff --git a/docs/08-development-plan.md b/docs/08-development-plan.md index d8313ee..122df41 100644 --- a/docs/08-development-plan.md +++ b/docs/08-development-plan.md @@ -50,6 +50,8 @@ 新增 `deploy/docker-compose.yml` 作为单节点可部署基线:MySQL 健康检查通过后依次执行迁移和 `orbit-admin verify`,核验成功才启动 Orbit;`make compose-check` 提供不泄露密码的静态配置门禁,首次管理员初始化和域名/同源访问说明见[Compose 部署基线](22-compose-deployment.md),实际正常启动与失败阻断证据见[Compose 部署基线验证](reports/phase-9-compose-deployment.md)。该基线不替代生产数据库高可用、正式 Secret、容量、备份和 RPO/RTO 验收。 +新增 `deploy/nginx/orbit.conf.example` 作为可选 HTTPS/WSS 入口模板:Compose 默认只将业务端口绑定到 `127.0.0.1`,Nginx 负责 TLS 终止、WebSocket 升级、健康探针代理和控制台登录来源限速;配置语法及端口边界证据见[HTTPS/WSS 入口基线验证](reports/phase-10-ingress.md)。Nginx 是部署入口,不会成为 Orbit 服务端的业务依赖,真实域名、正式证书、入口故障和公网重连仍需目标环境验收。 + ## 2. 持续推进方式 每个阶段按以下顺序推进: diff --git a/docs/22-compose-deployment.md b/docs/22-compose-deployment.md index 945bf78..0828bdb 100644 --- a/docs/22-compose-deployment.md +++ b/docs/22-compose-deployment.md @@ -2,7 +2,7 @@ 本文给出单节点部署的可重复入口,适合开发、验收和小规模生产前置环境。它把 MySQL、迁移、schema 核验和 Orbit 服务串成一条有明确失败边界的启动链路;生产多节点、数据库高可用、备份恢复和容量门禁仍按[生产验收](05-production-acceptance.md)执行。 -前置条件:Docker Engine、Docker Compose v2.20 或更高版本;正式环境还应具备镜像仓库、Secret 管理器和 HTTPS 反向代理。 +前置条件:Docker Engine、Docker Compose v2.20 或更高版本;如果使用仓库里的 Nginx 示例,需要 Nginx 1.25.1 或更高版本;正式环境还应具备镜像仓库、Secret 管理器和 HTTPS 反向代理。 ## 1. 启动契约 @@ -38,6 +38,7 @@ export ORBIT_DB_NAME=orbit export ORBIT_DB_USER=orbit_runtime export ORBIT_DB_PASSWORD='从秘密管理器读取' export ORBIT_DB_ROOT_PASSWORD='从秘密管理器读取' +export ORBIT_HTTP_BIND=127.0.0.1 export ORBIT_HTTP_PORT=8080 ``` @@ -87,7 +88,9 @@ curl --fail http://127.0.0.1:"${ORBIT_HTTP_PORT:-8080}"/health/ready Orbit Console 使用当前页面的同源地址调用服务端,浏览器不需要再填写 API URL,也不需要上传凭据 JSON 才能查看控制台。直接访问 `http://主机:端口/console/`,未登录会跳转到 `/console/login`。 -接入域名时,将 HTTPS 反向代理转发到 Orbit 的 `8080` 端口,并保留 WebSocket Upgrade/Connection 头;浏览器访问的域名就是 Console 自动使用的 API 地址。`ORBIT_HTTP_PORT` 只控制主机映射端口,不是业务域名配置。TLS 证书、域名和安全头由反向代理或入口网关管理。 +接入域名时,参考 [`deploy/nginx/orbit.conf.example`](../deploy/nginx/orbit.conf.example),将 HTTPS 反向代理转发到 Orbit 的 `8080` 端口,并保留 WebSocket Upgrade/Connection 头;浏览器访问的域名就是 Console 自动使用的 API 地址。`ORBIT_HTTP_BIND` 控制主机监听地址,`ORBIT_HTTP_PORT` 控制主机映射端口;两者都不是业务域名配置。生产建议保持 `ORBIT_HTTP_BIND=127.0.0.1`,只允许同机 Nginx 访问。TLS 证书、域名和安全头由反向代理或入口网关管理。 + +修改 Nginx 配置后先执行 `nginx -t`,再平滑加载配置;不要在未验证证书、HTTP 到 HTTPS 跳转和 WSS 升级前切换公网流量。Nginx 不应代理 Orbit 的独立 metrics 监听,metrics 只在内部采集网络开放。 ## 4. 日常操作与失败处理 diff --git a/docs/README.md b/docs/README.md index 493e4ae..d0f27e9 100644 --- a/docs/README.md +++ b/docs/README.md @@ -22,9 +22,11 @@ | [P7 Console 真实链路回归](reports/phase-7-console-e2e.md) | 真实 MySQL、Worker、流程、结果订阅与控制台读取闭环 | | [首次部署与控制台任务闭环](reports/phase-8-console-e2e.md) | 从空库初始化管理员、Docker 启动、控制台登录、WS Worker、重试和定时任务 | | [Compose 部署基线验证](reports/phase-9-compose-deployment.md) | MySQL、迁移、核验、Orbit 启动顺序及失败门禁 | +| [HTTPS/WSS 入口基线验证](reports/phase-10-ingress.md) | 本机端口边界、Nginx TLS、WebSocket 升级和健康探针配置 | | [20 运行与发布手册](20-operations-runbook.md) | 启动、发布、滚动停止、数据库恢复、备份和事故处理 | | [21 容器发布说明](21-container-deployment.md) | 最小镜像、迁移/核验/启动顺序、探针和发布边界 | | [22 Compose 部署基线](22-compose-deployment.md) | MySQL、迁移、核验、Orbit 服务和首次管理员初始化的可重复启动链路 | +| [Nginx HTTPS/WSS 入口示例](../deploy/nginx/orbit.conf.example) | 域名、TLS、WebSocket 升级、健康探针和控制台登录限速 | | [09 可靠结果订阅](09-result-subscription-core.md) | 提交水位、冻结结果与可靠交付 | | [10 Weave决策与交付](10-workflow-engine-core.md) | 有限步骤决策和内部结果Inbox | | [11 Weave实例受理](11-workflow-admission.md) | 固定版本、初始图和首批任务事务 | diff --git a/docs/reports/phase-10-ingress.md b/docs/reports/phase-10-ingress.md new file mode 100644 index 0000000..9b1cdcf --- /dev/null +++ b/docs/reports/phase-10-ingress.md @@ -0,0 +1,40 @@ +# HTTPS/WSS 入口基线验证 + +验证日期:2026-09-22 +验证范围:Orbit 主机端口边界、Nginx HTTPS 配置语法、WebSocket Upgrade 路由、健康探针和控制台登录限速配置。 +验证环境:Docker Desktop、Nginx 1.27 Alpine、当前 Compose 配置。 + +## 已通过 + +### Orbit 端口边界 + +使用临时数据库变量展开 Compose 配置,确认默认映射为: + +```text +host_ip: 127.0.0.1 +published: "8080" +target: 8080 +``` + +Orbit 的明文业务端口默认只对本机开放,公网入口由反向代理承担。需要直接暴露端口的隔离环境必须显式设置 `ORBIT_HTTP_BIND`,不能把开发配置直接当作生产入口。 + +### Nginx 配置语法 + +`deploy/nginx/orbit.conf.example` 使用一次性自签名证书挂载到 Nginx 1.27 Alpine 容器中执行: + +```sh +nginx -t +``` + +结果: + +```text +nginx: the configuration file /etc/nginx/nginx.conf syntax is ok +nginx: configuration file /etc/nginx/nginx.conf test is successful +``` + +配置包含 HTTP 到 HTTPS 跳转、TLS 1.2/1.3、`/v1/worker/connect` 和 `/v1/events/connect` 的 Upgrade/Connection 头、独立长连接超时、`/health/live`、`/health/ready` 代理以及 Console 登录来源限速。metrics 不经过业务入口。 + +## 尚未由本报告关闭的事项 + +本报告只验证配置和端口边界,没有使用真实域名、正式证书、外部负载均衡或公网流量。生产仍需验证证书签发/轮换、DNS、WSS 长连接重连、代理节点故障、真实健康摘除和入口访问日志脱敏;这些不能由一次 `nginx -t` 推导出来。