fix(libhttp,dnfs2): HttpClient 新增超时接口,dnfs2 启用配置超时
修复 dnfs2 审计 L-3(P2-N5): libhttp HttpClient 硬编码 CURLOPT_TIMEOUT=30L/CONNECTTIMEOUT=10L, dnfs2 的 gateway_timeout/review_timeout 配置加载后从未使用,实际超时恒为 30s。 修复: - libhttp HttpClient 新增 set_timeout/set_connect_timeout 接口 (默认 30s/10s,向后兼容 gateway/core) - request_stream_sync 使用成员变量替代硬编码值 - dnfs2 decode_token 设置 gateway_timeout - dnfs2 review_content 设置 review_timeout 安全评级 A- → A,已知限制 6→5 项。
This commit is contained in:
1 parent
9804838e96
commit
117fc0241e
5 files changed
+70
-16
No files matched your search
+5
-3
@@ -80,6 +80,8 @@ timeout = 5
|
|||||||
|
|
||||||
## 已知限制(P2,设计决策不修复)
|
## 已知限制(P2,设计决策不修复)
|
||||||
|
|
||||||
- P2-12: 错误响应可能泄露信息(新实现已尽量脱敏)
|
- L-1: update_note 允许创建循环(需图遍历检测后代关系)
|
||||||
- P2-13: CORS 反射任意 Origin(内部服务绑 127.0.0.1,可接受)
|
- L-2: delete_note TOCTOU 残余(需 db2 事务支持)
|
||||||
- P2-15: pid 判断已加严格类型校验,但未完全对齐 Python 语义
|
- L-4: 无速率限制(gateway/WAF 职责)
|
||||||
|
- L-5: title/content 无最大长度校验
|
||||||
|
- L-6: AI 审核不可用时阻断写入(fail-closed 设计决策)
|
||||||
@@ -643,6 +643,7 @@ private:
|
|||||||
|
|
||||||
HttpClient http;
|
HttpClient http;
|
||||||
http.set_max_response_size(kHttpMaxResponse);
|
http.set_max_response_size(kHttpMaxResponse);
|
||||||
|
http.set_timeout(static_cast<long>(_cfg.gateway_timeout));
|
||||||
http.set_header("Content-Type: application/json");
|
http.set_header("Content-Type: application/json");
|
||||||
http.set_header("Authorization: Bearer " + token);
|
http.set_header("Authorization: Bearer " + token);
|
||||||
|
|
||||||
@@ -704,6 +705,7 @@ private:
|
|||||||
bool review_content(const std::string& content) {
|
bool review_content(const std::string& content) {
|
||||||
HttpClient http;
|
HttpClient http;
|
||||||
http.set_max_response_size(kHttpMaxResponse);
|
http.set_max_response_size(kHttpMaxResponse);
|
||||||
|
http.set_timeout(static_cast<long>(_cfg.review_timeout));
|
||||||
http.set_header("Content-Type: application/json");
|
http.set_header("Content-Type: application/json");
|
||||||
|
|
||||||
json::object payload;
|
json::object payload;
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ dnfs2 是 Python dnfs 的 C++20 重实现,基于 libhttp + libmsg + boost::jso
|
|||||||
| 类别 | 数量 | 状态 |
|
| 类别 | 数量 | 状态 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
| 计划修复的 P0/P1(继承自 dnfs) | 6 | 全部有效 ✓ |
|
| 计划修复的 P0/P1(继承自 dnfs) | 6 | 全部有效 ✓ |
|
||||||
| 新发现问题 | 4 | 全部修复 ✓ |
|
| 新发现问题 | 5 | 全部修复 ✓ |
|
||||||
| 已知限制(架构性/继承性) | 6 | 记录 |
|
| 已知限制(架构性/继承性) | 5 | 记录 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -130,6 +130,32 @@ if (!child_r.data.as_array().empty()) {
|
|||||||
if (cfg.threads < 1 || cfg.threads > 256) { ... exit(1); }
|
if (cfg.threads < 1 || cfg.threads > 256) { ... exit(1); }
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### P2-N5: HttpClient 超时硬编码,配置值未生效 — ✓ 已修复
|
||||||
|
|
||||||
|
**位置**: `libhttp/src/HttpClient.cpp` L92-93(原)、`dnfs2/src/dnfs2.cpp`
|
||||||
|
**问题**: HttpClient `request_stream_sync` 硬编码 `CURLOPT_TIMEOUT=30L` / `CURLOPT_CONNECTTIMEOUT=10L`,无接口可覆盖。dnfs2 的 `review_timeout` 和 `gateway_timeout` 配置加载并校验范围后从未使用,实际超时恒为 30s。
|
||||||
|
|
||||||
|
**修复**:
|
||||||
|
1. libhttp `HttpClient` 新增 `set_timeout()` / `set_connect_timeout()` 接口(默认值 30s/10s,向后兼容 gateway/core)
|
||||||
|
2. `request_stream_sync` 使用成员变量替代硬编码值
|
||||||
|
3. dnfs2 `decode_token()` 设置 `gateway_timeout`,`review_content()` 设置 `review_timeout`
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
// libhttp/HttpClient.hpp
|
||||||
|
void set_timeout(long seconds);
|
||||||
|
long get_timeout() const;
|
||||||
|
void set_connect_timeout(long seconds);
|
||||||
|
long get_connect_timeout() const;
|
||||||
|
long timeout_ = 30; // 默认 30s
|
||||||
|
long connect_timeout_ = 10; // 默认 10s
|
||||||
|
|
||||||
|
// dnfs2 decode_token
|
||||||
|
http.set_timeout(static_cast<long>(_cfg.gateway_timeout));
|
||||||
|
|
||||||
|
// dnfs2 review_content
|
||||||
|
http.set_timeout(static_cast<long>(_cfg.review_timeout));
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. 已知限制(不修复,架构性/继承性)
|
## 4. 已知限制(不修复,架构性/继承性)
|
||||||
@@ -142,10 +168,6 @@ update_note 允许将节点的 pid 改为任意已存在的节点,包括自身
|
|||||||
|
|
||||||
delete_note 在"检查无子节点"和"执行删除"之间存在 TOCTOU 窗口,并发请求可在检查后添加子节点。修复需要 db2 支持事务或原子条件删除。dnfs2 已通过 `_call_db()` 统一检查 success 修复了"未检查 success"部分,但 TOCTOU 窗口仍存在。
|
delete_note 在"检查无子节点"和"执行删除"之间存在 TOCTOU 窗口,并发请求可在检查后添加子节点。修复需要 db2 支持事务或原子条件删除。dnfs2 已通过 `_call_db()` 统一检查 success 修复了"未检查 success"部分,但 TOCTOU 窗口仍存在。
|
||||||
|
|
||||||
### L-3: 配置的超时时间未使用(P2)
|
|
||||||
|
|
||||||
`review_timeout` 和 `gateway_timeout` 已加载并校验范围,但 HttpClient 硬编码 30s 超时(libhttp 限制,未提供 `set_timeout()` 接口)。实际超时为 30s 而非配置值。
|
|
||||||
|
|
||||||
### L-4: 无速率限制(P2,继承自 dnfs)
|
### L-4: 无速率限制(P2,继承自 dnfs)
|
||||||
|
|
||||||
无请求频率限制,恶意用户可高频调用 add_note 填充数据库。应由 gateway/WAF 层防护。
|
无请求频率限制,恶意用户可高频调用 add_note 填充数据库。应由 gateway/WAF 层防护。
|
||||||
@@ -191,18 +213,17 @@ dnfs 的错误消息泄露 IP 和用户名(如 `"未授权访问,从192.168.
|
|||||||
|
|
||||||
## 6. 安全评级
|
## 6. 安全评级
|
||||||
|
|
||||||
### 评级: A-
|
### 评级: A
|
||||||
|
|
||||||
**理由**:
|
**理由**:
|
||||||
- 计划修复的 6 项 P0/P1 全部有效 ✓
|
- 计划修复的 6 项 P0/P1 全部有效 ✓
|
||||||
- 新发现 4 项问题全部修复 ✓ (2 P1 + 2 P2)
|
- 新发现 5 项问题全部修复 ✓ (2 P1 + 3 P2)
|
||||||
- IP/用户名泄露问题作为重写副效果已修复 ✓
|
- IP/用户名泄露问题作为重写副效果已修复 ✓
|
||||||
- 残余 6 项已知限制均为架构性/继承性问题,有明确修复路径
|
- 残余 5 项已知限制均为架构性/继承性问题,有明确修复路径
|
||||||
- 相比 Python dnfs(评级 B,多个 P0/P1 未修复),安全基线显著提升
|
- 相比 Python dnfs(评级 B,多个 P0/P1 未修复),安全基线显著提升
|
||||||
|
|
||||||
**扣分项**:
|
**扣分项**:
|
||||||
- L-1/L-2 循环创建和 TOCTOU 未修复(需 db2 事务支持)
|
- L-1/L-2 循环创建和 TOCTOU 未修复(需 db2 事务支持)
|
||||||
- L-3 超时配置未生效(需 libhttp 增强)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -215,5 +236,8 @@ dnfs 的错误消息泄露 IP 和用户名(如 `"未授权访问,从192.168.
|
|||||||
| dnfs2/src/dnfs2.cpp | P2-N3: delete_note 分离 db 错误和子节点检查 |
|
| dnfs2/src/dnfs2.cpp | P2-N3: delete_note 分离 db 错误和子节点检查 |
|
||||||
| dnfs2/src/dnfs2.cpp | P2-N4: threads 上限 256 |
|
| dnfs2/src/dnfs2.cpp | P2-N4: threads 上限 256 |
|
||||||
| dnfs2/src/dnfs2.cpp | 新增 `#include <curl/curl.h>` |
|
| dnfs2/src/dnfs2.cpp | 新增 `#include <curl/curl.h>` |
|
||||||
|
| libhttp/inc/us/HttpClient.hpp | P2-N5: 新增 set_timeout/set_connect_timeout 接口 + 成员变量 |
|
||||||
|
| libhttp/src/HttpClient.cpp | P2-N5: 新增 set_timeout/set_connect_timeout 实现,request_stream_sync 使用成员变量 |
|
||||||
|
| dnfs2/src/dnfs2.cpp | P2-N5: decode_token 设置 gateway_timeout,review_content 设置 review_timeout |
|
||||||
|
|
||||||
编译验证: `g++ -std=c++20 -O3 -Wall -Werror` 通过,零错误零警告。
|
编译验证: `g++ -std=c++20 -O3 -Wall -Werror` 通过,零错误零警告(libhttp + dnfs2 均重新编译)。
|
||||||
@@ -65,6 +65,14 @@ public:
|
|||||||
void set_max_response_size(size_t bytes);
|
void set_max_response_size(size_t bytes);
|
||||||
size_t get_max_response_size() const;
|
size_t get_max_response_size() const;
|
||||||
|
|
||||||
|
// ===== 请求超时控制 =====
|
||||||
|
// 设置整体请求超时(秒,含连接+传输)。默认 30s。
|
||||||
|
void set_timeout(long seconds);
|
||||||
|
long get_timeout() const;
|
||||||
|
// 设置连接建立超时(秒)。默认 10s。
|
||||||
|
void set_connect_timeout(long seconds);
|
||||||
|
long get_connect_timeout() const;
|
||||||
|
|
||||||
// ===== WebSocket Methods (Synchronous, Boost.Beast-based) =====
|
// ===== WebSocket Methods (Synchronous, Boost.Beast-based) =====
|
||||||
bool ws_connect(const std::string& url, int timeout_ms = 5000);
|
bool ws_connect(const std::string& url, int timeout_ms = 5000);
|
||||||
bool ws_connect(const std::string& host, const std::string& port,
|
bool ws_connect(const std::string& host, const std::string& port,
|
||||||
@@ -84,6 +92,8 @@ private:
|
|||||||
std::map<std::string, std::string> response_headers_;
|
std::map<std::string, std::string> response_headers_;
|
||||||
long response_code_ = 0;
|
long response_code_ = 0;
|
||||||
size_t max_response_size_ = 10 * 1024 * 1024; // 默认 10MB,对齐 WAF;0 表示不限制
|
size_t max_response_size_ = 10 * 1024 * 1024; // 默认 10MB,对齐 WAF;0 表示不限制
|
||||||
|
long timeout_ = 30; // 整体请求超时(秒),默认 30s
|
||||||
|
long connect_timeout_ = 10; // 连接建立超时(秒),默认 10s
|
||||||
|
|
||||||
struct WsState {
|
struct WsState {
|
||||||
asio::io_context ioc;
|
asio::io_context ioc;
|
||||||
|
|||||||
@@ -42,6 +42,22 @@ size_t HttpClient::get_max_response_size() const {
|
|||||||
return max_response_size_;
|
return max_response_size_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HttpClient::set_timeout(long seconds) {
|
||||||
|
timeout_ = seconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
long HttpClient::get_timeout() const {
|
||||||
|
return timeout_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HttpClient::set_connect_timeout(long seconds) {
|
||||||
|
connect_timeout_ = seconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
long HttpClient::get_connect_timeout() const {
|
||||||
|
return connect_timeout_;
|
||||||
|
}
|
||||||
|
|
||||||
void HttpClient::set_header(const std::string& header) {
|
void HttpClient::set_header(const std::string& header) {
|
||||||
headers_ = curl_slist_append(headers_, header.c_str());
|
headers_ = curl_slist_append(headers_, header.c_str());
|
||||||
}
|
}
|
||||||
@@ -89,8 +105,8 @@ void HttpClient::request_stream_sync(
|
|||||||
curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION, write_callback);
|
curl_easy_setopt(curl_, CURLOPT_WRITEFUNCTION, write_callback);
|
||||||
curl_easy_setopt(curl_, CURLOPT_WRITEDATA, &write_ctx);
|
curl_easy_setopt(curl_, CURLOPT_WRITEDATA, &write_ctx);
|
||||||
|
|
||||||
curl_easy_setopt(curl_, CURLOPT_TIMEOUT, 30L);
|
curl_easy_setopt(curl_, CURLOPT_TIMEOUT, timeout_);
|
||||||
curl_easy_setopt(curl_, CURLOPT_CONNECTTIMEOUT, 10L);
|
curl_easy_setopt(curl_, CURLOPT_CONNECTTIMEOUT, connect_timeout_);
|
||||||
|
|
||||||
CURLcode res = curl_easy_perform(curl_);
|
CURLcode res = curl_easy_perform(curl_);
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user