fix: 规范开发文档,规范配置文件
This commit is contained in:
parent
1dcb60fa14
commit
bf45cd54a2
|
|
@ -1,277 +1,277 @@
|
||||||
# 消息交互
|
# 消息交互
|
||||||
|
|
||||||
## 数据包格式
|
## 数据包格式
|
||||||
|
|
||||||
### 通用数据包结构
|
### 通用数据包结构
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"type": "消息类型",
|
"type": "消息类型",
|
||||||
"timestamp": 1612345678901,
|
"timestamp": 1612345678901,
|
||||||
"payload": {
|
"payload": {
|
||||||
// 根据消息类型的具体数据结构
|
// 根据消息类型的具体数据结构
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 数据类型定义
|
### 数据类型定义
|
||||||
```go
|
```go
|
||||||
type Packet struct {
|
type Packet struct {
|
||||||
Type string `json:"type"` // 消息类型
|
Type string `json:"type"` // 消息类型
|
||||||
Timestamp int64 `json:"timestamp"` // Unix时间戳
|
Timestamp int64 `json:"timestamp"` // Unix时间戳
|
||||||
Payload interface{} `json:"payload"` // 消息载荷
|
Payload interface{} `json:"payload"` // 消息载荷
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 消息类型及数据结构
|
## 消息类型及数据结构
|
||||||
|
|
||||||
### 1. 系统状态更新 (`STATUS_UPDATE`)
|
### 1. 系统状态更新 (`STATUS_UPDATE`)
|
||||||
|
|
||||||
**描述**: 定期发送的系统性能指标
|
**描述**: 定期发送的系统性能指标
|
||||||
|
|
||||||
**推送频率**: 每30秒一次
|
**推送频率**: 每30秒一次
|
||||||
|
|
||||||
**Payload 结构**: `ServerMetrics`
|
**Payload 结构**: `ServerMetrics`
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"timestamp": "2024-01-15T10:30:00Z",
|
"timestamp": "2024-01-15T10:30:00Z",
|
||||||
"cpu": {
|
"cpu": {
|
||||||
"model": "Intel(R) Xeon(R) CPU E5-2680 v4",
|
"model": "Intel(R) Xeon(R) CPU E5-2680 v4",
|
||||||
"cores": 14,
|
"cores": 14,
|
||||||
"logical_cores": 28,
|
"logical_cores": 28,
|
||||||
"usage_percent": 45.67,
|
"usage_percent": 45.67,
|
||||||
"per_core_percent": [23.4, 45.6, 12.3, ...],
|
"per_core_percent": [23.4, 45.6, 12.3, ...],
|
||||||
"mhz": 2400.5,
|
"mhz": 2400.5,
|
||||||
"cache_size": 35840
|
"cache_size": 35840
|
||||||
},
|
},
|
||||||
"memory": {
|
"memory": {
|
||||||
"total_gb": 128.0,
|
"total_gb": 128.0,
|
||||||
"used_gb": 64.5,
|
"used_gb": 64.5,
|
||||||
"available_gb": 63.5,
|
"available_gb": 63.5,
|
||||||
"used_percent": 50.4,
|
"used_percent": 50.4,
|
||||||
"swap_total_gb": 16.0,
|
"swap_total_gb": 16.0,
|
||||||
"swap_used_gb": 2.3
|
"swap_used_gb": 2.3
|
||||||
},
|
},
|
||||||
"disk": [
|
"disk": [
|
||||||
{
|
{
|
||||||
"mountpoint": "/",
|
"mountpoint": "/",
|
||||||
"device": "/dev/sda1",
|
"device": "/dev/sda1",
|
||||||
"fstype": "ext4",
|
"fstype": "ext4",
|
||||||
"total_gb": 500.0,
|
"total_gb": 500.0,
|
||||||
"used_gb": 250.0,
|
"used_gb": 250.0,
|
||||||
"free_gb": 250.0,
|
"free_gb": 250.0,
|
||||||
"used_percent": 50.0,
|
"used_percent": 50.0,
|
||||||
"inodes_percent": 12.3
|
"inodes_percent": 12.3
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"network": {
|
"network": {
|
||||||
"interfaces": [
|
"interfaces": [
|
||||||
{
|
{
|
||||||
"name": "eth0",
|
"name": "eth0",
|
||||||
"hardware_addr": "00:11:22:33:44:55",
|
"hardware_addr": "00:11:22:33:44:55",
|
||||||
"ip_addresses": ["192.168.1.100", "fe80::211:22ff:fe33:4455"]
|
"ip_addresses": ["192.168.1.100", "fe80::211:22ff:fe33:4455"]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"total_recv_mb": 1234.56,
|
"total_recv_mb": 1234.56,
|
||||||
"total_sent_mb": 987.65,
|
"total_sent_mb": 987.65,
|
||||||
"tcp_connections": 245,
|
"tcp_connections": 245,
|
||||||
"established_conn": 128
|
"established_conn": 128
|
||||||
},
|
},
|
||||||
"load": {
|
"load": {
|
||||||
"load_1": 2.34,
|
"load_1": 2.34,
|
||||||
"load_5": 2.12,
|
"load_5": 2.12,
|
||||||
"load_15": 1.89,
|
"load_15": 1.89,
|
||||||
"relative_load_1": 0.83,
|
"relative_load_1": 0.83,
|
||||||
"relative_load_5": 0.76,
|
"relative_load_5": 0.76,
|
||||||
"relative_load_15": 0.68,
|
"relative_load_15": 0.68,
|
||||||
"procs_running": 132,
|
"procs_running": 132,
|
||||||
"procs_total": 456
|
"procs_total": 456
|
||||||
},
|
},
|
||||||
"processes": [
|
"processes": [
|
||||||
{
|
{
|
||||||
"pid": 1234,
|
"pid": 1234,
|
||||||
"name": "nginx",
|
"name": "nginx",
|
||||||
"cmdline": "nginx: master process",
|
"cmdline": "nginx: master process",
|
||||||
"memory_mb": 125.6,
|
"memory_mb": 125.6,
|
||||||
"cpu_percent": 12.3
|
"cpu_percent": 12.3
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"host": {
|
"host": {
|
||||||
"hostname": "server01",
|
"hostname": "server01",
|
||||||
"os": "linux",
|
"os": "linux",
|
||||||
"platform": "ubuntu",
|
"platform": "ubuntu",
|
||||||
"platform_version": "20.04",
|
"platform_version": "20.04",
|
||||||
"kernel_version": "5.4.0-42-generic",
|
"kernel_version": "5.4.0-42-generic",
|
||||||
"boot_time": "2024-01-15T08:00:00Z",
|
"boot_time": "2024-01-15T08:00:00Z",
|
||||||
"uptime": "2小时30分钟45秒",
|
"uptime": "2小时30分钟45秒",
|
||||||
"cpu_count": 28,
|
"cpu_count": 28,
|
||||||
"architecture": "x86_64",
|
"architecture": "x86_64",
|
||||||
"host_id": "abcdef12-3456-7890-abcd-ef1234567890"
|
"host_id": "abcdef12-3456-7890-abcd-ef1234567890"
|
||||||
},
|
},
|
||||||
"runtime": {
|
"runtime": {
|
||||||
"go_version": "go1.21.0",
|
"go_version": "go1.21.0",
|
||||||
"goos": "linux",
|
"goos": "linux",
|
||||||
"goarch": "amd64",
|
"goarch": "amd64",
|
||||||
"goroot": "/usr/local/go",
|
"goroot": "/usr/local/go",
|
||||||
"gomaxprocs": 28,
|
"gomaxprocs": 28,
|
||||||
"num_cpu": 28,
|
"num_cpu": 28,
|
||||||
"num_goroutine": 42
|
"num_goroutine": 42
|
||||||
},
|
},
|
||||||
"quick_metrics": {
|
"quick_metrics": {
|
||||||
"cpu_percent": 45.67,
|
"cpu_percent": 45.67,
|
||||||
"memory_percent": 50.4,
|
"memory_percent": 50.4,
|
||||||
"root_disk_percent": 50.0,
|
"root_disk_percent": 50.0,
|
||||||
"available_memory_gb": 63.5
|
"available_memory_gb": 63.5
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. SSH登录告警 (`SSH_ALERT`)
|
### 2. SSH登录告警 (`SSH_ALERT`)
|
||||||
|
|
||||||
**描述**: SSH登录安全告警(特别是root登录)
|
**描述**: SSH登录安全告警(特别是root登录)
|
||||||
|
|
||||||
**触发条件**: SSH登录事件,当检测到root登录时触发HIGH级别告警
|
**触发条件**: SSH登录事件,当检测到root登录时触发HIGH级别告警
|
||||||
|
|
||||||
**Payload 结构**: `Alert`
|
**Payload 结构**: `Alert`
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"type": "SSH_ROOT_LOGIN",
|
"type": "SSH_ROOT_LOGIN",
|
||||||
"level": "HIGH",
|
"level": "HIGH",
|
||||||
"message": "检测到来自192.168.1.50的root登录",
|
"message": "检测到来自192.168.1.50的root登录",
|
||||||
"timestamp": "2024-01-15T10:31:15Z",
|
"timestamp": "2024-01-15T10:31:15Z",
|
||||||
"data": {
|
"data": {
|
||||||
"timestamp": "2024-01-15T10:31:15Z",
|
"timestamp": "2024-01-15T10:31:15Z",
|
||||||
"hostname": "server01",
|
"hostname": "server01",
|
||||||
"username": "root",
|
"username": "root",
|
||||||
"method": "publickey",
|
"method": "publickey",
|
||||||
"source_ip": "192.168.1.50",
|
"source_ip": "192.168.1.50",
|
||||||
"port": "22",
|
"port": "22",
|
||||||
"service": "sshd",
|
"service": "sshd",
|
||||||
"pid": "12345",
|
"pid": "12345",
|
||||||
"message": "Accepted publickey for root from 192.168.1.50 port 22"
|
"message": "Accepted publickey for root from 192.168.1.50 port 22"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. 文件完整性告警
|
### 3. 文件完整性告警
|
||||||
|
|
||||||
#### 3.1 非白名单文件告警 (`NON_WHITELISTED_FILE`)
|
#### 3.1 非白名单文件告警 (`NON_WHITELISTED_FILE`)
|
||||||
|
|
||||||
**描述**: 扫描发现不在白名单中的文件
|
**描述**: 扫描发现不在白名单中的文件
|
||||||
|
|
||||||
**触发条件**: 定期扫描中发现未在白名单中注册的文件
|
**触发条件**: 定期扫描中发现未在白名单中注册的文件
|
||||||
|
|
||||||
**Payload 结构**:
|
**Payload 结构**:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"type": "NON_WHITELISTED_FILE",
|
"type": "NON_WHITELISTED_FILE",
|
||||||
"timestamp": 1612345678901,
|
"timestamp": 1612345678901,
|
||||||
"payload": {
|
"payload": {
|
||||||
"filepath": "/tmp/suspicious_file.bin",
|
"filepath": "/tmp/suspicious_file.bin",
|
||||||
"status": "detected"
|
"status": "detected"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 3.2 文件Hash不匹配告警 (`FILE_HASH_MISMATCH`)
|
#### 3.2 文件Hash不匹配告警 (`FILE_HASH_MISMATCH`)
|
||||||
|
|
||||||
**描述**: 白名单文件被篡改(Hash值不匹配)
|
**描述**: 白名单文件被篡改(Hash值不匹配)
|
||||||
|
|
||||||
**触发条件**: 文件hash与白名单记录不符
|
**触发条件**: 文件hash与白名单记录不符
|
||||||
|
|
||||||
**Payload 结构**:
|
**Payload 结构**:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"type": "FILE_HASH_MISMATCH",
|
"type": "FILE_HASH_MISMATCH",
|
||||||
"timestamp": 1612345678901,
|
"timestamp": 1612345678901,
|
||||||
"payload": {
|
"payload": {
|
||||||
"filepath": "/usr/bin/ls",
|
"filepath": "/usr/bin/ls",
|
||||||
"status": "detected"
|
"status": "detected"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. 实时文件监控告警
|
### 4. 实时文件监控告警
|
||||||
|
|
||||||
#### 4.1 实时文件变动告警 (`REALTIME_FILE_ALERT`)
|
#### 4.1 实时文件变动告警 (`REALTIME_FILE_ALERT`)
|
||||||
|
|
||||||
**描述**: 监控目录中检测到非白名单文件的创建或修改
|
**描述**: 监控目录中检测到非白名单文件的创建或修改
|
||||||
|
|
||||||
**触发条件**: 使用fsnotify监控到文件系统事件
|
**触发条件**: 使用fsnotify监控到文件系统事件
|
||||||
|
|
||||||
**Payload 结构**:
|
**Payload 结构**:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"type": "REALTIME_FILE_ALERT",
|
"type": "REALTIME_FILE_ALERT",
|
||||||
"timestamp": 1612345678901,
|
"timestamp": 1612345678901,
|
||||||
"payload": {
|
"payload": {
|
||||||
"filepath": "/tmp/new_suspicious_file",
|
"filepath": "/tmp/new_suspicious_file",
|
||||||
"operation": "CREATE",
|
"operation": "CREATE",
|
||||||
"time": "2024-01-15T10:32:00Z"
|
"time": "2024-01-15T10:32:00Z"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 4.2 实时Hash不匹配告警 (`REALTIME_HASH_MISMATCH`)
|
#### 4.2 实时Hash不匹配告警 (`REALTIME_HASH_MISMATCH`)
|
||||||
|
|
||||||
**描述**: 监控到白名单文件被实时篡改
|
**描述**: 监控到白名单文件被实时篡改
|
||||||
|
|
||||||
**Payload 结构**:
|
**Payload 结构**:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"type": "REALTIME_HASH_MISMATCH",
|
"type": "REALTIME_HASH_MISMATCH",
|
||||||
"timestamp": 1612345678901,
|
"timestamp": 1612345678901,
|
||||||
"payload": {
|
"payload": {
|
||||||
"filepath": "/etc/passwd",
|
"filepath": "/etc/passwd",
|
||||||
"operation": "WRITE",
|
"operation": "WRITE",
|
||||||
"time": "2024-01-15T10:33:00Z"
|
"time": "2024-01-15T10:33:00Z"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 配置接口
|
## 配置接口
|
||||||
|
|
||||||
### 1. 配置下载接口
|
### 1. 配置下载接口
|
||||||
|
|
||||||
Agent 启动时会通过 HTTP 下载两份配置:
|
Agent 启动时会通过 HTTP 下载两份配置:
|
||||||
|
|
||||||
#### 官方配置 (GET)
|
#### 官方配置 (GET)
|
||||||
- **URL**: `http://localhost:8090/api/v1/configs/official.json`
|
- **URL**: `http://localhost:8090/api/v1/configs/official.json`
|
||||||
- **响应格式**: 符合 `OfficialConfig` 结构
|
- **响应格式**: 符合 `OfficialConfig` 结构
|
||||||
|
|
||||||
#### 用户配置 (GET)
|
#### 用户配置 (GET)
|
||||||
- **URL**: `http://localhost:8090/api/v1/configs/user.json`
|
- **URL**: `http://localhost:8090/api/v1/configs/user.json`
|
||||||
- **响应格式**: 符合 `UserConfig` 结构
|
- **响应格式**: 符合 `UserConfig` 结构
|
||||||
|
|
||||||
### 2. 配置数据结构
|
### 2. 配置数据结构
|
||||||
|
|
||||||
#### OfficialConfig
|
#### OfficialConfig
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"whitelist_files": {
|
"whitelist_files": {
|
||||||
"/usr/bin/ls": ["hash1", "hash2"],
|
"/usr/bin/ls": ["hash1", "hash2"],
|
||||||
"/bin/bash": ["hash3"]
|
"/bin/bash": ["hash3"]
|
||||||
},
|
},
|
||||||
"whitelist_processes": ["sshd", "nginx", "docker"],
|
"whitelist_processes": ["sshd", "nginx", "docker"],
|
||||||
"ignored_paths": ["/proc", "/sys", "/dev"]
|
"ignored_paths": ["/proc", "/sys", "/dev"]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### UserConfig
|
#### UserConfig
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"audit_server_url": "ws://audit.example.com:8090/api/v1/ws",
|
"audit_server_url": "ws://audit.example.com:8090/api/v1/ws",
|
||||||
"supplement_files": {
|
"supplement_files": {
|
||||||
"/opt/myapp/bin/app": ["user_hash1"]
|
"/opt/myapp/bin/app": ["user_hash1"]
|
||||||
},
|
},
|
||||||
"supplement_processes": {
|
"supplement_processes": {
|
||||||
"myapp": "/opt/myapp/bin/app start",
|
"myapp": "/opt/myapp/bin/app start",
|
||||||
"custom_service": ""
|
"custom_service": ""
|
||||||
},
|
},
|
||||||
"ignored_paths": ["/mnt/temp"],
|
"ignored_paths": ["/mnt/temp"],
|
||||||
"check_perm_paths": ["/etc/sudoers", "/etc/shadow"],
|
"check_perm_paths": ["/etc/sudoers", "/etc/shadow"],
|
||||||
"email_config": {
|
"email_config": {
|
||||||
"imap_server": "imap.example.com",
|
"imap_server": "imap.example.com",
|
||||||
"emergency_mail": ["admin@example.com", "security@example.com"]
|
"emergency_mail": ["admin@example.com", "security@example.com"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -1,46 +1,72 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import "time"
|
type ModelSwitches struct {
|
||||||
|
FileScanner bool `json:"file_scanner"`
|
||||||
type Configuration struct {
|
FileWatcher bool `json:"file_watcher"`
|
||||||
Local Localconfig // 本地配置
|
SSHMonitor bool `json:"ssh_monitor"`
|
||||||
Offical OfficialConfig // 官方配置
|
SystemMonitor bool `json:"system_monitor"`
|
||||||
User UserConfig // 用户自定义配置
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Localconfig struct {
|
type SSHMonitorConfig struct {
|
||||||
LogPath string `yaml:"log_path"`
|
Enabled bool `json:"enabled"`
|
||||||
CheckInterval time.Duration `yaml:"check_interval"`
|
AlertOnRootLogin bool `json:"alert_on_root_login"`
|
||||||
ServerUrl string `yaml:"server_url"`
|
}
|
||||||
|
|
||||||
|
type SystemMonitorConfig struct {
|
||||||
|
CollectInterval string `json:"collect_interval"`
|
||||||
|
CollectNetwork bool `json:"collect_network"`
|
||||||
|
CollectProcess bool `json:"collect_process"`
|
||||||
|
ProcessLimit int `json:"process_limit"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type MonitorConfig struct {
|
||||||
|
SSHMonitorConfig SSHMonitorConfig `json:"ssh_monitor"`
|
||||||
|
SystemMonitorConfig SystemMonitorConfig `json:"system_monitor"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConnectionConfig struct {
|
||||||
|
CenterServerURL string `json:"center_server_url"`
|
||||||
|
AuditServerURL string `json:"audit_server_url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OfficialConfig struct {
|
type OfficialConfig struct {
|
||||||
WhitelistFiles map[string][]string `yaml:"whitelist_files"`
|
Version string `json:"version"`
|
||||||
WhitelistProcesses []string `yaml:"whitelist_processes"`
|
WhiteListFiles map[string]string `json:"white_list_files"`
|
||||||
IgnoredPaths []string `yaml:"ignored_paths"`
|
WhiteListProcesses []string `json:"white_list_processes"`
|
||||||
|
IgnoredPaths []string `json:"ignored_paths"`
|
||||||
|
ScanPaths []string `json:"scan_paths"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserConfig struct {
|
type UserConfig struct {
|
||||||
AuditServerUrl string `json:"audit_server_url"` // 审计服务器地址
|
Version string `json:"version"`
|
||||||
// 用户补充的白名单文件
|
Connection ConnectionConfig `json:"connection"`
|
||||||
SupplementFiles map[string][]string `json:"supplement_files"`
|
Models ModelSwitches `json:"models"`
|
||||||
// 用户补充的进程列表
|
SupplementFiles map[string]string `json:"supplement_files"`
|
||||||
// Key: 进程名, Value: 启动指令(如果为空则仅作为白名单,如果不为空则需保活)
|
SupplementProcesses []string `json:"supplement_processes"`
|
||||||
SupplementProcesses map[string]string `json:"supplement_processes"`
|
MonitorConfig MonitorConfig `json:"monitor_config"`
|
||||||
IgnoredPaths []string `json:"ignored_paths"`
|
|
||||||
CheckPermPaths []string `json:"check_perm_paths"` // 检查权限的目录
|
|
||||||
|
|
||||||
// 邮件配置
|
|
||||||
EmailConfig EmailConfig `json:"email_config"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type EmailConfig struct {
|
type Configuration struct {
|
||||||
ImapServer string `json:"imap_server"`
|
Official OfficialConfig // 官方配置
|
||||||
EmergencyMail []string `json:"emergency_mail"`
|
User UserConfig // 用户自定义配置
|
||||||
}
|
}
|
||||||
|
|
||||||
type SSHMonitor struct {
|
func NewDefaultUserConfig() UserConfig {
|
||||||
Enabled bool `yaml:"enabled"`
|
return UserConfig{
|
||||||
DisplayOnShell bool `yaml:"display_on_shell"`
|
Version: "BuildInDefault",
|
||||||
AlertOnRootLogin bool `yaml:"alert_on_root_login"`
|
Connection: ConnectionConfig{
|
||||||
|
CenterServerURL: "ws://localhost:8090/api/v1/ws",
|
||||||
|
AuditServerURL: "ws://localhost:8090/api/v1/ws",
|
||||||
|
},
|
||||||
|
Models: ModelSwitches{
|
||||||
|
FileScanner: false,
|
||||||
|
FileWatcher: true,
|
||||||
|
SSHMonitor: true,
|
||||||
|
SystemMonitor: true,
|
||||||
|
},
|
||||||
|
MonitorConfig: MonitorConfig{
|
||||||
|
SSHMonitorConfig: SSHMonitorConfig{Enabled: true},
|
||||||
|
SystemMonitorConfig: SystemMonitorConfig{CollectInterval: "30s", CollectNetwork: true, CollectProcess: true, ProcessLimit: 10},
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -1,141 +1,141 @@
|
||||||
package network
|
package network
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"log"
|
"log"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ClientConfig struct {
|
type ClientConfig struct {
|
||||||
ServerURL string
|
ServerURL string
|
||||||
SendInterval time.Duration
|
SendInterval time.Duration
|
||||||
BufferSize int
|
BufferSize int
|
||||||
}
|
}
|
||||||
|
|
||||||
type WSClient struct {
|
type WSClient struct {
|
||||||
config ClientConfig
|
config ClientConfig
|
||||||
conn *websocket.Conn
|
conn *websocket.Conn
|
||||||
sendChan chan Packet
|
sendChan chan Packet
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
isConnected bool
|
isConnected bool
|
||||||
stopChan chan struct{}
|
stopChan chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWSClient(cfg ClientConfig) *WSClient {
|
func NewWSClient(cfg ClientConfig) *WSClient {
|
||||||
if cfg.BufferSize == 0 {
|
if cfg.BufferSize == 0 {
|
||||||
cfg.BufferSize = 100
|
cfg.BufferSize = 100
|
||||||
}
|
}
|
||||||
return &WSClient{
|
return &WSClient{
|
||||||
config: cfg,
|
config: cfg,
|
||||||
sendChan: make(chan Packet, cfg.BufferSize),
|
sendChan: make(chan Packet, cfg.BufferSize),
|
||||||
stopChan: make(chan struct{}),
|
stopChan: make(chan struct{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *WSClient) Start() {
|
func (c *WSClient) Start() {
|
||||||
go c.connectionLoop()
|
go c.connectionLoop()
|
||||||
go c.sendLoop()
|
go c.sendLoop()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *WSClient) SendQueue(packet Packet) {
|
func (c *WSClient) SendQueue(packet Packet) {
|
||||||
select {
|
select {
|
||||||
case c.sendChan <- packet:
|
case c.sendChan <- packet:
|
||||||
default:
|
default:
|
||||||
log.Printf("[网络] 发送队列已满,丢弃消息: %s", packet.Type)
|
log.Printf("[网络] 发送队列已满,丢弃消息: %s", packet.Type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *WSClient) Stop() {
|
func (c *WSClient) Stop() {
|
||||||
close(c.stopChan)
|
close(c.stopChan)
|
||||||
if c.conn != nil {
|
if c.conn != nil {
|
||||||
c.conn.Close()
|
c.conn.Close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *WSClient) sendLoop() {
|
func (c *WSClient) sendLoop() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-c.stopChan:
|
case <-c.stopChan:
|
||||||
return
|
return
|
||||||
case packet := <-c.sendChan:
|
case packet := <-c.sendChan:
|
||||||
c.sendRaw(packet)
|
c.sendRaw(packet)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *WSClient) sendRaw(packet Packet) {
|
func (c *WSClient) sendRaw(packet Packet) {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
if !c.isConnected || c.conn == nil {
|
if !c.isConnected || c.conn == nil {
|
||||||
log.Printf("[网络] 无连接,无法发送消息: %s", packet.Type)
|
log.Printf("[网络] 无连接,无法发送消息: %s", packet.Type)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.conn.SetWriteDeadline(time.Now().Add(5 * time.Second))
|
c.conn.SetWriteDeadline(time.Now().Add(5 * time.Second))
|
||||||
|
|
||||||
payload, _ := json.Marshal(packet)
|
payload, _ := json.Marshal(packet)
|
||||||
if err := c.conn.WriteMessage(websocket.TextMessage, payload); err != nil {
|
if err := c.conn.WriteMessage(websocket.TextMessage, payload); err != nil {
|
||||||
log.Printf("[网络] 发送消息失败: %v", err)
|
log.Printf("[网络] 发送消息失败: %v", err)
|
||||||
c.isConnected = false
|
c.isConnected = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *WSClient) connectionLoop() {
|
func (c *WSClient) connectionLoop() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-c.stopChan:
|
case <-c.stopChan:
|
||||||
return
|
return
|
||||||
default:
|
default:
|
||||||
if !c.isConnected {
|
if !c.isConnected {
|
||||||
if err := c.connect(); err != nil {
|
if err := c.connect(); err != nil {
|
||||||
log.Printf("[网络] 连接 %s 失败: %v. 5秒后重试...", c.config.ServerURL, err)
|
log.Printf("[网络] 连接 %s 失败: %v. 5秒后重试...", c.config.ServerURL, err)
|
||||||
time.Sleep(5 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, err := c.conn.ReadMessage()
|
_, _, err := c.conn.ReadMessage()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[网络] 连接断开: %v", err)
|
log.Printf("[网络] 连接断开: %v", err)
|
||||||
c.closeConn()
|
c.closeConn()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: 处理服务器消息
|
// TODO: 处理服务器消息
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *WSClient) closeConn() {
|
func (c *WSClient) closeConn() {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
if c.conn != nil {
|
if c.conn != nil {
|
||||||
c.conn.Close()
|
c.conn.Close()
|
||||||
c.conn = nil
|
c.conn = nil
|
||||||
}
|
}
|
||||||
c.isConnected = false
|
c.isConnected = false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *WSClient) connect() error {
|
func (c *WSClient) connect() error {
|
||||||
conn, _, err := websocket.DefaultDialer.Dial(c.config.ServerURL, nil)
|
conn, _, err := websocket.DefaultDialer.Dial(c.config.ServerURL, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-c.stopChan:
|
case <-c.stopChan:
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return nil
|
return nil
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
c.conn = conn
|
c.conn = conn
|
||||||
c.isConnected = true
|
c.isConnected = true
|
||||||
log.Printf("[网络] 成功连接到服务器: %s", c.config.ServerURL)
|
log.Printf("[网络] 成功连接到服务器: %s", c.config.ServerURL)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,49 +1,49 @@
|
||||||
package network
|
package network
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/wuko233/sysmonitord/internal/config"
|
"github.com/wuko233/sysmonitord/internal/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ConfigLoader struct {
|
type ConfigLoader struct {
|
||||||
client *http.Client
|
client *http.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConfigLoader() *ConfigLoader {
|
func NewConfigLoader() *ConfigLoader {
|
||||||
return &ConfigLoader{
|
return &ConfigLoader{
|
||||||
client: &http.Client{
|
client: &http.Client{
|
||||||
Timeout: 10 * time.Second,
|
Timeout: 10 * time.Second,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *ConfigLoader) LoadConfigs(urls ConfigUrls) (config.OfficialConfig, config.UserConfig, error) {
|
func (l *ConfigLoader) LoadConfigs(urls ConfigUrls) (config.OfficialConfig, config.UserConfig, error) {
|
||||||
var official config.OfficialConfig
|
var official config.OfficialConfig
|
||||||
var user config.UserConfig
|
var user config.UserConfig
|
||||||
// 1. 下载官方配置
|
// 1. 下载官方配置
|
||||||
if err := l.fetchJSON(urls.OfficialConfigUrl, &official); err != nil {
|
if err := l.fetchJSON(urls.OfficialConfigUrl, &official); err != nil {
|
||||||
return official, user, fmt.Errorf("下载官方配置失败: %v", err)
|
return official, user, fmt.Errorf("下载官方配置失败: %v", err)
|
||||||
}
|
}
|
||||||
// 2. 下载用户配置
|
// 2. 下载用户配置
|
||||||
if err := l.fetchJSON(urls.UserConfigUrl, &user); err != nil {
|
if err := l.fetchJSON(urls.UserConfigUrl, &user); err != nil {
|
||||||
return official, user, fmt.Errorf("下载用户配置失败: %v", err)
|
return official, user, fmt.Errorf("下载用户配置失败: %v", err)
|
||||||
}
|
}
|
||||||
return official, user, nil
|
return official, user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *ConfigLoader) fetchJSON(url string, target interface{}) error {
|
func (l *ConfigLoader) fetchJSON(url string, target interface{}) error {
|
||||||
resp, err := l.client.Get(url)
|
resp, err := l.client.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("请求失败: %v", err)
|
return fmt.Errorf("请求失败: %v", err)
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return fmt.Errorf("非200响应: %d", resp.StatusCode)
|
return fmt.Errorf("非200响应: %d", resp.StatusCode)
|
||||||
}
|
}
|
||||||
return json.NewDecoder(resp.Body).Decode(target)
|
return json.NewDecoder(resp.Body).Decode(target)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,22 @@
|
||||||
package network
|
package network
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
type Packet struct {
|
type Packet struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Timestamp int64 `json:"timestamp"`
|
Timestamp int64 `json:"timestamp"`
|
||||||
Payload interface{} `json:"payload"`
|
Payload interface{} `json:"payload"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPacket(msgType string, payload interface{}) Packet {
|
func NewPacket(msgType string, payload interface{}) Packet {
|
||||||
return Packet{
|
return Packet{
|
||||||
Type: msgType,
|
Type: msgType,
|
||||||
Timestamp: time.Now().Unix(),
|
Timestamp: time.Now().Unix(),
|
||||||
Payload: payload,
|
Payload: payload,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type ConfigUrls struct {
|
type ConfigUrls struct {
|
||||||
OfficialConfigUrl string
|
OfficialConfigUrl string
|
||||||
UserConfigUrl string
|
UserConfigUrl string
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,133 +1,133 @@
|
||||||
package scanner
|
package scanner
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/shirou/gopsutil/v4/cpu"
|
"github.com/shirou/gopsutil/v4/cpu"
|
||||||
"github.com/wuko233/sysmonitord/internal/network"
|
"github.com/wuko233/sysmonitord/internal/network"
|
||||||
"github.com/wuko233/sysmonitord/internal/whitelist"
|
"github.com/wuko233/sysmonitord/internal/whitelist"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Scanner struct {
|
type Scanner struct {
|
||||||
wlManager *whitelist.Manager
|
wlManager *whitelist.Manager
|
||||||
client *network.WSClient
|
client *network.WSClient
|
||||||
cpuLimit float64
|
cpuLimit float64
|
||||||
scanPaths []string
|
scanPaths []string
|
||||||
stopChan chan struct{}
|
stopChan chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewScanner(wl *whitelist.Manager, client *network.WSClient) *Scanner {
|
func NewScanner(wl *whitelist.Manager, client *network.WSClient) *Scanner {
|
||||||
return &Scanner{
|
return &Scanner{
|
||||||
wlManager: wl,
|
wlManager: wl,
|
||||||
client: client,
|
client: client,
|
||||||
cpuLimit: 50.0,
|
cpuLimit: 50.0,
|
||||||
scanPaths: []string{"/bin", "/sbin", "/usr/bin", "/usr/sbin", "/etc", "/tmp", "/home"},
|
scanPaths: []string{"/bin", "/sbin", "/usr/bin", "/usr/sbin", "/etc", "/tmp", "/home"},
|
||||||
stopChan: make(chan struct{}),
|
stopChan: make(chan struct{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Scanner) Start() {
|
func (s *Scanner) Start() {
|
||||||
log.Println("[扫描器] 启动文件完整性扫描...")
|
log.Println("[扫描器] 启动文件完整性扫描...")
|
||||||
go s.scanLoop()
|
go s.scanLoop()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Scanner) scanLoop() {
|
func (s *Scanner) scanLoop() {
|
||||||
ticker := time.NewTicker(10 * time.Minute)
|
ticker := time.NewTicker(10 * time.Minute)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-s.stopChan:
|
case <-s.stopChan:
|
||||||
return
|
return
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
s.performScan()
|
s.performScan()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Scanner) performScan() {
|
func (s *Scanner) performScan() {
|
||||||
log.Println("[扫描器] 开始新一轮全盘扫描")
|
log.Println("[扫描器] 开始新一轮全盘扫描")
|
||||||
fileCount := 0
|
fileCount := 0
|
||||||
|
|
||||||
for _, root := range s.scanPaths {
|
for _, root := range s.scanPaths {
|
||||||
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case <-s.stopChan:
|
case <-s.stopChan:
|
||||||
return filepath.SkipDir
|
return filepath.SkipDir
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[扫描器] 访问错误: %v", err)
|
log.Printf("[扫描器] 访问错误: %v", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
fileCount++
|
fileCount++
|
||||||
|
|
||||||
if fileCount%100 == 0 {
|
if fileCount%100 == 0 {
|
||||||
s.checkCPUAndSleep()
|
s.checkCPUAndSleep()
|
||||||
}
|
}
|
||||||
|
|
||||||
if info.IsDir() {
|
if info.IsDir() {
|
||||||
if s.wlManager.IsPathIgnored(path) {
|
if s.wlManager.IsPathIgnored(path) {
|
||||||
return filepath.SkipDir
|
return filepath.SkipDir
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
isWhitelisted, isHashMatch, err := s.wlManager.CheckFileStatus(path)
|
isWhitelisted, isHashMatch, err := s.wlManager.CheckFileStatus(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[扫描器] 检查文件状态失败: %v", err)
|
log.Printf("[扫描器] 检查文件状态失败: %v", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isWhitelisted {
|
if !isWhitelisted {
|
||||||
log.Printf("[扫描器] 发现未在白名单文件: %s", path)
|
log.Printf("[扫描器] 发现未在白名单文件: %s", path)
|
||||||
s.reportFile(path, "NON_WHITELISTED_FILE")
|
s.reportFile(path, "NON_WHITELISTED_FILE")
|
||||||
} else if !isHashMatch {
|
} else if !isHashMatch {
|
||||||
log.Printf("[扫描器] 警告!文件Hash不匹配(可能被篡改): %s", path)
|
log.Printf("[扫描器] 警告!文件Hash不匹配(可能被篡改): %s", path)
|
||||||
s.reportFile(path, "FILE_HASH_MISMATCH")
|
s.reportFile(path, "FILE_HASH_MISMATCH")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[扫描器] 扫描目录 %s 出错: %v", root, err)
|
log.Printf("[扫描器] 扫描目录 %s 出错: %v", root, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Scanner) checkCPUAndSleep() {
|
func (s *Scanner) checkCPUAndSleep() {
|
||||||
percent, err := cpu.Percent(200*time.Millisecond, false)
|
percent, err := cpu.Percent(200*time.Millisecond, false)
|
||||||
if err != nil || len(percent) == 0 {
|
if err != nil || len(percent) == 0 {
|
||||||
log.Printf("[扫描器] 获取CPU使用率失败: %v", err)
|
log.Printf("[扫描器] 获取CPU使用率失败: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if percent[0] > s.cpuLimit {
|
if percent[0] > s.cpuLimit {
|
||||||
log.Printf("[扫描器] CPU使用率过高 (%.2f%%),暂停扫描5秒", percent[0])
|
log.Printf("[扫描器] CPU使用率过高 (%.2f%%),暂停扫描5秒", percent[0])
|
||||||
time.Sleep(5 * time.Second)
|
time.Sleep(5 * time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
time.Sleep(10 * time.Millisecond)
|
time.Sleep(10 * time.Millisecond)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Scanner) reportFile(path string, alertType string) {
|
func (s *Scanner) reportFile(path string, alertType string) {
|
||||||
payload := map[string]interface{}{
|
payload := map[string]interface{}{
|
||||||
"filepath": path,
|
"filepath": path,
|
||||||
"status": "detected",
|
"status": "detected",
|
||||||
}
|
}
|
||||||
|
|
||||||
packet := network.NewPacket(alertType, payload)
|
packet := network.NewPacket(alertType, payload)
|
||||||
|
|
||||||
s.client.SendQueue(packet)
|
s.client.SendQueue(packet)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Scanner) Stop() {
|
func (s *Scanner) Stop() {
|
||||||
log.Println("[扫描器] 停止文件完整性扫描...")
|
log.Println("[扫描器] 停止文件完整性扫描...")
|
||||||
close(s.stopChan)
|
close(s.stopChan)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,119 +1,119 @@
|
||||||
package scanner
|
package scanner
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/fsnotify/fsnotify"
|
"github.com/fsnotify/fsnotify"
|
||||||
"github.com/wuko233/sysmonitord/internal/network"
|
"github.com/wuko233/sysmonitord/internal/network"
|
||||||
"github.com/wuko233/sysmonitord/internal/whitelist"
|
"github.com/wuko233/sysmonitord/internal/whitelist"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Watcher struct {
|
type Watcher struct {
|
||||||
wlManager *whitelist.Manager
|
wlManager *whitelist.Manager
|
||||||
client *network.WSClient
|
client *network.WSClient
|
||||||
watcher *fsnotify.Watcher
|
watcher *fsnotify.Watcher
|
||||||
stopChan chan struct{}
|
stopChan chan struct{}
|
||||||
watchPaths []string
|
watchPaths []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWatcher(wl *whitelist.Manager, client *network.WSClient) (*Watcher, error) {
|
func NewWatcher(wl *whitelist.Manager, client *network.WSClient) (*Watcher, error) {
|
||||||
fsWatch, err := fsnotify.NewWatcher()
|
fsWatch, err := fsnotify.NewWatcher()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Watcher{
|
return &Watcher{
|
||||||
wlManager: wl,
|
wlManager: wl,
|
||||||
client: client,
|
client: client,
|
||||||
watcher: fsWatch,
|
watcher: fsWatch,
|
||||||
stopChan: make(chan struct{}),
|
stopChan: make(chan struct{}),
|
||||||
|
|
||||||
// TODO: 当前仅实现对主目录的监控,后续实现递归监控子目录
|
// TODO: 当前仅实现对主目录的监控,后续实现递归监控子目录
|
||||||
watchPaths: []string{
|
watchPaths: []string{
|
||||||
"/bin", "/sbin", "/usr/bin", "/etc/init.d", "/tmp",
|
"/bin", "/sbin", "/usr/bin", "/etc/init.d", "/tmp",
|
||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Watcher) Start() {
|
func (w *Watcher) Start() {
|
||||||
log.Println("[监听器] 启动实时文件监控...")
|
log.Println("[监听器] 启动实时文件监控...")
|
||||||
|
|
||||||
for _, path := range w.watchPaths {
|
for _, path := range w.watchPaths {
|
||||||
if _, err := os.Stat(path); err == nil {
|
if _, err := os.Stat(path); err == nil {
|
||||||
if err := w.watcher.Add(path); err != nil {
|
if err := w.watcher.Add(path); err != nil {
|
||||||
log.Printf("[监听器] 无法监控路径 %s: %v", path, err)
|
log.Printf("[监听器] 无法监控路径 %s: %v", path, err)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("[监听器] 开始监控路径: %s", path)
|
log.Printf("[监听器] 开始监控路径: %s", path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
go w.eventLoop()
|
go w.eventLoop()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Watcher) eventLoop() {
|
func (w *Watcher) eventLoop() {
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-w.stopChan:
|
case <-w.stopChan:
|
||||||
return
|
return
|
||||||
case event, ok := <-w.watcher.Events:
|
case event, ok := <-w.watcher.Events:
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if event.Has(fsnotify.Create) || event.Has(fsnotify.Write) {
|
if event.Has(fsnotify.Create) || event.Has(fsnotify.Write) {
|
||||||
if w.wlManager.IsPathIgnored(event.Name) {
|
if w.wlManager.IsPathIgnored(event.Name) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
go w.handleFileChange(event.Name, event.Op.String())
|
go w.handleFileChange(event.Name, event.Op.String())
|
||||||
}
|
}
|
||||||
case err, ok := <-w.watcher.Errors:
|
case err, ok := <-w.watcher.Errors:
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.Printf("[监听器] 错误: %v", err)
|
log.Printf("[监听器] 错误: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Watcher) handleFileChange(path string, op string) {
|
func (w *Watcher) handleFileChange(path string, op string) {
|
||||||
|
|
||||||
time.Sleep(200 * time.Millisecond) // 等待文件写入完成
|
time.Sleep(200 * time.Millisecond) // 等待文件写入完成
|
||||||
|
|
||||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
isWhitelisted, isHashMatch, err := w.wlManager.CheckFileStatus(path)
|
isWhitelisted, isHashMatch, err := w.wlManager.CheckFileStatus(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isWhitelisted {
|
if !isWhitelisted {
|
||||||
log.Printf("[监听器] 实时拦截:检测到非白名单文件变动 (%s): %s", op, path)
|
log.Printf("[监听器] 实时拦截:检测到非白名单文件变动 (%s): %s", op, path)
|
||||||
w.reportEvent(path, "REALTIME_FILE_ALERT", op)
|
w.reportEvent(path, "REALTIME_FILE_ALERT", op)
|
||||||
} else if !isHashMatch {
|
} else if !isHashMatch {
|
||||||
log.Printf("[监听器] 实时拦截:检测到白名单文件被篡改 (%s): %s", op, path)
|
log.Printf("[监听器] 实时拦截:检测到白名单文件被篡改 (%s): %s", op, path)
|
||||||
w.reportEvent(path, "REALTIME_HASH_MISMATCH", op)
|
w.reportEvent(path, "REALTIME_HASH_MISMATCH", op)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Watcher) reportEvent(path, alertType, op string) {
|
func (w *Watcher) reportEvent(path, alertType, op string) {
|
||||||
payload := map[string]interface{}{
|
payload := map[string]interface{}{
|
||||||
"filepath": path,
|
"filepath": path,
|
||||||
"operation": op,
|
"operation": op,
|
||||||
"time": time.Now(),
|
"time": time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
packet := network.NewPacket(alertType, payload)
|
packet := network.NewPacket(alertType, payload)
|
||||||
w.client.SendQueue(packet)
|
w.client.SendQueue(packet)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Watcher) Stop() {
|
func (w *Watcher) Stop() {
|
||||||
log.Println("[监听器] 停止实时文件监控...")
|
log.Println("[监听器] 停止实时文件监控...")
|
||||||
close(w.stopChan)
|
close(w.stopChan)
|
||||||
w.watcher.Close()
|
w.watcher.Close()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,135 +1,135 @@
|
||||||
package whitelist
|
package whitelist
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/wuko233/sysmonitord/internal/config"
|
"github.com/wuko233/sysmonitord/internal/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
official config.OfficialConfig
|
official config.OfficialConfig
|
||||||
user config.UserConfig
|
user config.UserConfig
|
||||||
mergedIgnore []string
|
mergedIgnore []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewManager() *Manager {
|
func NewManager() *Manager {
|
||||||
return &Manager{
|
return &Manager{
|
||||||
official: config.OfficialConfig{
|
official: config.OfficialConfig{
|
||||||
WhitelistFiles: make(map[string][]string),
|
WhitelistFiles: make(map[string][]string),
|
||||||
},
|
},
|
||||||
user: config.UserConfig{
|
user: config.UserConfig{
|
||||||
SupplementFiles: make(map[string][]string),
|
SupplementFiles: make(map[string][]string),
|
||||||
SupplementProcesses: make(map[string]string),
|
SupplementProcesses: make(map[string]string),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) UpdateConfig(official config.OfficialConfig, user config.UserConfig) {
|
func (m *Manager) UpdateConfig(official config.OfficialConfig, user config.UserConfig) {
|
||||||
m.mu.Lock()
|
m.mu.Lock()
|
||||||
defer m.mu.Unlock()
|
defer m.mu.Unlock()
|
||||||
|
|
||||||
m.official = official
|
m.official = official
|
||||||
m.user = user
|
m.user = user
|
||||||
|
|
||||||
m.mergedIgnore = append([]string{}, m.official.IgnoredPaths...)
|
m.mergedIgnore = append([]string{}, m.official.IgnoredPaths...)
|
||||||
m.mergedIgnore = append(m.mergedIgnore, m.user.IgnoredPaths...)
|
m.mergedIgnore = append(m.mergedIgnore, m.user.IgnoredPaths...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) IsPathIgnored(path string) bool {
|
func (m *Manager) IsPathIgnored(path string) bool {
|
||||||
m.mu.RLock()
|
m.mu.RLock()
|
||||||
defer m.mu.RUnlock()
|
defer m.mu.RUnlock()
|
||||||
|
|
||||||
return m.IsPathIgnoredUnsafe(path)
|
return m.IsPathIgnoredUnsafe(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) IsPathIgnoredUnsafe(path string) bool {
|
func (m *Manager) IsPathIgnoredUnsafe(path string) bool {
|
||||||
path = filepath.Clean(path)
|
path = filepath.Clean(path)
|
||||||
for _, ignore := range m.mergedIgnore {
|
for _, ignore := range m.mergedIgnore {
|
||||||
if strings.HasPrefix(path, filepath.Clean(ignore)) {
|
if strings.HasPrefix(path, filepath.Clean(ignore)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckFileStatus 检查文件状态
|
// CheckFileStatus 检查文件状态
|
||||||
// 返回: isWhitelisted(是否在白名单), isValid(Hash是否匹配), err
|
// 返回: isWhitelisted(是否在白名单), isValid(Hash是否匹配), err
|
||||||
func (m *Manager) CheckFileStatus(path string) (bool, bool, error) {
|
func (m *Manager) CheckFileStatus(path string) (bool, bool, error) {
|
||||||
|
|
||||||
m.mu.RLock()
|
m.mu.RLock()
|
||||||
defer m.mu.RUnlock()
|
defer m.mu.RUnlock()
|
||||||
|
|
||||||
if m.IsPathIgnoredUnsafe((path)) {
|
if m.IsPathIgnoredUnsafe((path)) {
|
||||||
return true, true, nil
|
return true, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
hashes, exists := m.official.WhitelistFiles[path]
|
hashes, exists := m.official.WhitelistFiles[path]
|
||||||
if !exists {
|
if !exists {
|
||||||
hashes, exists = m.user.SupplementFiles[path]
|
hashes, exists = m.user.SupplementFiles[path]
|
||||||
}
|
}
|
||||||
if !exists {
|
if !exists {
|
||||||
return false, false, nil
|
return false, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
fileHash, err := CalculateFileHash(path)
|
fileHash, err := CalculateFileHash(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return true, false, fmt.Errorf("计算文件哈希失败: %v", err)
|
return true, false, fmt.Errorf("计算文件哈希失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, h := range hashes {
|
for _, h := range hashes {
|
||||||
if strings.EqualFold(h, fmt.Sprintf("%v", fileHash)) {
|
if strings.EqualFold(h, fmt.Sprintf("%v", fileHash)) {
|
||||||
return true, true, nil
|
return true, true, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true, false, nil
|
return true, false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CalculateFileHash(filePath string) (string, error) {
|
func CalculateFileHash(filePath string) (string, error) {
|
||||||
file, err := os.Open(filePath)
|
file, err := os.Open(filePath)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
hash := sha256.New()
|
hash := sha256.New()
|
||||||
if _, err := io.Copy(hash, file); err != nil {
|
if _, err := io.Copy(hash, file); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return hex.EncodeToString(hash.Sum(nil)), nil
|
return hex.EncodeToString(hash.Sum(nil)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) IsProcessAllowed(procName string, cmdLine string) bool {
|
func (m *Manager) IsProcessAllowed(procName string, cmdLine string) bool {
|
||||||
m.mu.RLock()
|
m.mu.RLock()
|
||||||
defer m.mu.RUnlock()
|
defer m.mu.RUnlock()
|
||||||
|
|
||||||
for _, p := range m.official.WhitelistProcesses {
|
for _, p := range m.official.WhitelistProcesses {
|
||||||
if p == procName {
|
if p == procName {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := m.user.SupplementProcesses[procName]; ok {
|
if _, ok := m.user.SupplementProcesses[procName]; ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) GetAuditServerUrl() string {
|
func (m *Manager) GetAuditServerUrl() string {
|
||||||
m.mu.RLock()
|
m.mu.RLock()
|
||||||
defer m.mu.RUnlock()
|
defer m.mu.RUnlock()
|
||||||
|
|
||||||
return m.user.AuditServerUrl
|
return m.user.AuditServerUrl
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Reference in New Issue
Block a user