Feature: In-memory web audit log for the MFC server

Add a process-local security audit trail to the Windows server's web
console, mirroring the Go server's event names and reply shape. The ring
keeps the most recent 1000 events since process start and is never
persisted.

- New query_audit WebSocket command, admin-only, with time/event/actor/
  IP/device/keyword/warn-only filters and pagination; the reply is the
  audit_result payload the existing Audit Log page already understands.
  The challenge advertises caps.auditLog so browsers enable the button.
- Record logins (success, failure, throttling, salt probes), WebSocket
  connect/close, screen and terminal session start/stop/denials,
  clipboard transfers (byte counts only), RDP resets, and admin user
  mutations.
- Repeated failed logins and username probes are de-duplicated per
  connection so scanners cannot flood the ring.
- Audit writes never hold m_ClientsMutex: disconnect paths snapshot the
  client identity before the map entry is erased, and offline-device
  stops are audited after the clients lock is released.

Co-Authored-By: deepseek-flash
This commit is contained in:
yuanyuanxianganddeepseek-flash committed 2026-10-18 19:16:17 +02:00
1 parent a522424491
commit ca671d6453
3 files changed
+473 -15

No files matched your search

+21 -2
View File
@@ -5,6 +5,8 @@
#include <vector>
#include <map>
#include <set>
#include <initializer_list>
#include <utility>
#include <atomic>
#include <chrono>
#include <mutex>
@@ -76,6 +78,10 @@ struct WebClient {
uint64_t connected_at;
uint64_t last_activity; // Last message/pong received (for heartbeat timeout)
std::string client_ip;
// Per-connection audit de-duplication: probes an attacker can repeat in a
// tight loop are recorded once per connection instead of flooding the ring.
bool salt_probe_logged = false;
bool login_throttle_logged = false;
WebClient() : connected_at(0), last_activity(0) {}
};
@@ -239,6 +245,15 @@ private:
void HandleListUsers(void* ws_ptr, const std::string& token);
void HandleGetGroups(void* ws_ptr, const std::string& token);
// Security audit trail: in-memory ring of the last 1000 events since
// startup (see WebService.cpp). query_audit is admin-only.
void HandleQueryAudit(void* ws_ptr, const std::string& msg);
std::string ClientIP(void* ws_ptr);
void AuditWeb(const std::string& event, bool warn,
const std::string& actor, const std::string& role,
const std::string& ip, void* ws_ptr, uint64_t device_id,
std::initializer_list<std::pair<const char*, std::string>> extra = {});
// Send to WebSocket
void SendText(void* ws_ptr, const std::string& text);
void SendBinary(void* ws_ptr, const uint8_t* data, size_t len);
@@ -388,8 +403,12 @@ private:
std::set<uint64_t> m_TermPending; // 已发 COMMAND_SHELL 待响应
std::mutex m_TermMutex;
// 内部清理(已持锁版本)
void CloseTermSessionLocked(uint64_t device_id);
// 内部清理(已持锁版本)。reason/actor/role/ip 仅用于审计日志;actor 为空时
// 尝试从 m_Clients 反查(ws 已注销时需由调用方传入)。
void CloseTermSessionLocked(uint64_t device_id, const char* reason = "ws_close",
const std::string& actor = std::string(),
const std::string& role = std::string(),
const std::string& ip = std::string());
};
// Global accessor