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