refactor(ca): v1.1.0 配置体系重构为纯 CLI 传递
- 移除 confs 依赖:删 Conf.hpp 与 confs/configs/ca.ini,配置加载改为 CLI > 环境变量 > 默认值三层(与 dnfs2 同模式) - 新增 16 个配置选项,env 以 CA_* 为主并兼容旧名(LISTEN_ADDR/UPSTREAM_GATEWAY/DB_* 等),启动时配置完整性校验 - 原 ca.ini 配置值迁入 core/services.yaml ca 条目 args - 修复伪造 Token 触发 500:verify_token 统一捕获 RSA 验签异常,按验签失败返回 401(符合黑盒 B3/B4 契约) - 同步更新 ca/README.md 与根 README.md
This commit is contained in:
1 parent
8b6eb47c80
commit
9d548c064f
8 files changed
+244
-302
No files matched your search
@@ -35,7 +35,7 @@
|
||||
└────────────┘
|
||||
日志链路: waf / ca / gateway / static / dnfs / ai_review / captcha / smtp / aibox / confs / redirect80 ──(log_client, pymsg)──► db:8005 (Logs 表)
|
||||
认证链路(v0.9.0 起): waf:443 ──HTTP──► ca:8443 (认证+前置转发) ──HTTP──► gateway:8445 (纯转发, 仅 loopback) ──► 内部服务
|
||||
配置链路: ca / gateway / db2 / static / redirect80 / smtp / captcha ──(pymsg get)──► confs:8010(配置统一存放于 confs/configs/,mtime 变化自动重载)
|
||||
配置链路: gateway / db2 / static / redirect80 / smtp / captcha ──(pymsg get)──► confs:8010(配置统一存放于 confs/configs/,mtime 变化自动重载;ca/dnfs2 例外,配置纯 CLI 传递)
|
||||
```
|
||||
|
||||
> `db2` / `dnfs2` 分别是 `db` / `dnfs` 的 C++20 重实现(接口完全兼容,可平替),架构位置与 Python 版一致。`BotOps` 为 aibox 的终端前端(WebSocket 客户端),`lci` 为嵌入式帧缓冲图形界面,均不在上图中。
|
||||
@@ -90,9 +90,10 @@
|
||||
- 极客操作:`kill -HUP <pid>` 热加载规则;`waf -list` 列规则;`waf -test <file>` 校验语法;`waf -check "<input>"` 测试单条输入。
|
||||
- 配置见 [waf/config.yaml](waf/config.yaml),详见 [waf/README.md](waf/README.md)。
|
||||
|
||||
### ca(中央认证服务 · v1.0.0)
|
||||
### ca(中央认证服务 · v1.1.0)
|
||||
|
||||
- 内部 HTTP 服务,默认监听 127.0.0.1:8443(接管原 gateway 端口,WAF upstream 不变),不再承载 TLS(TLS 已由 WAF 终止)。
|
||||
- **配置体系(v1.1.0)**:彻底移除 confs 依赖,配置全部经 CLI 传递(CLI > 环境变量 > 默认值三层),原 `ca.ini` 配置值迁入 [core/services.yaml](core/services.yaml) 的 ca 条目 args;兼容旧环境变量名(`LISTEN_ADDR`/`UPSTREAM_GATEWAY`/`DB_*` 等)。
|
||||
- **密码安全**:PBKDF2-HMAC-SHA256(10 万轮 + 16 字节随机盐),密码强度校验(8+ 字符、4 类中至少 3 类),常数时间比较防 timing attack。
|
||||
- **用户存储**:用户数据通过 pymsg/TCP 调用 `db:8005`(`DbC::call_msg`)存取;登录成功即更新 `Users.last_login`(`"$now"` 哨兵)。
|
||||
- **Token 机制**:`Base64(payload) + "_" + RSA-Sign(payload)`,默认 1 小时过期;RSA 密钥对启动时内存生成,重启后旧 Token 全部失效;jti 黑名单 + 用户版本号双轨吊销。
|
||||
@@ -206,7 +207,6 @@
|
||||
|
||||
| 配置文件 | 消费模块 | 依赖强度 |
|
||||
|----------|----------|----------|
|
||||
| `ca.ini` | ca | confs 拉取 |
|
||||
| `gateway.ini` | gateway | confs 拉取(0.7.0 起配置完全迁入) |
|
||||
| `db.ini` | db / db2 | 强依赖(confs 未就绪自动重试) |
|
||||
| `static.ini` | static(fsys.py)/ fsys | 强依赖(拉取失败直接退出) |
|
||||
@@ -215,7 +215,7 @@
|
||||
| `captcha.ini` | captcha / captcha2 | confs 拉取 |
|
||||
| `example.ini` | —(旧 gateway 示例模板) | 无消费方 |
|
||||
|
||||
- **部署安全约定**:`confs/configs/*.ini` 含敏感字段(`ca.ini` 管理员白名单是 `is_admin` 判定依据、`smtp.ini` 密码字段),权限必须收紧为 `root:root 0640`,仅 root 可写;被同机普通用户篡改可直接提权为管理员或劫持邮件验证码(详见 `docs/security/core_SECURITY_AUDIT_20260801.md` 2026-08-10 复审)。
|
||||
- **部署安全约定**:`confs/configs/*.ini` 含敏感字段(如 `smtp.ini` 密码字段),权限必须收紧为 `root:root 0640`,仅 root 可写;被同机普通用户篡改可直接劫持邮件验证码(详见 `docs/security/core_SECURITY_AUDIT_20260801.md` 2026-08-10 复审)。原 `ca.ini` 管理员白名单已随 ca v1.1.0 迁入 `core/services.yaml` 的 CLI args,其防护对象相应转为 `services.yaml` 的文件权限。
|
||||
|
||||
### aibox(独立 AI 智能体系统)
|
||||
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
VERSION := 1.0.0
|
||||
VERSION := 1.1.0
|
||||
APP_NAME := ca
|
||||
INSTL_DIR := /usr/local/bin
|
||||
CPP := g++
|
||||
@@ -13,7 +13,7 @@ $(shell mkdir -p $(BUILD_DIR))
|
||||
all: $(BUILD_DIR)/$(APP_NAME).out
|
||||
$(BUILD_DIR)/$(APP_NAME).out: $(BUILD_DIR)/main.o
|
||||
$(CPP) $^ $(LIBS) $(MY_LIBS) -o $@
|
||||
$(BUILD_DIR)/main.o: src/main.cpp inc/DbC.hpp inc/Conf.hpp inc/ExpBase.hpp inc/LocalTime.hpp
|
||||
$(BUILD_DIR)/main.o: src/main.cpp inc/DbC.hpp inc/ExpBase.hpp inc/LocalTime.hpp
|
||||
$(CPP) $(CPPFLAGS) -c $< $(INCS) -o $@
|
||||
clear:
|
||||
@rm -rvf build/
|
||||
+31
-16
@@ -21,7 +21,7 @@ UniServe Framework 的中央认证服务(Central Auth),C++20 实现,v1.0
|
||||
- 无 `Target-Url` 头 = 公开请求:剥离伪造身份头(`X-Auth-User`/`X-Auth-Admin`)后直转。
|
||||
- 有 `Target-Url` 头 = 受保护请求:本地验签(RSA + 过期 + jti 黑名单 + 用户版本号),通过后注入 `X-Auth-User`(管理员追加 `X-Auth-Admin: 1`)与 `Authorization: Bearer` 再转发。
|
||||
- 上游不可达/超时统一返回 `502 Bad Gateway`(区别于 CA 自身故障的 500)。
|
||||
- **WebSocket 反向代理(仅管理员)**:`[WsProxy]` 段配置的路径在 HTTP Upgrade 握手时校验 Token(`is_admin=true`),通过后透明桥接到后端 ws:// 或 wss:// 服务。
|
||||
- **WebSocket 反向代理(仅管理员)**:`--ws-proxy-routes` 配置的路径在 HTTP Upgrade 握手时校验 Token(`is_admin=true`),通过后透明桥接到后端 ws:// 或 wss:// 服务。
|
||||
- **CAPTCHA 代理**:`GET /captcha/image` 代理 captcha2 签发图形验证码(PNG + `X-Captcha-Token` 响应头,10 次/分钟/IP,禁缓存)。
|
||||
- **邮箱验证码**:`/register/send_code`、`/reset/send_code` 发送 6 位验证码邮件(10 分钟有效);`/reset_password` 重置密码(防枚举恒成功 + 改密后版本号吊销全端 Token)。
|
||||
- **速率限制**:登录 5 次/5 分钟、注册 3 次/10 分钟、发验证码 5 次/10 分钟(IP 级)+ 3 次/小时/10 次/天(邮箱级),按 `X-Forwarded-For` 客户端 IP 计数(由 WAF 注入),超限一律返回 429。
|
||||
@@ -48,8 +48,8 @@ gateway 侧仅做**存在性不变量**检查(显式 `Target-Url` 请求必须
|
||||
| 常数时间比较 | PBKDF2 验证用 `diff |= hash[i] ^ expected[i]` 防 timing attack |
|
||||
| 防用户枚举 | 登录对不存在用户执行 dummy PBKDF2 拉平时序;重置密码恒返回成功 |
|
||||
| CAPTCHA | 统一走 captcha2 远端校验(Token 一次性消费);`/login` 强制 |
|
||||
| TLS 可选 | `Server.tls = off` 时纯 HTTP(配合 WAF),`on` 时用 `full_chain.crt` / `server.key` |
|
||||
| 下游容错 | pymsg 客户端 `retry_on_refused`:db/confs 未就绪时阻塞等待;日志客户端 fail-safe 静默丢弃 |
|
||||
| TLS 可选 | 缺省(不传 `--tls`)纯 HTTP(配合 WAF),`--tls` 时用 `full_chain.crt` / `server.key` |
|
||||
| 下游容错 | pymsg 客户端 `retry_on_refused`:db 未就绪时阻塞等待;日志客户端 fail-safe 静默丢弃 |
|
||||
|
||||
## 接口
|
||||
|
||||
@@ -93,32 +93,46 @@ Target-Url: http://127.0.0.1:8006 // 可选;携带即视为
|
||||
- 有 `Target-Url`:验签通过后注入身份头转发;`Target-Url` 白名单由 gateway 侧 `Proxy.opened` 裁决(403)。
|
||||
- 上游故障:`502 {"success": false, "message": "Bad Gateway"}`。
|
||||
|
||||
### WebSocket 代理(`[WsProxy]` 配置的路径)
|
||||
### WebSocket 代理(`--ws-proxy-routes` 配置的路径)
|
||||
|
||||
握手阶段从 `Authorization: Bearer` / `Cookie: q4_token` 提取 Token,验签且 `is_admin=true` 才升级连接并桥接后端;否则拒绝。路由精确匹配,优先级高于 HTTP 通配 `*`。
|
||||
|
||||
## 配置
|
||||
|
||||
两层加载:**confs 统一配置服务**(启动时 pymsg `get` 拉取 `ca.ini`,`retry_on_refused=true`)→ **环境变量覆盖**(最高优先级)。
|
||||
v1.1.0 起配置**全部经 CLI 传递**,不再依赖 confs 统一配置服务(与 dnfs2 同模式)。三层优先级:**CLI 参数 > 环境变量 > 代码默认值**;部署侧由 [`core/services.yaml`](../core/services.yaml) 的 `args` 声明完整传入。
|
||||
|
||||
配置文件:[confs/configs/ca.ini](../confs/configs/ca.ini)。
|
||||
| CLI 选项 | 环境变量(CA_ 前缀优先,兼容旧名) | 默认值 |
|
||||
|----------|-----------------------------------|--------|
|
||||
| `--host` | `CA_HOST` / `LISTEN_ADDR` | `127.0.0.1` |
|
||||
| `--port` | `CA_PORT` / `LISTEN_PORT` | `8443` |
|
||||
| `--tls` | `CA_TLS=on` | 关(纯 HTTP) |
|
||||
| `--upstream-gateway` | `CA_UPSTREAM_GATEWAY` / `UPSTREAM_GATEWAY` | `http://127.0.0.1:8445` |
|
||||
| `--token-keep-time` | `CA_TOKEN_KEEP_TIME` | `3600` |
|
||||
| `--token-refresh-grace-multiplier` | `CA_TOKEN_REFRESH_GRACE_MULTIPLIER` | `2` |
|
||||
| `--token-max-refresh` | `CA_TOKEN_MAX_REFRESH` | `10` |
|
||||
| `--cors-origins` | `CA_CORS_ORIGINS` | 空 |
|
||||
| `--admin-users` | `CA_ADMIN_USERS` | 空 |
|
||||
| `--ws-proxy-routes` | `CA_WS_PROXY_ROUTES` | 空 |
|
||||
| `--db-host` / `--db-port` | `CA_DB_HOST`/`DB_HOST` / `CA_DB_PORT`/`DB_PORT` | `127.0.0.1:8005` |
|
||||
| `--captcha-host` / `--captcha-port` | `CA_CAPTCHA_HOST`/`CAPTCHA_HOST` / `CA_CAPTCHA_PORT`/`CAPTCHA_PORT` | `127.0.0.1:8009` |
|
||||
| `--smtp-host` / `--smtp-port` | `CA_SMTP_HOST`/`SMTP_HOST` / `CA_SMTP_PORT`/`SMTP_PORT` | `127.0.0.1:8008` |
|
||||
| `--instance` | — | `ca` |
|
||||
| `--log-level` | — | `info` |
|
||||
|
||||
- `Server.address`/`port`/`tls`:监听地址/端口/TLS(默认 `127.0.0.1:8443`,env `LISTEN_ADDR`/`LISTEN_PORT`)。
|
||||
- `Upstream.gateway`:唯一上游 gateway 地址(默认 `http://127.0.0.1:8445`,env `UPSTREAM_GATEWAY`)。
|
||||
- `Token.*`:`keep_time` / `refresh_grace_multiplier` / `max_refresh`。
|
||||
- `Admin.users`:管理员白名单(分号分隔邮箱),登录命中时 Token payload 写入 `is_admin=true`。
|
||||
- `CORS.allowed_origins`:已读取但尚未用于预检响应(见 [`ca_SECURITY_AUDIT.md`](../docs/security/ca_SECURITY_AUDIT.md) P2-3)。
|
||||
- `WsProxy.routes`:`/path=ws://host:port/target`,分号分隔。
|
||||
- `Downstream.*`:db/captcha/smtp 地址(env `DB_*`/`CAPTCHA_*`/`SMTP_*`/`CONFS_*`)。
|
||||
- `--admin-users`:管理员白名单(分号分隔邮箱),登录命中时 Token payload 写入 `is_admin=true`。
|
||||
- `--cors-origins`:已读取但尚未用于预检响应(见 [`ca_SECURITY_AUDIT.md`](../docs/security/ca_SECURITY_AUDIT.md) P2-3)。
|
||||
- `--ws-proxy-routes`:`/path=ws://host:port/target`,分号分隔多条。
|
||||
- 旧环境变量名(`LISTEN_ADDR` 等)保留兼容,供 Docker 部署用服务名覆盖 `127.0.0.1`。
|
||||
- 启动时做配置完整性校验:监听地址/端口、上游 gateway、db 地址非法则启动失败。
|
||||
|
||||
## 编译与运行
|
||||
|
||||
```bash
|
||||
bash _set.sh # make clean && make
|
||||
bash _run.sh # 从 confs 拉取配置启动(先 sleep 2 等待 confs/db)
|
||||
bash _run.sh # 纯 CLI 配置启动("$@" 透传,可临时覆盖任意配置项)
|
||||
```
|
||||
|
||||
命令行参数:`-h/--help`、`-v/--version`。日志经 LogC 异步批量推送 db:8005 `Logs` 表(service 名 `ca`)。
|
||||
命令行参数:完整选项见 `--help`(见上表);`-h/--help`、`-v/--version`。日志经 LogC 异步批量推送 db:8005 `Logs` 表(service 名 `ca`)。
|
||||
|
||||
## 目录结构
|
||||
|
||||
@@ -129,7 +143,6 @@ ca/
|
||||
├── inc/
|
||||
│ ├── DbC.hpp # db 服务客户端(pymsg,retry_on_refused=true;client 名 "ca")
|
||||
│ ├── LogC.hpp # 统一日志客户端(异步批量,fail-safe;client 名 "ca-log")
|
||||
│ ├── Conf.hpp # ConfIni:ini 解析 + env 覆盖
|
||||
│ ├── ExpBase.hpp # 异常基类(带 source_location)
|
||||
│ └── LocalTime.hpp # 本地时间工具
|
||||
├── Makefile
|
||||
@@ -139,4 +152,6 @@ ca/
|
||||
|
||||
## 版本
|
||||
|
||||
`1.1.0`:配置体系重构——彻底移除 confs 依赖(删 `Conf.hpp` 与 `confs/configs/ca.ini`),配置全部经 CLI 传递(CLI > 环境变量 > 默认值三层),原 ca.ini 配置值迁入 `core/services.yaml` 的 ca 条目 args;修复伪造 Token 触发 500(RSA 验签异常未捕获,现统一返回 401,符合黑盒测试 B3/B4 契约)。
|
||||
|
||||
`1.0.0`:认证独立化首发——自 gateway v0.8.0 整体搬迁全部认证资产(逐字保留实现),新增前置转发层(固定上游 + strip-then-set 身份头契约 + 上游 502 语义)。原 gateway 同步瘦身为 v0.9.0 纯转发服务。
|
||||
+3
-2
@@ -1,7 +1,8 @@
|
||||
#!/bin/bash
|
||||
# ca 启动脚本
|
||||
# 配置统一由 confs 服务管理(confs/configs/ca.ini),
|
||||
# 启动时从 confs(CONFS_HOST:CONFS_PORT,默认 127.0.0.1:8010)拉取配置。
|
||||
# 配置完全经 CLI 参数传递(v1.1.0 起不再依赖 confs),
|
||||
# 部署侧由 core/services.yaml 的 args 声明完整传入;
|
||||
# "$@" 透传允许命令行临时覆盖任意配置项。
|
||||
cd "$(dirname "$0")" || exit
|
||||
|
||||
# sleep 已移至 services.yaml 的 sleep_before 字段统一管理
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
#pragma once
|
||||
#include <iostream>
|
||||
#include <exception>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <cstdlib>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/ini_parser.hpp>
|
||||
namespace property_tree = boost::property_tree;
|
||||
using ptree = property_tree::ptree;
|
||||
namespace qing {
|
||||
|
||||
class ConfIni {
|
||||
public:
|
||||
/// 默认构造(空配置,后续通过赋值覆盖,仅供 confs 加载流程内部使用)
|
||||
ConfIni() = default;
|
||||
|
||||
/// 从文件路径解析 ini(保留用于开发/调试模式)
|
||||
ConfIni(const std::string &filename) {
|
||||
if (filename.empty()) return; // 空文件名不报错,后续会通过赋值覆盖
|
||||
try {
|
||||
std::cout << "[conf] 从文件加载配置: " << filename << std::endl;
|
||||
property_tree::ini_parser::read_ini(filename, pt);
|
||||
}
|
||||
catch (const boost::property_tree::ini_parser_error& e) {
|
||||
std::cerr << "[conf] INI 解析错误: " << e.what() << std::endl;
|
||||
throw IniConfigureFileParseException("INI parsing error!");
|
||||
}
|
||||
}
|
||||
|
||||
/// 从 ini 文本字符串解析(用于从 confs 拉取后解析)
|
||||
static ConfIni fromString(const std::string &ini_text) {
|
||||
ConfIni c;
|
||||
try {
|
||||
std::istringstream iss(ini_text);
|
||||
property_tree::ini_parser::read_ini(iss, c.pt);
|
||||
}
|
||||
catch (const boost::property_tree::ini_parser_error& e) {
|
||||
std::cerr << "[conf] INI 文本解析错误: " << e.what() << std::endl;
|
||||
throw IniConfigureFileParseException("INI parsing error!");
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T get(const std::string &path, const T &_defaut) {
|
||||
return pt.get<T>(path, _defaut);
|
||||
}
|
||||
|
||||
/// 读取环境变量,不存在则返回默认值
|
||||
static std::string env(const char *name, const std::string &default_val = "") {
|
||||
const char *v = std::getenv(name);
|
||||
return v ? std::string(v) : default_val;
|
||||
}
|
||||
|
||||
static int env_int(const char *name, int default_val) {
|
||||
const char *v = std::getenv(name);
|
||||
if (!v || !*v) return default_val;
|
||||
try { return std::stoi(v); }
|
||||
catch (...) { return default_val; }
|
||||
}
|
||||
|
||||
class IniConfigureFileParseException: public std::exception {
|
||||
public:
|
||||
explicit IniConfigureFileParseException(const std::string &msg)
|
||||
: message(msg) {}
|
||||
const char* what() const noexcept override {
|
||||
return message.c_str();
|
||||
}
|
||||
private:
|
||||
std::string message;
|
||||
};
|
||||
|
||||
private:
|
||||
ptree pt;
|
||||
};
|
||||
}
|
||||
+167
-149
@@ -10,6 +10,7 @@
|
||||
#include <typeinfo>
|
||||
#include <memory>
|
||||
#include <csignal>
|
||||
#include <cstdlib>
|
||||
#include <random>
|
||||
#include <regex>
|
||||
#include <thread>
|
||||
@@ -23,7 +24,6 @@
|
||||
#include <openssl/kdf.h>
|
||||
#include "ExpBase.hpp"
|
||||
#include "LocalTime.hpp"
|
||||
#include "Conf.hpp"
|
||||
#include "DbC.hpp"
|
||||
#include <us/LogC.hpp>
|
||||
|
||||
@@ -32,7 +32,7 @@ using namespace qing;
|
||||
namespace json = boost::json;
|
||||
namespace po = boost::program_options;
|
||||
|
||||
constexpr const char *_VER = "1.0.0";
|
||||
constexpr const char *_VER = "1.1.0";
|
||||
|
||||
//////////////////////////////////
|
||||
#include <sstream>
|
||||
@@ -376,28 +376,41 @@ std::string extract_token_from_request(const http::request<http::string_body>& r
|
||||
// 参数 raw_token:"base64data_signature" 格式(不含 "Bearer " 前缀)
|
||||
TokenInfo verify_token(const std::string& raw_token);
|
||||
|
||||
/* configure items */
|
||||
static std::string _ADDR;
|
||||
static int _PORT;
|
||||
static bool _TLS = false;
|
||||
/* 上游 gateway 地址(前置拓扑:ca 认证后转发至此),由 [Upstream] gateway 配置 */
|
||||
static std::string _UPSTREAM_GATEWAY = "http://127.0.0.1:8445";
|
||||
static int _TOKEN_KEEP_TIME = 3600; // Token 有效期(秒),可由 [Token] keep_time 配置
|
||||
static int _TOKEN_REFRESH_GRACE_MULTIPLIER = 2; // Token 刷新宽限期倍数,过期后 iat + keep_time*倍数 窗口内仍可刷新
|
||||
static int _TOKEN_MAX_REFRESH = 10; // Token 生命周期内最大刷新次数,可由 [Token] max_refresh 配置
|
||||
static std::vector<std::string> _CORS_ORIGINS; // CORS 允许的 Origin 白名单,可由 [CORS] allowed_origins 配置
|
||||
static std::vector<std::string> _ADMIN_USERS; // 管理员账号白名单,可由 [Admin] users 配置(分号分隔),登录时写入 Token payload 的 is_admin
|
||||
/* 环境变量读取辅助(v1.1.0 配置体系 CLI > env > 默认值) */
|
||||
static std::string env_or(const char* name, const std::string& def = "") {
|
||||
const char* v = std::getenv(name);
|
||||
return (v && *v) ? std::string(v) : def;
|
||||
}
|
||||
static int env_int_or(const char* name, int def) {
|
||||
const char* v = std::getenv(name);
|
||||
if (!v || !*v) return def;
|
||||
try { return std::stoi(v); }
|
||||
catch (...) { return def; }
|
||||
}
|
||||
static bool env_flag(const char* name) {
|
||||
std::string v = env_or(name);
|
||||
return v == "1" || v == "on" || v == "true" || v == "yes";
|
||||
}
|
||||
|
||||
/* 下游 pymsg 服务地址(从 confs [Downstream] 段加载,可被环境变量覆盖) */
|
||||
/* configure items(默认值即声明处初值,env 与 CLI 依次覆盖) */
|
||||
static std::string _ADDR = "127.0.0.1";
|
||||
static int _PORT = 8443;
|
||||
static bool _TLS = false;
|
||||
/* 上游 gateway 地址(前置拓扑:ca 认证后转发至此) */
|
||||
static std::string _UPSTREAM_GATEWAY = "http://127.0.0.1:8445";
|
||||
static int _TOKEN_KEEP_TIME = 3600; // Token 有效期(秒)
|
||||
static int _TOKEN_REFRESH_GRACE_MULTIPLIER = 2; // Token 刷新宽限期倍数,过期后 iat + keep_time*倍数 窗口内仍可刷新
|
||||
static int _TOKEN_MAX_REFRESH = 10; // Token 生命周期内最大刷新次数
|
||||
static std::vector<std::string> _CORS_ORIGINS; // CORS 允许的 Origin 白名单(分号分隔)
|
||||
static std::vector<std::string> _ADMIN_USERS; // 管理员账号白名单(分号分隔),登录时写入 Token payload 的 is_admin
|
||||
|
||||
/* 下游 pymsg 服务地址 */
|
||||
static std::string _DB_HOST = "127.0.0.1";
|
||||
static int _DB_PORT = 8005;
|
||||
static std::string _CAPTCHA_HOST = "127.0.0.1";
|
||||
static int _CAPTCHA_PORT = 8009;
|
||||
static std::string _SMTP_HOST = "127.0.0.1";
|
||||
static int _SMTP_PORT = 8008;
|
||||
/* confs 引导配置(不依赖 confs 本身,环境变量 CONFS_HOST/CONFS_PORT 覆盖,默认 127.0.0.1:8010) */
|
||||
static std::string _CONFS_HOST = "127.0.0.1";
|
||||
static int _CONFS_PORT = 8010;
|
||||
|
||||
/* WebSocket 代理路由:path -> ws://backend-host:port/target
|
||||
* 仅管理员用户可访问,握手阶段由 ws_proxy 回调验签 Token 中的 is_admin */
|
||||
@@ -620,9 +633,16 @@ TokenInfo verify_token(const std::string& raw_token) {
|
||||
return info;
|
||||
}
|
||||
|
||||
// RSA 验签
|
||||
// RSA 验签(Verify 对非法签名格式(如奇数长度 hex)会抛异常,
|
||||
// 此处统一捕获按验签失败处理,避免伪造 Token 触发 500——审计 B3/B4 契约要求 401)
|
||||
auto publ = MyRSA::Public_Key::from_pem(_KEYGEN->get_public_key_pem());
|
||||
if (!publ.Verify(auth, sign)) {
|
||||
bool sig_ok = false;
|
||||
try {
|
||||
sig_ok = publ.Verify(auth, sign);
|
||||
} catch (const std::exception&) {
|
||||
sig_ok = false;
|
||||
}
|
||||
if (!sig_ok) {
|
||||
info.http_status = 401;
|
||||
return info;
|
||||
}
|
||||
@@ -767,14 +787,27 @@ void set_signal_action() {
|
||||
/* -------- 入口 --------*/
|
||||
int main(int argc, char **argv) {
|
||||
|
||||
/* 首先处理命令行参数 */
|
||||
/* 首先处理命令行参数(v1.1.0 起配置全部经 CLI 传递) */
|
||||
po::options_description desc("cpp language web ca (central auth)");
|
||||
desc.add_options()
|
||||
("help,h", "output help message")
|
||||
("version,v", "output version message")
|
||||
("host", po::value<std::string>(), "绑定地址(默认 127.0.0.1),覆盖 [Server] address")
|
||||
("port", po::value<int>(), "监听端口(默认 8443),覆盖 [Server] port")
|
||||
("config", po::value<std::string>(), "confs 拉取的配置名(默认 ca.ini)")
|
||||
("host", po::value<std::string>(), "绑定地址(默认 127.0.0.1,env CA_HOST/LISTEN_ADDR)")
|
||||
("port", po::value<int>(), "监听端口(默认 8443,env CA_PORT/LISTEN_PORT)")
|
||||
("tls", "启用 HTTPS(证书 full_chain.crt/server.key;缺省纯 HTTP,env CA_TLS=on)")
|
||||
("upstream-gateway", po::value<std::string>(), "上游 gateway 地址(默认 http://127.0.0.1:8445,env CA_UPSTREAM_GATEWAY/UPSTREAM_GATEWAY)")
|
||||
("token-keep-time", po::value<int>(), "Token 有效期秒数(默认 3600,env CA_TOKEN_KEEP_TIME)")
|
||||
("token-refresh-grace-multiplier", po::value<int>(), "Token 刷新宽限期倍数(默认 2,env CA_TOKEN_REFRESH_GRACE_MULTIPLIER)")
|
||||
("token-max-refresh", po::value<int>(), "Token 最大刷新次数(默认 10,env CA_TOKEN_MAX_REFRESH)")
|
||||
("cors-origins", po::value<std::string>(), "CORS Origin 白名单,分号分隔(env CA_CORS_ORIGINS)")
|
||||
("admin-users", po::value<std::string>(), "管理员账号白名单,分号分隔(env CA_ADMIN_USERS)")
|
||||
("ws-proxy-routes", po::value<std::string>(), "WS 代理路由 /path=ws://host:port,分号分隔(env CA_WS_PROXY_ROUTES)")
|
||||
("db-host", po::value<std::string>(), "db 服务地址(默认 127.0.0.1,env CA_DB_HOST/DB_HOST)")
|
||||
("db-port", po::value<int>(), "db 服务端口(默认 8005,env CA_DB_PORT/DB_PORT)")
|
||||
("captcha-host", po::value<std::string>(), "captcha 服务地址(默认 127.0.0.1,env CA_CAPTCHA_HOST/CAPTCHA_HOST)")
|
||||
("captcha-port", po::value<int>(), "captcha 服务端口(默认 8009,env CA_CAPTCHA_PORT/CAPTCHA_PORT)")
|
||||
("smtp-host", po::value<std::string>(), "smtp 服务地址(默认 127.0.0.1,env CA_SMTP_HOST/SMTP_HOST)")
|
||||
("smtp-port", po::value<int>(), "smtp 服务端口(默认 8008,env CA_SMTP_PORT/SMTP_PORT)")
|
||||
("log-level", po::value<std::string>(), "日志级别 debug|info|warn|error(默认 info)")
|
||||
("instance", po::value<std::string>(), "实例名(注册中心用,默认 ca)")
|
||||
;
|
||||
@@ -808,133 +841,98 @@ int main(int argc, char **argv) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ==================== 配置加载(两层:confs 强依赖 → 环境变量覆盖) ====================
|
||||
* 与 Python 服务(dnfs/db/static)保持一致的加载模式:
|
||||
* 1. 引导配置:confs 地址从 CONFS_HOST/CONFS_PORT 环境变量读取,缺省 127.0.0.1:8010
|
||||
* 2. 从 confs 拉取 ca.ini 全文(retry_on_refused=true 等待 confs 就绪)
|
||||
* 3. 用 boost::property_tree 解析 ini
|
||||
* 4. 环境变量覆盖(最高优先级,Docker 环境需要服务名而非 127.0.0.1)
|
||||
* 5. 配置完整性校验:必需字段缺失则启动失败
|
||||
* 不再支持本地 ini 文件回退(与 static 模块一致,配置统一由 confs 管理)。
|
||||
* ==================================================================================== */
|
||||
/* ==================== 配置加载(v1.1.0:CLI > 环境变量 > 默认值) ====================
|
||||
* 配置完全经命令行传递(与 dnfs2 同模式),不再依赖 confs 统一配置服务:
|
||||
* 1. 默认值:各配置项声明处初值(与原 ca.ini 默认值一致)
|
||||
* 2. 环境变量覆盖:CA_ 前缀为主,兼容旧名(LISTEN_ADDR / LISTEN_PORT / UPSTREAM_GATEWAY / DB_x / CAPTCHA_x / SMTP_x,Docker 环境需要服务名而非 127.0.0.1)
|
||||
* 3. CLI 参数覆盖:最高优先级
|
||||
* 4. 配置完整性校验:必需字段非法则启动失败
|
||||
* 部署侧由 core/services.yaml 的 args 声明完整传入配置。
|
||||
* ============================================================================== */
|
||||
|
||||
{
|
||||
/* 1. 引导配置:confs 连接地址(不依赖 confs 本身,避免循环依赖) */
|
||||
_CONFS_HOST = ConfIni::env("CONFS_HOST", "127.0.0.1");
|
||||
_CONFS_PORT = ConfIni::env_int("CONFS_PORT", 8010);
|
||||
|
||||
/* --config 覆盖 confs 拉取的配置名(在 confs 拉取前读取) */
|
||||
std::string confs_config_name = "ca.ini";
|
||||
if (vm.count("config")) {
|
||||
confs_config_name = vm["config"].as<std::string>();
|
||||
}
|
||||
|
||||
ConfIni cf;
|
||||
try {
|
||||
/* 从 confs 拉取配置 */
|
||||
std::cout << "[ca] 从 confs (" << _CONFS_HOST << ":" << _CONFS_PORT
|
||||
<< ") 拉取 " << confs_config_name << " ..." << std::endl;
|
||||
pmc::net::MsgClient confs_client(_CONFS_HOST, _CONFS_PORT, "ca");
|
||||
json::object req;
|
||||
req["_route"] = "get";
|
||||
req["name"] = confs_config_name;
|
||||
std::string resp_str = confs_client.call(
|
||||
json::serialize(req), /*timeout=*/5.0,
|
||||
/*retry_on_refused=*/true);
|
||||
auto resp = json::parse(resp_str).as_object();
|
||||
if (!resp.at("success").as_bool()) {
|
||||
std::string err = "unknown";
|
||||
if (auto* ev = resp.if_contains("error")) {
|
||||
if (ev->is_string()) err = ev->as_string().c_str();
|
||||
}
|
||||
std::cerr << "[ca] 致命错误: 从 confs 获取 ca.ini 失败: " << err << std::endl;
|
||||
return 1;
|
||||
}
|
||||
std::string ini_content = resp.at("data").as_object().at("content").as_string().c_str();
|
||||
cf = ConfIni::fromString(ini_content);
|
||||
std::cout << "[ca] 配置来源: confs (ca.ini @ "
|
||||
<< _CONFS_HOST << ":" << _CONFS_PORT << ")" << std::endl;
|
||||
}
|
||||
catch (ConfIni::IniConfigureFileParseException &e) {
|
||||
std::cerr << "ERROR: " << e.what() << std::endl;
|
||||
std::cerr << "ini parse error" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
catch (const std::exception &e) {
|
||||
std::cerr << "[ca] 致命错误: 配置加载失败: " << e.what() << std::endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* 2. 从 ini 读取配置 */
|
||||
_ADDR = cf.get<std::string>("Server.address", "");
|
||||
_PORT = cf.get<int>("Server.port", -1);
|
||||
_TLS = cf.get<bool>("Server.tls", false);
|
||||
_UPSTREAM_GATEWAY = cf.get<std::string>("Upstream.gateway", "http://127.0.0.1:8445");
|
||||
_TOKEN_KEEP_TIME = cf.get<int>("Token.keep_time", 3600);
|
||||
_TOKEN_REFRESH_GRACE_MULTIPLIER = cf.get<int>("Token.refresh_grace_multiplier", 2);
|
||||
_TOKEN_MAX_REFRESH = cf.get<int>("Token.max_refresh", 10);
|
||||
_CORS_ORIGINS = split(cf.get<std::string>("CORS.allowed_origins", ""), ';');
|
||||
_ADMIN_USERS = split(cf.get<std::string>("Admin.users", ""), ';');
|
||||
|
||||
/* 下游服务地址 */
|
||||
_DB_HOST = cf.get<std::string>("Downstream.db_host", "127.0.0.1");
|
||||
_DB_PORT = cf.get<int>("Downstream.db_port", 8005);
|
||||
_CAPTCHA_HOST = cf.get<std::string>("Downstream.captcha_host", "127.0.0.1");
|
||||
_CAPTCHA_PORT = cf.get<int>("Downstream.captcha_port", 8009);
|
||||
_SMTP_HOST = cf.get<std::string>("Downstream.smtp_host", "127.0.0.1");
|
||||
_SMTP_PORT = cf.get<int>("Downstream.smtp_port", 8008);
|
||||
|
||||
/* [WsProxy] WebSocket 代理路由:格式 /path=ws://host:port/target;... */
|
||||
/* 1. 环境变量覆盖(默认值之上) */
|
||||
{
|
||||
std::string ws_cfg = cf.get<std::string>("WsProxy.routes", "");
|
||||
if (!ws_cfg.empty()) {
|
||||
auto entries = split(ws_cfg, ';');
|
||||
for (auto& entry : entries) {
|
||||
auto eq = entry.find('=');
|
||||
if (eq != std::string::npos) {
|
||||
std::string path = entry.substr(0, eq);
|
||||
std::string url = entry.substr(eq + 1);
|
||||
auto trim = [](std::string& s) {
|
||||
while (!s.empty() && std::isspace((unsigned char)s.front())) s.erase(s.begin());
|
||||
while (!s.empty() && std::isspace((unsigned char)s.back())) s.pop_back();
|
||||
};
|
||||
trim(path); trim(url);
|
||||
if (!path.empty() && !url.empty()) {
|
||||
_WS_PROXY_ROUTES[path] = url;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
std::string v = env_or("CA_HOST", env_or("LISTEN_ADDR"));
|
||||
if (!v.empty()) _ADDR = v;
|
||||
}
|
||||
{
|
||||
int v = env_int_or("CA_PORT", env_int_or("LISTEN_PORT", -1));
|
||||
if (v > 0) _PORT = v;
|
||||
}
|
||||
if (env_flag("CA_TLS")) _TLS = true;
|
||||
|
||||
{
|
||||
std::string v = env_or("CA_UPSTREAM_GATEWAY", env_or("UPSTREAM_GATEWAY"));
|
||||
if (!v.empty()) _UPSTREAM_GATEWAY = v;
|
||||
}
|
||||
{
|
||||
int v = env_int_or("CA_TOKEN_KEEP_TIME", -1);
|
||||
if (v > 0) _TOKEN_KEEP_TIME = v;
|
||||
}
|
||||
{
|
||||
int v = env_int_or("CA_TOKEN_REFRESH_GRACE_MULTIPLIER", -1);
|
||||
if (v > 0) _TOKEN_REFRESH_GRACE_MULTIPLIER = v;
|
||||
}
|
||||
{
|
||||
int v = env_int_or("CA_TOKEN_MAX_REFRESH", -1);
|
||||
if (v > 0) _TOKEN_MAX_REFRESH = v;
|
||||
}
|
||||
{
|
||||
std::string v = env_or("CA_CORS_ORIGINS");
|
||||
if (!v.empty()) _CORS_ORIGINS = split(v, ';');
|
||||
}
|
||||
{
|
||||
std::string v = env_or("CA_ADMIN_USERS");
|
||||
if (!v.empty()) _ADMIN_USERS = split(v, ';');
|
||||
}
|
||||
|
||||
/* 3. 环境变量覆盖(优先级最高,Docker 环境使用服务名而非 127.0.0.1) */
|
||||
std::string env_listen_addr = ConfIni::env("LISTEN_ADDR");
|
||||
if (!env_listen_addr.empty()) _ADDR = env_listen_addr;
|
||||
int env_listen_port = ConfIni::env_int("LISTEN_PORT", -1);
|
||||
if (env_listen_port > 0) _PORT = env_listen_port;
|
||||
{
|
||||
std::string v = env_or("CA_DB_HOST", env_or("DB_HOST"));
|
||||
if (!v.empty()) _DB_HOST = v;
|
||||
}
|
||||
{
|
||||
int v = env_int_or("CA_DB_PORT", env_int_or("DB_PORT", -1));
|
||||
if (v > 0) _DB_PORT = v;
|
||||
}
|
||||
{
|
||||
std::string v = env_or("CA_CAPTCHA_HOST", env_or("CAPTCHA_HOST"));
|
||||
if (!v.empty()) _CAPTCHA_HOST = v;
|
||||
}
|
||||
{
|
||||
int v = env_int_or("CA_CAPTCHA_PORT", env_int_or("CAPTCHA_PORT", -1));
|
||||
if (v > 0) _CAPTCHA_PORT = v;
|
||||
}
|
||||
{
|
||||
std::string v = env_or("CA_SMTP_HOST", env_or("SMTP_HOST"));
|
||||
if (!v.empty()) _SMTP_HOST = v;
|
||||
}
|
||||
{
|
||||
int v = env_int_or("CA_SMTP_PORT", env_int_or("SMTP_PORT", -1));
|
||||
if (v > 0) _SMTP_PORT = v;
|
||||
}
|
||||
|
||||
std::string env_upstream = ConfIni::env("UPSTREAM_GATEWAY");
|
||||
if (!env_upstream.empty()) _UPSTREAM_GATEWAY = env_upstream;
|
||||
/* WS 代理路由 env:/path=ws://host:port/target;...(解析逻辑与 CLI 共用) */
|
||||
std::string ws_cfg = env_or("CA_WS_PROXY_ROUTES");
|
||||
|
||||
std::string env_db_host = ConfIni::env("DB_HOST");
|
||||
if (!env_db_host.empty()) _DB_HOST = env_db_host;
|
||||
int env_db_port = ConfIni::env_int("DB_PORT", -1);
|
||||
if (env_db_port > 0) _DB_PORT = env_db_port;
|
||||
|
||||
std::string env_captcha_host = ConfIni::env("CAPTCHA_HOST");
|
||||
if (!env_captcha_host.empty()) _CAPTCHA_HOST = env_captcha_host;
|
||||
int env_captcha_port = ConfIni::env_int("CAPTCHA_PORT", -1);
|
||||
if (env_captcha_port > 0) _CAPTCHA_PORT = env_captcha_port;
|
||||
|
||||
std::string env_smtp_host = ConfIni::env("SMTP_HOST");
|
||||
if (!env_smtp_host.empty()) _SMTP_HOST = env_smtp_host;
|
||||
int env_smtp_port = ConfIni::env_int("SMTP_PORT", -1);
|
||||
if (env_smtp_port > 0) _SMTP_PORT = env_smtp_port;
|
||||
|
||||
/* 4. CLI 参数覆盖(最高优先级) */
|
||||
if (vm.count("host")) _ADDR = vm["host"].as<std::string>();
|
||||
if (vm.count("port")) _PORT = vm["port"].as<int>();
|
||||
if (vm.count("instance")) _INSTANCE = vm["instance"].as<std::string>();
|
||||
/* 2. CLI 参数覆盖(最高优先级) */
|
||||
if (vm.count("host")) _ADDR = vm["host"].as<std::string>();
|
||||
if (vm.count("port")) _PORT = vm["port"].as<int>();
|
||||
if (vm.count("tls")) _TLS = true;
|
||||
if (vm.count("upstream-gateway")) _UPSTREAM_GATEWAY = vm["upstream-gateway"].as<std::string>();
|
||||
if (vm.count("token-keep-time")) _TOKEN_KEEP_TIME = vm["token-keep-time"].as<int>();
|
||||
if (vm.count("token-refresh-grace-multiplier")) _TOKEN_REFRESH_GRACE_MULTIPLIER = vm["token-refresh-grace-multiplier"].as<int>();
|
||||
if (vm.count("token-max-refresh")) _TOKEN_MAX_REFRESH = vm["token-max-refresh"].as<int>();
|
||||
if (vm.count("cors-origins")) _CORS_ORIGINS = split(vm["cors-origins"].as<std::string>(), ';');
|
||||
if (vm.count("admin-users")) _ADMIN_USERS = split(vm["admin-users"].as<std::string>(), ';');
|
||||
if (vm.count("ws-proxy-routes")) ws_cfg = vm["ws-proxy-routes"].as<std::string>();
|
||||
if (vm.count("db-host")) _DB_HOST = vm["db-host"].as<std::string>();
|
||||
if (vm.count("db-port")) _DB_PORT = vm["db-port"].as<int>();
|
||||
if (vm.count("captcha-host")) _CAPTCHA_HOST = vm["captcha-host"].as<std::string>();
|
||||
if (vm.count("captcha-port")) _CAPTCHA_PORT = vm["captcha-port"].as<int>();
|
||||
if (vm.count("smtp-host")) _SMTP_HOST = vm["smtp-host"].as<std::string>();
|
||||
if (vm.count("smtp-port")) _SMTP_PORT = vm["smtp-port"].as<int>();
|
||||
if (vm.count("instance")) _INSTANCE = vm["instance"].as<std::string>();
|
||||
if (vm.count("log-level")) {
|
||||
_LOG_LEVEL = vm["log-level"].as<std::string>();
|
||||
// 归一化 warning → warn
|
||||
@@ -952,18 +950,38 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
}
|
||||
|
||||
/* 5. 配置完整性校验 */
|
||||
if (_ADDR.empty() || _PORT == -1) {
|
||||
std::cerr << "[ca] 致命错误: 配置项 [Server] address/port 缺失"
|
||||
"(ini 中未配置且无 LISTEN_ADDR/LISTEN_PORT 环境变量覆盖)" << std::endl;
|
||||
/* WS 代理路由解析:/path=ws://host:port/target;... */
|
||||
if (!ws_cfg.empty()) {
|
||||
auto entries = split(ws_cfg, ';');
|
||||
for (auto& entry : entries) {
|
||||
auto eq = entry.find('=');
|
||||
if (eq != std::string::npos) {
|
||||
std::string path = entry.substr(0, eq);
|
||||
std::string url = entry.substr(eq + 1);
|
||||
auto trim = [](std::string& s) {
|
||||
while (!s.empty() && std::isspace((unsigned char)s.front())) s.erase(s.begin());
|
||||
while (!s.empty() && std::isspace((unsigned char)s.back())) s.pop_back();
|
||||
};
|
||||
trim(path); trim(url);
|
||||
if (!path.empty() && !url.empty()) {
|
||||
_WS_PROXY_ROUTES[path] = url;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 3. 配置完整性校验 */
|
||||
if (_ADDR.empty() || _PORT <= 0) {
|
||||
std::cerr << "[ca] 致命错误: 监听地址/端口非法"
|
||||
"(--host/--port 或 CA_HOST/CA_PORT 环境变量)" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
if (_UPSTREAM_GATEWAY.empty()) {
|
||||
std::cerr << "[ca] 致命错误: 配置项 [Upstream] gateway 缺失" << std::endl;
|
||||
std::cerr << "[ca] 致命错误: 上游 gateway 地址缺失(--upstream-gateway)" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
if (_DB_HOST.empty() || _DB_PORT <= 0) {
|
||||
std::cerr << "[ca] 致命错误: 配置项 [Downstream] db_host/db_port 缺失" << std::endl;
|
||||
std::cerr << "[ca] 致命错误: db 服务地址缺失(--db-host/--db-port)" << std::endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
[Server]
|
||||
# CA(Central Auth)前置认证服务:接管原 gateway 的 8443 端口,
|
||||
# WAF upstream 不变(waf → ca → gateway 前置拓扑)。
|
||||
# - tls = off 启用 HTTP 模式(配合 waf 模块使用)
|
||||
# - tls = on 启用 HTTPS 模式(独立部署,证书为 full_chain.crt / server.key)
|
||||
address = 127.0.0.1
|
||||
port = 8443
|
||||
tls = off
|
||||
|
||||
[Upstream]
|
||||
# 前置拓扑的唯一上游:认证通过后转发到 gateway(仅 loopback)
|
||||
# 环境变量 UPSTREAM_GATEWAY 可覆盖
|
||||
gateway = http://127.0.0.1:8445
|
||||
|
||||
[Token]
|
||||
# Token 有效期(秒),默认 3600 = 1 小时
|
||||
keep_time = 3600
|
||||
# Token 刷新宽限期倍数:过期后 iat + keep_time * 倍数 窗口内仍可刷新,默认 2
|
||||
refresh_grace_multiplier = 2
|
||||
# Token 生命周期内最大刷新次数,超过后需重新登录,默认 10
|
||||
max_refresh = 10
|
||||
|
||||
[Admin]
|
||||
# 管理员账号白名单(分号分隔)。登录时命中即在 Token payload 写入 is_admin=true,
|
||||
# 下游服务(dnfs 等)凭该字段豁免所有权校验,无需各自维护名单。
|
||||
# 修改后需重启 ca 生效;旧 Token 无此字段,按非管理员处理。
|
||||
users = 1776177662@qq.com;277665039@qq.com;
|
||||
|
||||
[CORS]
|
||||
# 允许跨域的 Origin 白名单(分号分隔)
|
||||
# 仅命中的 Origin 才会回填 Access-Control-Allow-Origin;缺省为空(不回填)
|
||||
allowed_origins = https://qsont.me;https://www.qsont.me;http://127.0.0.1:8443;http://localhost:8443
|
||||
|
||||
[WsProxy]
|
||||
# WebSocket 反向代理路由(仅管理员可访问)
|
||||
# 格式:routes = /path=ws://host:port/target;/path2=ws://host2:port2/target2
|
||||
# 握手阶段会校验 HTTP Upgrade 请求中的 Token(Authorization: Bearer xxx 或 Cookie: q4_token=xxx),
|
||||
# 仅 Token 中 is_admin=true 的用户才能升级连接;非管理员或无 Token 直接拒绝。
|
||||
# 支持 ws:// 和 wss:// 后端
|
||||
# 示例:routes = /ws/aibox=ws://127.0.0.1:8765;/ws/other=wss://example.com/ws
|
||||
routes = /ws/aibox=ws://127.0.0.1:8765;
|
||||
|
||||
[Downstream]
|
||||
# 下游 pymsg 服务地址(其他微服务的 host:port)
|
||||
# 环境变量可覆盖:DB_HOST/DB_PORT, CAPTCHA_HOST/CAPTCHA_PORT, SMTP_HOST/SMTP_PORT, CONFS_HOST/CONFS_PORT
|
||||
db_host = 127.0.0.1
|
||||
db_port = 8005
|
||||
captcha_host = 127.0.0.1
|
||||
captcha_port = 8009
|
||||
smtp_host = 127.0.0.1
|
||||
smtp_port = 8008
|
||||
+37
-1
@@ -76,7 +76,43 @@ services:
|
||||
|
||||
- name: ca
|
||||
cmd: bash /opt/micro_servers/ca/_run.sh
|
||||
args: ["--instance", "ca", "--log-level", "info"]
|
||||
args:
|
||||
- "--instance"
|
||||
- "ca"
|
||||
- "--log-level"
|
||||
- "info"
|
||||
# --- v1.1.0:配置全部经 CLI 传递(原 confs/configs/ca.ini 迁入) ---
|
||||
- "--host"
|
||||
- "127.0.0.1"
|
||||
- "--port"
|
||||
- "8443"
|
||||
- "--upstream-gateway"
|
||||
- "http://127.0.0.1:8445"
|
||||
- "--token-keep-time"
|
||||
- "3600"
|
||||
- "--token-refresh-grace-multiplier"
|
||||
- "2"
|
||||
- "--token-max-refresh"
|
||||
- "10"
|
||||
# 管理员白名单(分号分隔):登录命中时 Token 写入 is_admin=true
|
||||
- "--admin-users"
|
||||
- "1776177662@qq.com;277665039@qq.com"
|
||||
- "--cors-origins"
|
||||
- "https://qsont.me;https://www.qsont.me;http://127.0.0.1:8443;http://localhost:8443"
|
||||
- "--ws-proxy-routes"
|
||||
- "/ws/aibox=ws://127.0.0.1:8765"
|
||||
- "--db-host"
|
||||
- "127.0.0.1"
|
||||
- "--db-port"
|
||||
- "8005"
|
||||
- "--captcha-host"
|
||||
- "127.0.0.1"
|
||||
- "--captcha-port"
|
||||
- "8009"
|
||||
- "--smtp-host"
|
||||
- "127.0.0.1"
|
||||
- "--smtp-port"
|
||||
- "8008"
|
||||
depends_on: [db2]
|
||||
sleep_before: 2
|
||||
|
||||
|
||||
Reference in new issue
Block a user