Files
Aluer/src/main/java/com/aluer/security/FileIntegrityMonitorService.java
T
2026-05-10 12:59:00 +08:00

202 lines
7.4 KiB
Java

package com.aluer.security;
import com.aluer.config.ServerGuardConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.PathMatcher;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
@Service
public class FileIntegrityMonitorService {
private final ServerGuardConfig config;
private final Map<String, String> baselineHashes = new ConcurrentHashMap<>();
private final ConcurrentLinkedQueue<IntegrityAlert> alerts = new ConcurrentLinkedQueue<>();
private volatile long lastScanTime = 0L;
public FileIntegrityMonitorService() {
this(new ServerGuardConfig());
}
@Autowired
public FileIntegrityMonitorService(ServerGuardConfig config) {
this.config = config;
}
public synchronized Map<String, Object> scanNow() {
if (!config.getSecurity().getAntiIntrusion().getFileIntegrity().isEnabled()) {
return Map.of("enabled", false, "trackedFiles", baselineHashes.size(), "alerts", 0);
}
Map<String, String> currentHashes = collectCurrentHashes();
int newAlerts = 0;
if (baselineHashes.isEmpty()) {
baselineHashes.putAll(currentHashes);
} else {
for (Map.Entry<String, String> entry : currentHashes.entrySet()) {
String oldHash = baselineHashes.get(entry.getKey());
if (oldHash == null) {
alerts.offer(new IntegrityAlert("CREATED", entry.getKey(), "New monitored file discovered", Instant.now().toEpochMilli()));
newAlerts++;
} else if (!oldHash.equals(entry.getValue())) {
alerts.offer(new IntegrityAlert("MODIFIED", entry.getKey(), "File hash changed", Instant.now().toEpochMilli()));
newAlerts++;
}
}
for (String tracked : new ArrayList<>(baselineHashes.keySet())) {
if (!currentHashes.containsKey(tracked)) {
alerts.offer(new IntegrityAlert("DELETED", tracked, "Monitored file disappeared", Instant.now().toEpochMilli()));
newAlerts++;
}
}
baselineHashes.clear();
baselineHashes.putAll(currentHashes);
}
trimAlerts();
lastScanTime = System.currentTimeMillis();
Map<String, Object> result = new LinkedHashMap<>();
result.put("enabled", true);
result.put("trackedFiles", baselineHashes.size());
result.put("newAlerts", newAlerts);
result.put("lastScanTime", lastScanTime);
return result;
}
public synchronized Map<String, Object> rebuildBaseline() {
baselineHashes.clear();
Map<String, String> hashes = collectCurrentHashes();
baselineHashes.putAll(hashes);
lastScanTime = System.currentTimeMillis();
Map<String, Object> result = new LinkedHashMap<>();
result.put("baselineFiles", baselineHashes.size());
result.put("lastScanTime", lastScanTime);
return result;
}
public Map<String, Object> getBaselineStatus() {
Map<String, Object> result = new LinkedHashMap<>();
result.put("enabled", config.getSecurity().getAntiIntrusion().getFileIntegrity().isEnabled());
result.put("trackedFiles", baselineHashes.size());
result.put("alerts", alerts.size());
result.put("lastScanTime", lastScanTime);
result.put("paths", config.getSecurity().getAntiIntrusion().getFileIntegrity().getMonitoredPaths());
return result;
}
public List<IntegrityAlert> getRecentAlerts(int limit) {
List<IntegrityAlert> result = new ArrayList<>();
int count = 0;
for (IntegrityAlert alert : alerts) {
if (count++ >= limit) {
break;
}
result.add(alert);
}
result.sort(Comparator.comparingLong(IntegrityAlert::getTimestamp).reversed());
return result;
}
private Map<String, String> collectCurrentHashes() {
Map<String, String> hashes = new HashMap<>();
for (String configuredPath : config.getSecurity().getAntiIntrusion().getFileIntegrity().getMonitoredPaths()) {
hashes.putAll(resolvePathHashes(configuredPath));
}
return hashes;
}
private Map<String, String> resolvePathHashes(String configuredPath) {
Map<String, String> hashes = new HashMap<>();
Path path = Paths.get(configuredPath);
try {
if (configuredPath.contains("*")) {
Path parent = path.getParent();
if (parent != null && Files.isDirectory(parent)) {
PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:" + configuredPath);
try (var stream = Files.walk(parent, 1)) {
stream.filter(Files::isRegularFile)
.filter(matcher::matches)
.forEach(file -> hashes.put(file.toString(), sha256(file)));
}
}
return hashes;
}
if (Files.isDirectory(path)) {
int maxDepth = config.getSecurity().getAntiIntrusion().getFileIntegrity().getMaxDepth();
try (var stream = Files.walk(path, maxDepth)) {
stream.filter(Files::isRegularFile)
.forEach(file -> hashes.put(file.toString(), sha256(file)));
}
} else if (Files.isRegularFile(path)) {
hashes.put(path.toString(), sha256(path));
}
} catch (IOException ignored) {
}
return hashes;
}
private String sha256(Path file) {
try {
MessageDigest digest = MessageDigest.getInstance("SHA-256");
byte[] bytes = Files.readAllBytes(file);
byte[] hash = digest.digest(bytes);
StringBuilder hex = new StringBuilder();
for (byte b : hash) {
hex.append(String.format("%02x", b));
}
return hex.toString();
} catch (IOException | NoSuchAlgorithmException e) {
return "unavailable";
}
}
private void trimAlerts() {
while (alerts.size() > 1000) {
alerts.poll();
}
}
public static class IntegrityAlert {
private final String type;
private final String path;
private final String details;
private final long timestamp;
public IntegrityAlert(String type, String path, String details, long timestamp) {
this.type = type;
this.path = path;
this.details = details;
this.timestamp = timestamp;
}
public String getType() { return type; }
public String getPath() { return path; }
public String getDetails() { return details; }
public long getTimestamp() { return timestamp; }
}
}