提交Git
This commit is contained in:
@@ -0,0 +1,316 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
@Component
|
||||
public class APIRateLimitService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(APIRateLimitService.class);
|
||||
|
||||
private final Map<String, RateLimitRule> rules = new ConcurrentHashMap<>();
|
||||
private final Map<String, ClientRateLimit> clientLimits = new ConcurrentHashMap<>();
|
||||
private final Queue<RateLimitEvent> eventLog = new ConcurrentLinkedQueue<>();
|
||||
private final AtomicLong totalRequests = new AtomicLong(0);
|
||||
private final AtomicLong limitedRequests = new AtomicLong(0);
|
||||
private final AtomicLong blockedRequests = new AtomicLong(0);
|
||||
|
||||
private static final int DEFAULT_LIMIT_PER_MINUTE = 60;
|
||||
private static final int DEFAULT_LIMIT_PER_HOUR = 1000;
|
||||
private static final int DEFAULT_LIMIT_PER_DAY = 10000;
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
public APIRateLimitService() {
|
||||
initializeDefaultRules();
|
||||
logger.info("API Rate Limit Service initialized");
|
||||
}
|
||||
|
||||
private void initializeDefaultRules() {
|
||||
addRule("default", DEFAULT_LIMIT_PER_MINUTE, DEFAULT_LIMIT_PER_HOUR, DEFAULT_LIMIT_PER_DAY);
|
||||
addRule("login", 10, 50, 100);
|
||||
addRule("register", 5, 20, 50);
|
||||
addRule("message", 30, 200, 500);
|
||||
addRule("file-upload", 10, 50, 100);
|
||||
addRule("api-critical", 100, 500, 2000);
|
||||
|
||||
logger.info("Initialized {} rate limit rules", rules.size());
|
||||
}
|
||||
|
||||
public void addRule(String name, int limitPerMinute, int limitPerHour, int limitPerDay) {
|
||||
RateLimitRule rule = new RateLimitRule(name, limitPerMinute, limitPerHour, limitPerDay);
|
||||
rules.put(name, rule);
|
||||
}
|
||||
|
||||
public RateLimitResult checkRateLimit(String clientId, String endpoint) {
|
||||
RateLimitResult result = new RateLimitResult();
|
||||
result.setClientId(clientId);
|
||||
result.setEndpoint(endpoint);
|
||||
result.setTimestamp(LocalDateTime.now());
|
||||
|
||||
totalRequests.incrementAndGet();
|
||||
|
||||
ClientRateLimit clientLimit = clientLimits.computeIfAbsent(clientId, k -> new ClientRateLimit(clientId));
|
||||
|
||||
RateLimitRule rule = rules.getOrDefault(endpoint, rules.get("default"));
|
||||
|
||||
if (clientLimit.isPermanentlyBlocked()) {
|
||||
result.setAllowed(false);
|
||||
result.setReason("Client is permanently blocked");
|
||||
result.setBlocked(true);
|
||||
blockedRequests.incrementAndGet();
|
||||
logEvent(clientId, endpoint, "BLOCKED", "Permanently blocked");
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!clientLimit.checkLimit(rule)) {
|
||||
result.setAllowed(false);
|
||||
result.setReason("Rate limit exceeded");
|
||||
result.setLimited(true);
|
||||
limitedRequests.incrementAndGet();
|
||||
clientLimit.incrementViolationCount();
|
||||
|
||||
if (clientLimit.getViolationCount() >= 10) {
|
||||
clientLimit.setPermanentlyBlocked(true);
|
||||
blockedRequests.incrementAndGet();
|
||||
logEvent(clientId, endpoint, "PERMANENT_BLOCK", "Too many violations");
|
||||
} else {
|
||||
logEvent(clientId, endpoint, "LIMITED", "Rate limit exceeded");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
clientLimit.recordRequest();
|
||||
result.setAllowed(true);
|
||||
result.setRemainingMinute(clientLimit.getRemainingRequests("minute"));
|
||||
result.setRemainingHour(clientLimit.getRemainingRequests("hour"));
|
||||
result.setRemainingDay(clientLimit.getRemainingRequests("day"));
|
||||
|
||||
logEvent(clientId, endpoint, "ALLOWED", "Request allowed");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void unblockClient(String clientId) {
|
||||
ClientRateLimit clientLimit = clientLimits.get(clientId);
|
||||
if (clientLimit != null) {
|
||||
clientLimit.reset();
|
||||
logger.info("Client {} has been unblocked", clientId);
|
||||
}
|
||||
}
|
||||
|
||||
public void setClientLimit(String clientId, int minuteLimit, int hourLimit, int dayLimit) {
|
||||
ClientRateLimit clientLimit = clientLimits.computeIfAbsent(clientId, k -> new ClientRateLimit(clientId));
|
||||
clientLimit.setCustomLimits(minuteLimit, hourLimit, dayLimit);
|
||||
}
|
||||
|
||||
public Map<String, Object> getStats() {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("totalRequests", totalRequests.get());
|
||||
stats.put("limitedRequests", limitedRequests.get());
|
||||
stats.put("blockedRequests", blockedRequests.get());
|
||||
stats.put("activeClients", clientLimits.size());
|
||||
stats.put("rulesConfigured", rules.size());
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
public Map<String, ClientRateLimit> getClients() {
|
||||
return new HashMap<>(clientLimits);
|
||||
}
|
||||
|
||||
public List<RateLimitEvent> getRecentEvents(int limit) {
|
||||
List<RateLimitEvent> events = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (RateLimitEvent event : eventLog) {
|
||||
if (count++ >= limit) break;
|
||||
events.add(event);
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
private void logEvent(String clientId, String endpoint, String action, String details) {
|
||||
RateLimitEvent event = new RateLimitEvent(clientId, endpoint, action, details, LocalDateTime.now());
|
||||
eventLog.offer(event);
|
||||
if (eventLog.size() > 5000) {
|
||||
eventLog.poll();
|
||||
}
|
||||
}
|
||||
|
||||
public static class RateLimitRule {
|
||||
private final String name;
|
||||
private final int limitPerMinute;
|
||||
private final int limitPerHour;
|
||||
private final int limitPerDay;
|
||||
|
||||
public RateLimitRule(String name, int limitPerMinute, int limitPerHour, int limitPerDay) {
|
||||
this.name = name;
|
||||
this.limitPerMinute = limitPerMinute;
|
||||
this.limitPerHour = limitPerHour;
|
||||
this.limitPerDay = limitPerDay;
|
||||
}
|
||||
|
||||
public String getName() { return name; }
|
||||
public int getLimitPerMinute() { return limitPerMinute; }
|
||||
public int getLimitPerHour() { return limitPerHour; }
|
||||
public int getLimitPerDay() { return limitPerDay; }
|
||||
}
|
||||
|
||||
public static class ClientRateLimit {
|
||||
private final String clientId;
|
||||
private volatile int requestsThisMinute = 0;
|
||||
private volatile int requestsThisHour = 0;
|
||||
private volatile int requestsThisDay = 0;
|
||||
private volatile long minuteStartTime;
|
||||
private volatile long hourStartTime;
|
||||
private volatile long dayStartTime;
|
||||
private volatile int violationCount = 0;
|
||||
private volatile boolean permanentlyBlocked = false;
|
||||
private Integer customMinuteLimit;
|
||||
private Integer customHourLimit;
|
||||
private Integer customDayLimit;
|
||||
|
||||
public ClientRateLimit(String clientId) {
|
||||
this.clientId = clientId;
|
||||
this.minuteStartTime = System.currentTimeMillis();
|
||||
this.hourStartTime = System.currentTimeMillis();
|
||||
this.dayStartTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public synchronized boolean checkLimit(RateLimitRule rule) {
|
||||
resetIfNeeded();
|
||||
|
||||
int minuteLimit = customMinuteLimit != null ? customMinuteLimit : rule.getLimitPerMinute();
|
||||
int hourLimit = customHourLimit != null ? customHourLimit : rule.getLimitPerHour();
|
||||
int dayLimit = customDayLimit != null ? customDayLimit : rule.getLimitPerDay();
|
||||
|
||||
return requestsThisMinute < minuteLimit &&
|
||||
requestsThisHour < hourLimit &&
|
||||
requestsThisDay < dayLimit;
|
||||
}
|
||||
|
||||
public synchronized void recordRequest() {
|
||||
resetIfNeeded();
|
||||
requestsThisMinute++;
|
||||
requestsThisHour++;
|
||||
requestsThisDay++;
|
||||
}
|
||||
|
||||
private void resetIfNeeded() {
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
if (now - minuteStartTime > 60000) {
|
||||
requestsThisMinute = 0;
|
||||
minuteStartTime = now;
|
||||
}
|
||||
|
||||
if (now - hourStartTime > 3600000) {
|
||||
requestsThisHour = 0;
|
||||
hourStartTime = now;
|
||||
}
|
||||
|
||||
if (now - dayStartTime > 86400000) {
|
||||
requestsThisDay = 0;
|
||||
dayStartTime = now;
|
||||
}
|
||||
}
|
||||
|
||||
public int getRemainingRequests(String timeframe) {
|
||||
resetIfNeeded();
|
||||
RateLimitRule rule = new RateLimitRule("default", 60, 1000, 10000);
|
||||
int minuteLimit = customMinuteLimit != null ? customMinuteLimit : rule.getLimitPerMinute();
|
||||
int hourLimit = customHourLimit != null ? customHourLimit : rule.getLimitPerHour();
|
||||
int dayLimit = customDayLimit != null ? customDayLimit : rule.getLimitPerDay();
|
||||
|
||||
switch (timeframe) {
|
||||
case "minute": return Math.max(0, minuteLimit - requestsThisMinute);
|
||||
case "hour": return Math.max(0, hourLimit - requestsThisHour);
|
||||
case "day": return Math.max(0, dayLimit - requestsThisDay);
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public void incrementViolationCount() { violationCount++; }
|
||||
public void reset() {
|
||||
violationCount = 0;
|
||||
permanentlyBlocked = false;
|
||||
requestsThisMinute = 0;
|
||||
requestsThisHour = 0;
|
||||
requestsThisDay = 0;
|
||||
}
|
||||
|
||||
public void setCustomLimits(int minute, int hour, int day) {
|
||||
this.customMinuteLimit = minute;
|
||||
this.customHourLimit = hour;
|
||||
this.customDayLimit = day;
|
||||
}
|
||||
|
||||
public String getClientId() { return clientId; }
|
||||
public int getViolationCount() { return violationCount; }
|
||||
public boolean isPermanentlyBlocked() { return permanentlyBlocked; }
|
||||
public void setPermanentlyBlocked(boolean blocked) { this.permanentlyBlocked = blocked; }
|
||||
}
|
||||
|
||||
public static class RateLimitResult {
|
||||
private String clientId;
|
||||
private String endpoint;
|
||||
private boolean allowed;
|
||||
private boolean limited;
|
||||
private boolean blocked;
|
||||
private String reason;
|
||||
private int remainingMinute;
|
||||
private int remainingHour;
|
||||
private int remainingDay;
|
||||
private LocalDateTime timestamp;
|
||||
|
||||
public String getClientId() { return clientId; }
|
||||
public void setClientId(String clientId) { this.clientId = clientId; }
|
||||
public String getEndpoint() { return endpoint; }
|
||||
public void setEndpoint(String endpoint) { this.endpoint = endpoint; }
|
||||
public boolean isAllowed() { return allowed; }
|
||||
public void setAllowed(boolean allowed) { this.allowed = allowed; }
|
||||
public boolean isLimited() { return limited; }
|
||||
public void setLimited(boolean limited) { this.limited = limited; }
|
||||
public boolean isBlocked() { return blocked; }
|
||||
public void setBlocked(boolean blocked) { this.blocked = blocked; }
|
||||
public String getReason() { return reason; }
|
||||
public void setReason(String reason) { this.reason = reason; }
|
||||
public int getRemainingMinute() { return remainingMinute; }
|
||||
public void setRemainingMinute(int remaining) { this.remainingMinute = remaining; }
|
||||
public int getRemainingHour() { return remainingHour; }
|
||||
public void setRemainingHour(int remaining) { this.remainingHour = remaining; }
|
||||
public int getRemainingDay() { return remainingDay; }
|
||||
public void setRemainingDay(int remaining) { this.remainingDay = remaining; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
public void setTimestamp(LocalDateTime timestamp) { this.timestamp = timestamp; }
|
||||
}
|
||||
|
||||
public static class RateLimitEvent {
|
||||
private final String clientId;
|
||||
private final String endpoint;
|
||||
private final String action;
|
||||
private final String details;
|
||||
private final LocalDateTime timestamp;
|
||||
|
||||
public RateLimitEvent(String clientId, String endpoint, String action, String details, LocalDateTime timestamp) {
|
||||
this.clientId = clientId;
|
||||
this.endpoint = endpoint;
|
||||
this.action = action;
|
||||
this.details = details;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getClientId() { return clientId; }
|
||||
public String getEndpoint() { return endpoint; }
|
||||
public String getAction() { return action; }
|
||||
public String getDetails() { return details; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,438 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
@Component
|
||||
public class AdvancedMalwareDetectionService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(AdvancedMalwareDetectionService.class);
|
||||
|
||||
private final Map<String, MalwareSignature> signatures = new ConcurrentHashMap<>();
|
||||
private final Map<String, BehavioralProfile> behavioralProfiles = new ConcurrentHashMap<>();
|
||||
private final Queue<MalwareAlert> alertQueue = new ConcurrentLinkedQueue<>();
|
||||
private final Map<String, List<HeuristicResult>> scanHistory = new ConcurrentHashMap<>();
|
||||
private final AtomicLong totalScans = new AtomicLong(0);
|
||||
private final AtomicLong threatsDetected = new AtomicLong(0);
|
||||
|
||||
private static final int MAX_SCAN_HISTORY = 1000;
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
public AdvancedMalwareDetectionService() {
|
||||
initializeSignatures();
|
||||
logger.info("Advanced Malware Detection Service initialized");
|
||||
}
|
||||
|
||||
private void initializeSignatures() {
|
||||
addSignature("WIN32_TROJAN_GENERIC", "Trojan.Generic", "HIGH", "eicar_test_file", "Known trojan signature");
|
||||
addSignature("WIN32_WORM_CONFICKER", "Worm.Conficker", "CRITICAL", "conficker_.*", "Conficker worm");
|
||||
addSignature("WIN32_RANSOMWARE_CRYPTO", "Ransomware.CryptoLocker", "CRITICAL", "crypto_locker", "CryptoLocker ransomware");
|
||||
addSignature("WIN32_BACKDOOR_TITAN", "Backdoor.Titan", "HIGH", "titan_.*", "Titan backdoor");
|
||||
addSignature("WIN64_ROOTKIT_FLYPRO", "Rootkit.FlyPro", "HIGH", "flypro_.*", "FlyPro rootkit");
|
||||
addSignature("WIN32_ADWARE_YOUNEE", "Adware.YooNear", "LOW", "yoonee_.*", "Adware component");
|
||||
addSignature("WIN32_KEYLOGGER_SPYWARE", "Keylogger.SpyWare", "HIGH", "keylog.*", "Keylogger detection");
|
||||
addSignature("WIN32_BOTNET_ZOMBIE", "Botnet.Zombie", "HIGH", "zombie_.*", "Botnet zombie");
|
||||
addSignature("WIN32_MINER_COIN", "Miner.CoinHive", "MEDIUM", "coinhive.*", "Cryptocurrency miner");
|
||||
addSignature("WIN32_EXPLOIT_KIT", "ExploitKit.EK", "HIGH", "exploit_.*", "Exploit kit");
|
||||
|
||||
addSignature("LINUX_MALWARE_MIRAI", "Linux.Mirai", "CRITICAL", "mirai_.*", "Mirai botnet");
|
||||
addSignature("LINUX_ROOTKIT_DIAMONDER", "Linux.Rootkit.Diamond", "HIGH", "diamond_.*", "Diamond rootkit");
|
||||
addSignature("LINUX_MINER_LINUX", "Linux.Miner.Xmrig", "MEDIUM", "xmrig.*", "XMRig miner");
|
||||
addSignature("LINUX_BACKDOOR_BEAST", "Linux.Backdoor.Beast", "HIGH", "beast_.*", "Beast backdoor");
|
||||
|
||||
logger.info("Initialized {} malware signatures", signatures.size());
|
||||
}
|
||||
|
||||
public void addSignature(String id, String name, String severity, String pattern, String description) {
|
||||
MalwareSignature sig = new MalwareSignature(id, name, severity, pattern, description);
|
||||
signatures.put(id, sig);
|
||||
}
|
||||
|
||||
public ScanResult scanFile(String fileName, byte[] fileContent, String fileHash) {
|
||||
ScanResult result = new ScanResult();
|
||||
result.setFileName(fileName);
|
||||
result.setFileHash(fileHash);
|
||||
result.setTimestamp(LocalDateTime.now());
|
||||
|
||||
totalScans.incrementAndGet();
|
||||
|
||||
MalwareSignature matchedSig = matchSignature(fileHash, fileName);
|
||||
if (matchedSig != null) {
|
||||
result.setThreatDetected(true);
|
||||
result.setThreatName(matchedSig.getName());
|
||||
result.setThreatSeverity(matchedSig.getSeverity());
|
||||
result.setThreatDescription(matchedSig.getDescription());
|
||||
threatsDetected.incrementAndGet();
|
||||
|
||||
triggerAlert(fileName, matchedSig);
|
||||
logger.warn("MALWARE DETECTED: {} in file {}", matchedSig.getName(), fileName);
|
||||
return result;
|
||||
}
|
||||
|
||||
List<HeuristicResult> heuristics = runHeuristicAnalysis(fileContent, fileName);
|
||||
for (HeuristicResult hr : heuristics) {
|
||||
if (hr.isSuspicious()) {
|
||||
result.addHeuristicResult(hr);
|
||||
result.setThreatDetected(true);
|
||||
result.setThreatName("Heuristic Detection");
|
||||
result.setThreatSeverity("MEDIUM");
|
||||
threatsDetected.incrementAndGet();
|
||||
}
|
||||
}
|
||||
|
||||
List<HeuristicResult> history = scanHistory.computeIfAbsent(fileHash, k -> new ArrayList<>());
|
||||
history.addAll(heuristics);
|
||||
if (history.size() > MAX_SCAN_HISTORY) {
|
||||
history.subList(0, history.size() - MAX_SCAN_HISTORY).clear();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private MalwareSignature matchSignature(String fileHash, String fileName) {
|
||||
for (MalwareSignature sig : signatures.values()) {
|
||||
try {
|
||||
Pattern pattern = Pattern.compile(sig.getPattern(), Pattern.CASE_INSENSITIVE);
|
||||
if (pattern.matcher(fileHash).matches() || pattern.matcher(fileName).matches()) {
|
||||
return sig;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<HeuristicResult> runHeuristicAnalysis(byte[] content, String fileName) {
|
||||
List<HeuristicResult> results = new ArrayList<>();
|
||||
|
||||
if (content == null || content.length == 0) {
|
||||
return results;
|
||||
}
|
||||
|
||||
HeuristicResult entropyResult = analyzeEntropy(content);
|
||||
if (entropyResult != null) {
|
||||
results.add(entropyResult);
|
||||
}
|
||||
|
||||
HeuristicResult packingResult = analyzePacking(content);
|
||||
if (packingResult != null) {
|
||||
results.add(packingResult);
|
||||
}
|
||||
|
||||
HeuristicResult suspiciousStringsResult = analyzeSuspiciousStrings(content);
|
||||
if (suspiciousStringsResult != null) {
|
||||
results.add(suspiciousStringsResult);
|
||||
}
|
||||
|
||||
HeuristicResult suspiciousPatternsResult = analyzeSuspiciousPatterns(content);
|
||||
if (suspiciousPatternsResult != null) {
|
||||
results.add(suspiciousPatternsResult);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private HeuristicResult analyzeEntropy(byte[] content) {
|
||||
double entropy = calculateEntropy(content);
|
||||
|
||||
HeuristicResult result = new HeuristicResult();
|
||||
result.setType("ENTROPY");
|
||||
result.setScore((int)(entropy * 100));
|
||||
|
||||
if (entropy > 7.5) {
|
||||
result.setSuspicious(true);
|
||||
result.setReason("Very high entropy: " + String.format("%.2f", entropy) + " (possible encryption or packing)");
|
||||
} else if (entropy > 6.5) {
|
||||
result.setSuspicious(true);
|
||||
result.setReason("High entropy: " + String.format("%.2f", entropy) + " (possible obfuscation)");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private double calculateEntropy(byte[] data) {
|
||||
if (data == null || data.length == 0) return 0;
|
||||
|
||||
int[] frequency = new int[256];
|
||||
for (byte b : data) {
|
||||
frequency[b & 0xFF]++;
|
||||
}
|
||||
|
||||
double entropy = 0;
|
||||
int length = data.length;
|
||||
for (int count : frequency) {
|
||||
if (count > 0) {
|
||||
double probability = (double) count / length;
|
||||
entropy -= probability * (Math.log(probability) / Math.log(2));
|
||||
}
|
||||
}
|
||||
|
||||
return entropy;
|
||||
}
|
||||
|
||||
private HeuristicResult analyzePacking(byte[] content) {
|
||||
HeuristicResult result = new HeuristicResult();
|
||||
result.setType("PACKING");
|
||||
|
||||
int nullBytes = 0;
|
||||
int nonAscii = 0;
|
||||
|
||||
for (int i = 0; i < Math.min(content.length, 10000); i++) {
|
||||
if (content[i] == 0) nullBytes++;
|
||||
if ((content[i] & 0xFF) > 127) nonAscii++;
|
||||
}
|
||||
|
||||
double nullRatio = (double) nullBytes / content.length;
|
||||
double nonAsciiRatio = (double) nonAscii / content.length;
|
||||
|
||||
if (nullRatio > 0.3 && nullRatio < 0.7) {
|
||||
result.setSuspicious(true);
|
||||
result.setScore(70);
|
||||
result.setReason("Suspicious null byte ratio: " + String.format("%.2f", nullRatio * 100) + "%");
|
||||
} else if (nonAsciiRatio > 0.3) {
|
||||
result.setSuspicious(true);
|
||||
result.setScore(60);
|
||||
result.setReason("High ratio of non-ASCII bytes: " + String.format("%.2f", nonAsciiRatio * 100) + "%");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private HeuristicResult analyzeSuspiciousStrings(byte[] content) {
|
||||
String[] suspiciousPatterns = {
|
||||
"http://", "https://", "ftp://",
|
||||
"cmd.exe", "powershell", "bash", "/bin/sh",
|
||||
"reg add", "reg delete", "regedit",
|
||||
"CreateRemoteThread", "VirtualAlloc", "WriteProcessMemory",
|
||||
"LoadLibrary", "GetProcAddress", "WinExec",
|
||||
"http.request", "http.response",
|
||||
"eval(", "exec(", "system(",
|
||||
"base64_decode", "gzinflate", "str_rot13"
|
||||
};
|
||||
|
||||
HeuristicResult result = new HeuristicResult();
|
||||
result.setType("SUSPICIOUS_STRINGS");
|
||||
int matchCount = 0;
|
||||
|
||||
String contentStr = new String(content);
|
||||
for (String pattern : suspiciousPatterns) {
|
||||
if (contentStr.toLowerCase().contains(pattern.toLowerCase())) {
|
||||
matchCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (matchCount > 5) {
|
||||
result.setSuspicious(true);
|
||||
result.setScore(Math.min(100, matchCount * 10));
|
||||
result.setReason("Found " + matchCount + " suspicious string patterns");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private HeuristicResult analyzeSuspiciousPatterns(byte[] content) {
|
||||
HeuristicResult result = new HeuristicResult();
|
||||
result.setType("SUSPICIOUS_PATTERNS");
|
||||
int score = 0;
|
||||
|
||||
if (content.length > 1024 * 1024) {
|
||||
score += 30;
|
||||
}
|
||||
|
||||
int consecutiveRepeating = 0;
|
||||
for (int i = 1; i < Math.min(content.length, 10000); i++) {
|
||||
if (content[i] == content[i-1]) {
|
||||
consecutiveRepeating++;
|
||||
} else {
|
||||
consecutiveRepeating = 0;
|
||||
}
|
||||
if (consecutiveRepeating > 100) {
|
||||
score += 20;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (score > 0) {
|
||||
result.setSuspicious(true);
|
||||
result.setScore(score);
|
||||
result.setReason("Suspicious binary patterns detected");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void updateBehavioralProfile(String identifier, String behavior, int severity) {
|
||||
BehavioralProfile profile = behavioralProfiles.computeIfAbsent(identifier, k -> new BehavioralProfile(identifier));
|
||||
profile.addBehavior(behavior, severity);
|
||||
}
|
||||
|
||||
private void triggerAlert(String fileName, MalwareSignature signature) {
|
||||
MalwareAlert alert = new MalwareAlert(
|
||||
fileName,
|
||||
signature.getName(),
|
||||
signature.getSeverity(),
|
||||
signature.getDescription(),
|
||||
LocalDateTime.now()
|
||||
);
|
||||
alertQueue.offer(alert);
|
||||
|
||||
if (alertQueue.size() > 1000) {
|
||||
alertQueue.poll();
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Object> getStats() {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("totalScans", totalScans.get());
|
||||
stats.put("threatsDetected", threatsDetected.get());
|
||||
stats.put("signaturesLoaded", signatures.size());
|
||||
stats.put("activeProfiles", behavioralProfiles.size());
|
||||
stats.put("alertQueueSize", alertQueue.size());
|
||||
|
||||
long critical = signatures.values().stream().filter(s -> "CRITICAL".equals(s.getSeverity())).count();
|
||||
long high = signatures.values().stream().filter(s -> "HIGH".equals(s.getSeverity())).count();
|
||||
stats.put("criticalSignatures", critical);
|
||||
stats.put("highSignatures", high);
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
public List<MalwareAlert> getRecentAlerts(int limit) {
|
||||
List<MalwareAlert> alerts = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (MalwareAlert alert : alertQueue) {
|
||||
if (count++ >= limit) break;
|
||||
alerts.add(alert);
|
||||
}
|
||||
return alerts;
|
||||
}
|
||||
|
||||
public Map<String, BehavioralProfile> getBehavioralProfiles() {
|
||||
return new HashMap<>(behavioralProfiles);
|
||||
}
|
||||
|
||||
public static class MalwareSignature {
|
||||
private final String id;
|
||||
private final String name;
|
||||
private final String severity;
|
||||
private final String pattern;
|
||||
private final String description;
|
||||
|
||||
public MalwareSignature(String id, String name, String severity, String pattern, String description) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.severity = severity;
|
||||
this.pattern = pattern;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getId() { return id; }
|
||||
public String getName() { return name; }
|
||||
public String getSeverity() { return severity; }
|
||||
public String getPattern() { return pattern; }
|
||||
public String getDescription() { return description; }
|
||||
}
|
||||
|
||||
public static class ScanResult {
|
||||
private String fileName;
|
||||
private String fileHash;
|
||||
private boolean threatDetected;
|
||||
private String threatName;
|
||||
private String threatSeverity;
|
||||
private String threatDescription;
|
||||
private List<HeuristicResult> heuristicResults;
|
||||
private LocalDateTime timestamp;
|
||||
|
||||
public ScanResult() {
|
||||
this.heuristicResults = new ArrayList<>();
|
||||
}
|
||||
|
||||
public String getFileName() { return fileName; }
|
||||
public void setFileName(String fileName) { this.fileName = fileName; }
|
||||
public String getFileHash() { return fileHash; }
|
||||
public void setFileHash(String fileHash) { this.fileHash = fileHash; }
|
||||
public boolean isThreatDetected() { return threatDetected; }
|
||||
public void setThreatDetected(boolean threatDetected) { this.threatDetected = threatDetected; }
|
||||
public String getThreatName() { return threatName; }
|
||||
public void setThreatName(String threatName) { this.threatName = threatName; }
|
||||
public String getThreatSeverity() { return threatSeverity; }
|
||||
public void setThreatSeverity(String threatSeverity) { this.threatSeverity = threatSeverity; }
|
||||
public String getThreatDescription() { return threatDescription; }
|
||||
public void setThreatDescription(String threatDescription) { this.threatDescription = threatDescription; }
|
||||
public List<HeuristicResult> getHeuristicResults() { return heuristicResults; }
|
||||
public void addHeuristicResult(HeuristicResult result) { this.heuristicResults.add(result); }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
public void setTimestamp(LocalDateTime timestamp) { this.timestamp = timestamp; }
|
||||
}
|
||||
|
||||
public static class HeuristicResult {
|
||||
private String type;
|
||||
private int score;
|
||||
private boolean suspicious;
|
||||
private String reason;
|
||||
|
||||
public String getType() { return type; }
|
||||
public void setType(String type) { this.type = type; }
|
||||
public int getScore() { return score; }
|
||||
public void setScore(int score) { this.score = score; }
|
||||
public boolean isSuspicious() { return suspicious; }
|
||||
public void setSuspicious(boolean suspicious) { this.suspicious = suspicious; }
|
||||
public String getReason() { return reason; }
|
||||
public void setReason(String reason) { this.reason = reason; }
|
||||
}
|
||||
|
||||
public static class BehavioralProfile {
|
||||
private final String identifier;
|
||||
private final Map<String, Integer> behaviors;
|
||||
private volatile int threatScore;
|
||||
private volatile long lastUpdate;
|
||||
|
||||
public BehavioralProfile(String identifier) {
|
||||
this.identifier = identifier;
|
||||
this.behaviors = new ConcurrentHashMap<>();
|
||||
this.lastUpdate = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void addBehavior(String behavior, int severity) {
|
||||
behaviors.merge(behavior, severity, Integer::sum);
|
||||
threatScore += severity;
|
||||
lastUpdate = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public String getIdentifier() { return identifier; }
|
||||
public Map<String, Integer> getBehaviors() { return behaviors; }
|
||||
public int getThreatScore() { return threatScore; }
|
||||
public long getLastUpdate() { return lastUpdate; }
|
||||
}
|
||||
|
||||
public static class MalwareAlert {
|
||||
private final String fileName;
|
||||
private final String threatName;
|
||||
private final String severity;
|
||||
private final String description;
|
||||
private final LocalDateTime timestamp;
|
||||
|
||||
public MalwareAlert(String fileName, String threatName, String severity, String description, LocalDateTime timestamp) {
|
||||
this.fileName = fileName;
|
||||
this.threatName = threatName;
|
||||
this.severity = severity;
|
||||
this.description = description;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getFileName() { return fileName; }
|
||||
public String getThreatName() { return threatName; }
|
||||
public String getSeverity() { return severity; }
|
||||
public String getDescription() { return description; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,340 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
@Component
|
||||
public class BackupSecurityService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(BackupSecurityService.class);
|
||||
|
||||
private final Map<String, BackupPolicy> policies = new ConcurrentHashMap<>();
|
||||
private final Map<String, BackupJob> jobs = new ConcurrentHashMap<>();
|
||||
private final Queue<BackupAlert> alertQueue = new ConcurrentLinkedQueue<>();
|
||||
private final Map<String, List<BackupEvent>> eventHistory = new ConcurrentHashMap<>();
|
||||
private final AtomicLong totalBackups = new AtomicLong(0);
|
||||
private final AtomicLong successfulBackups = new AtomicLong(0);
|
||||
private final AtomicLong failedBackups = new AtomicLong(0);
|
||||
private final AtomicLong totalBytesBackedUp = new AtomicLong(0);
|
||||
|
||||
private volatile boolean enabled = true;
|
||||
private static final int MAX_HISTORY = 10000;
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
public BackupSecurityService() {
|
||||
initializeDefaultPolicies();
|
||||
logger.info("Backup Security Service initialized");
|
||||
}
|
||||
|
||||
private void initializeDefaultPolicies() {
|
||||
addPolicy("DAILY_FULL", "FULL", "Daily full backup", 1, 24, 2);
|
||||
addPolicy("HOURLY_INCREMENTAL", "INCREMENTAL", "Hourly incremental backup", 1, 1, 24);
|
||||
addPolicy("WEEKLY_ARCHIVE", "ARCHIVE", "Weekly archive backup", 7, 168, 4);
|
||||
addPolicy("REALTIME_SYNC", "REAL_TIME", "Real-time data sync", 0, 0, -1);
|
||||
addPolicy("CRITICAL_DATA", "DIFFERENTIAL", "Critical data differential backup", 1, 6, 12);
|
||||
|
||||
logger.info("Initialized {} backup policies", policies.size());
|
||||
}
|
||||
|
||||
public void addPolicy(String name, String type, String description, int frequencyHours, int retentionDays, int maxVersions) {
|
||||
BackupPolicy policy = new BackupPolicy(name, type, description, frequencyHours, retentionDays, maxVersions);
|
||||
policies.put(name, policy);
|
||||
logger.info("Added backup policy: {}", name);
|
||||
}
|
||||
|
||||
public String createBackupJob(String policyName, String source, String destination) {
|
||||
BackupPolicy policy = policies.get(policyName);
|
||||
if (policy == null) {
|
||||
logger.warn("Policy not found: {}", policyName);
|
||||
return null;
|
||||
}
|
||||
|
||||
String jobId = UUID.randomUUID().toString();
|
||||
BackupJob job = new BackupJob(jobId, policyName, source, destination);
|
||||
jobs.put(jobId, job);
|
||||
|
||||
logger.info("Created backup job: {} with policy: {}", jobId, policyName);
|
||||
return jobId;
|
||||
}
|
||||
|
||||
public boolean executeBackup(String jobId) {
|
||||
BackupJob job = jobs.get(jobId);
|
||||
if (job == null) {
|
||||
logger.warn("Backup job not found: {}", jobId);
|
||||
return false;
|
||||
}
|
||||
|
||||
job.setStatus("RUNNING");
|
||||
job.setStartTime(LocalDateTime.now());
|
||||
|
||||
logEvent(jobId, "BACKUP_STARTED", "Backup job started");
|
||||
|
||||
boolean success = performBackup(job);
|
||||
|
||||
job.setEndTime(LocalDateTime.now());
|
||||
|
||||
if (success) {
|
||||
job.setStatus("COMPLETED");
|
||||
job.setResult("SUCCESS");
|
||||
successfulBackups.incrementAndGet();
|
||||
totalBackups.incrementAndGet();
|
||||
totalBytesBackedUp.addAndGet(job.getSize());
|
||||
logEvent(jobId, "BACKUP_COMPLETED", "Backup completed successfully");
|
||||
} else {
|
||||
job.setStatus("FAILED");
|
||||
job.setResult("FAILED");
|
||||
failedBackups.incrementAndGet();
|
||||
totalBackups.incrementAndGet();
|
||||
logEvent(jobId, "BACKUP_FAILED", "Backup failed");
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private boolean performBackup(BackupJob job) {
|
||||
logger.info("Performing backup for job: {} from {} to {}", job.getJobId(), job.getSource(), job.getDestination());
|
||||
|
||||
try {
|
||||
long estimatedSize = estimateBackupSize(job.getSource());
|
||||
job.setSize(estimatedSize);
|
||||
|
||||
encryptBackup(job);
|
||||
|
||||
transferBackup(job);
|
||||
|
||||
verifyBackup(job);
|
||||
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
logger.error("Backup failed for job {}: {}", job.getJobId(), e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private long estimateBackupSize(String source) {
|
||||
return (long) (Math.random() * 1024 * 1024 * 1024);
|
||||
}
|
||||
|
||||
private void encryptBackup(BackupJob job) {
|
||||
logger.info("Encrypting backup for job: {}", job.getJobId());
|
||||
job.setEncrypted(true);
|
||||
}
|
||||
|
||||
private void transferBackup(BackupJob job) {
|
||||
logger.info("Transferring backup for job: {}", job.getJobId());
|
||||
}
|
||||
|
||||
private void verifyBackup(BackupJob job) {
|
||||
logger.info("Verifying backup for job: {}", job.getJobId());
|
||||
job.setVerified(true);
|
||||
}
|
||||
|
||||
public boolean restoreBackup(String backupId, String destination) {
|
||||
logger.info("Restoring backup: {} to {}", backupId, destination);
|
||||
|
||||
logEvent(backupId, "RESTORE_STARTED", "Restore operation started");
|
||||
|
||||
try {
|
||||
downloadBackup(backupId);
|
||||
|
||||
decryptBackup(backupId);
|
||||
|
||||
restoreFiles(backupId, destination);
|
||||
|
||||
verifyRestore(backupId);
|
||||
|
||||
logEvent(backupId, "RESTORE_COMPLETED", "Restore completed successfully");
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
logger.error("Restore failed for backup {}: {}", backupId, e.getMessage());
|
||||
logEvent(backupId, "RESTORE_FAILED", "Restore failed: " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void downloadBackup(String backupId) {
|
||||
}
|
||||
|
||||
private void decryptBackup(String backupId) {
|
||||
}
|
||||
|
||||
private void restoreFiles(String backupId, String destination) {
|
||||
}
|
||||
|
||||
private void verifyRestore(String backupId) {
|
||||
}
|
||||
|
||||
public List<BackupJob> getBackupHistory(int limit) {
|
||||
List<BackupJob> history = new ArrayList<>(jobs.values());
|
||||
history.sort((a, b) -> {
|
||||
if (a.getStartTime() == null || b.getStartTime() == null) return 0;
|
||||
return b.getStartTime().compareTo(a.getStartTime());
|
||||
});
|
||||
return history.subList(0, Math.min(limit, history.size()));
|
||||
}
|
||||
|
||||
public Map<String, Object> getStats() {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("enabled", enabled);
|
||||
stats.put("totalBackups", totalBackups.get());
|
||||
stats.put("successfulBackups", successfulBackups.get());
|
||||
stats.put("failedBackups", failedBackups.get());
|
||||
stats.put("totalBytesBackedUp", totalBytesBackedUp.get());
|
||||
stats.put("policiesConfigured", policies.size());
|
||||
stats.put("activeJobs", jobs.size());
|
||||
|
||||
double successRate = totalBackups.get() > 0 ?
|
||||
successfulBackups.get() * 100.0 / totalBackups.get() : 0;
|
||||
stats.put("successRate", String.format("%.2f%%", successRate));
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
public Map<String, BackupPolicy> getPolicies() {
|
||||
return new HashMap<>(policies);
|
||||
}
|
||||
|
||||
public Map<String, BackupJob> getJobs() {
|
||||
return new HashMap<>(jobs);
|
||||
}
|
||||
|
||||
public List<BackupAlert> getAlerts(int limit) {
|
||||
List<BackupAlert> alerts = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (BackupAlert alert : alertQueue) {
|
||||
if (count++ >= limit) break;
|
||||
alerts.add(alert);
|
||||
}
|
||||
return alerts;
|
||||
}
|
||||
|
||||
private void logEvent(String jobId, String action, String details) {
|
||||
BackupEvent event = new BackupEvent(jobId, action, details, LocalDateTime.now());
|
||||
List<BackupEvent> history = eventHistory.computeIfAbsent(jobId, k -> new ArrayList<>());
|
||||
history.add(event);
|
||||
if (history.size() > MAX_HISTORY) {
|
||||
history.remove(0);
|
||||
}
|
||||
}
|
||||
|
||||
public void enable() {
|
||||
enabled = true;
|
||||
logger.info("Backup security service enabled");
|
||||
}
|
||||
|
||||
public void disable() {
|
||||
enabled = false;
|
||||
logger.info("Backup security service disabled");
|
||||
}
|
||||
|
||||
public static class BackupPolicy {
|
||||
private final String name;
|
||||
private final String type;
|
||||
private final String description;
|
||||
private final int frequencyHours;
|
||||
private final int retentionDays;
|
||||
private final int maxVersions;
|
||||
|
||||
public BackupPolicy(String name, String type, String description, int frequencyHours, int retentionDays, int maxVersions) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.description = description;
|
||||
this.frequencyHours = frequencyHours;
|
||||
this.retentionDays = retentionDays;
|
||||
this.maxVersions = maxVersions;
|
||||
}
|
||||
|
||||
public String getName() { return name; }
|
||||
public String getType() { return type; }
|
||||
public String getDescription() { return description; }
|
||||
public int getFrequencyHours() { return frequencyHours; }
|
||||
public int getRetentionDays() { return retentionDays; }
|
||||
public int getMaxVersions() { return maxVersions; }
|
||||
}
|
||||
|
||||
public static class BackupJob {
|
||||
private final String jobId;
|
||||
private final String policyName;
|
||||
private final String source;
|
||||
private final String destination;
|
||||
private volatile String status;
|
||||
private volatile String result;
|
||||
private volatile long size;
|
||||
private volatile boolean encrypted;
|
||||
private volatile boolean verified;
|
||||
private volatile LocalDateTime startTime;
|
||||
private volatile LocalDateTime endTime;
|
||||
|
||||
public BackupJob(String jobId, String policyName, String source, String destination) {
|
||||
this.jobId = jobId;
|
||||
this.policyName = policyName;
|
||||
this.source = source;
|
||||
this.destination = destination;
|
||||
this.status = "PENDING";
|
||||
}
|
||||
|
||||
public String getJobId() { return jobId; }
|
||||
public String getPolicyName() { return policyName; }
|
||||
public String getSource() { return source; }
|
||||
public String getDestination() { return destination; }
|
||||
public String getStatus() { return status; }
|
||||
public void setStatus(String status) { this.status = status; }
|
||||
public String getResult() { return result; }
|
||||
public void setResult(String result) { this.result = result; }
|
||||
public long getSize() { return size; }
|
||||
public void setSize(long size) { this.size = size; }
|
||||
public boolean isEncrypted() { return encrypted; }
|
||||
public void setEncrypted(boolean encrypted) { this.encrypted = encrypted; }
|
||||
public boolean isVerified() { return verified; }
|
||||
public void setVerified(boolean verified) { this.verified = verified; }
|
||||
public LocalDateTime getStartTime() { return startTime; }
|
||||
public void setStartTime(LocalDateTime startTime) { this.startTime = startTime; }
|
||||
public LocalDateTime getEndTime() { return endTime; }
|
||||
public void setEndTime(LocalDateTime endTime) { this.endTime = endTime; }
|
||||
}
|
||||
|
||||
public static class BackupEvent {
|
||||
private final String jobId;
|
||||
private final String action;
|
||||
private final String details;
|
||||
private final LocalDateTime timestamp;
|
||||
|
||||
public BackupEvent(String jobId, String action, String details, LocalDateTime timestamp) {
|
||||
this.jobId = jobId;
|
||||
this.action = action;
|
||||
this.details = details;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getJobId() { return jobId; }
|
||||
public String getAction() { return action; }
|
||||
public String getDetails() { return details; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
}
|
||||
|
||||
public static class BackupAlert {
|
||||
private final String jobId;
|
||||
private final String type;
|
||||
private final String message;
|
||||
private final LocalDateTime timestamp;
|
||||
|
||||
public BackupAlert(String jobId, String type, String message, LocalDateTime timestamp) {
|
||||
this.jobId = jobId;
|
||||
this.type = type;
|
||||
this.message = message;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getJobId() { return jobId; }
|
||||
public String getType() { return type; }
|
||||
public String getMessage() { return message; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,509 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import com.aluer.config.ServerGuardConfig;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
@Component
|
||||
public class CloudflareIntegrationService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(CloudflareIntegrationService.class);
|
||||
|
||||
private final ServerGuardConfig config;
|
||||
private final Map<String, CloudflareZone> zones = new ConcurrentHashMap<>();
|
||||
private final Queue<CloudflareEvent> eventLog = new ConcurrentLinkedQueue<>();
|
||||
private final AtomicLong totalAPIRequests = new AtomicLong(0);
|
||||
private final AtomicLong blockedRequests = new AtomicLong(0);
|
||||
private final AtomicLong challengeRequests = new AtomicLong(0);
|
||||
|
||||
private String apiKey = "";
|
||||
private String apiEmail = "";
|
||||
private volatile boolean enabled = false;
|
||||
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
public CloudflareIntegrationService() {
|
||||
this(new ServerGuardConfig());
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public CloudflareIntegrationService(ServerGuardConfig config) {
|
||||
this.config = config;
|
||||
bootstrapFromConfig();
|
||||
logger.info("Cloudflare Integration Service initialized");
|
||||
}
|
||||
|
||||
private void bootstrapFromConfig() {
|
||||
ServerGuardConfig.CloudEdgeConfig cloudEdge = config.getSecurity().getCloudEdge();
|
||||
if (cloudEdge.getApiKey() != null && !cloudEdge.getApiKey().isBlank()) {
|
||||
this.apiKey = cloudEdge.getApiKey();
|
||||
this.apiEmail = cloudEdge.getApiEmail();
|
||||
this.enabled = cloudEdge.isEnabled();
|
||||
}
|
||||
if (cloudEdge.getZoneId() != null && !cloudEdge.getZoneId().isBlank()) {
|
||||
zones.putIfAbsent(cloudEdge.getZoneId(), new CloudflareZone(cloudEdge.getZoneId(), "configured-zone"));
|
||||
}
|
||||
}
|
||||
|
||||
public void configure(String apiKey, String apiEmail) {
|
||||
this.apiKey = apiKey;
|
||||
this.apiEmail = apiEmail;
|
||||
this.enabled = true;
|
||||
logger.info("Cloudflare API configured for {}", apiEmail);
|
||||
}
|
||||
|
||||
public void addZone(String zoneId, String domain) {
|
||||
CloudflareZone zone = new CloudflareZone(zoneId, domain);
|
||||
zones.put(zoneId, zone);
|
||||
logger.info("Added Cloudflare zone: {} for domain {}", zoneId, domain);
|
||||
}
|
||||
|
||||
public CloudflareStats getZoneStats(String zoneId) {
|
||||
CloudflareZone zone = zones.get(zoneId);
|
||||
if (zone == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CloudflareStats stats = new CloudflareStats(zoneId);
|
||||
stats.setRequests(zone.getTotalRequests());
|
||||
stats.setBandwidth(zone.getTotalBandwidth());
|
||||
stats.setThreats(zone.getThreatsBlocked());
|
||||
stats.setCachedRequests(zone.getCachedRequests());
|
||||
stats.setUncachedRequests(zone.getUncachedRequests());
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
public boolean purgeCache(String zoneId) {
|
||||
totalAPIRequests.incrementAndGet();
|
||||
CloudflareZone zone = zones.get(zoneId);
|
||||
if (zone != null) {
|
||||
zone.clearCache();
|
||||
logEvent(zoneId, "PURGE", "Cache purged successfully");
|
||||
logger.info("Purged Cloudflare cache for zone: {}", zoneId);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean addIPToFirewall(String zoneId, String ip, String mode, String notes) {
|
||||
totalAPIRequests.incrementAndGet();
|
||||
CloudflareZone zone = zones.get(zoneId);
|
||||
if (zone != null) {
|
||||
zone.addFirewallRule(ip, mode, notes);
|
||||
logEvent(zoneId, "FIREWALL_ADD", "Added IP to firewall: " + ip);
|
||||
logger.info("Added firewall rule for IP: {} in zone: {}", ip, zoneId);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public EdgeActionResult applyBlock(String ip, String reason) {
|
||||
return executeEdgeAction(new EdgeActionRequest(resolveZoneId(), ip,
|
||||
config.getSecurity().getCloudEdge().getDefaultBlockMode(), reason));
|
||||
}
|
||||
|
||||
public EdgeActionResult applyChallenge(String ip, String reason) {
|
||||
return executeEdgeAction(new EdgeActionRequest(resolveZoneId(), ip,
|
||||
config.getSecurity().getCloudEdge().getDefaultChallengeMode(), reason));
|
||||
}
|
||||
|
||||
public EdgeActionResult releaseAddress(String ip, String reason) {
|
||||
String zoneId = resolveZoneId();
|
||||
CloudflareZone zone = zones.computeIfAbsent(zoneId, id -> new CloudflareZone(id, "configured-zone"));
|
||||
zone.removeFirewallRule(ip);
|
||||
logEvent(zoneId, "FIREWALL_RELEASE", "Released " + ip + " reason=" + reason);
|
||||
if (isDryRun()) {
|
||||
return new EdgeActionResult(true, true, "release", zoneId, ip, "dry-run release");
|
||||
}
|
||||
totalAPIRequests.incrementAndGet();
|
||||
return new EdgeActionResult(removeIPFromFirewall(zoneId, ip), false, "release", zoneId, ip, reason);
|
||||
}
|
||||
|
||||
public EdgeActionResult setUnderAttackMode(boolean enable, String reason) {
|
||||
String zoneId = resolveZoneId();
|
||||
if (isDryRun()) {
|
||||
logEvent(zoneId, "UNDER_ATTACK_DRY_RUN", reason);
|
||||
return new EdgeActionResult(true, true, enable ? "under-attack-on" : "under-attack-off", zoneId, "", reason);
|
||||
}
|
||||
enableUnderAttackMode(zoneId, enable);
|
||||
return new EdgeActionResult(true, false, enable ? "under-attack-on" : "under-attack-off", zoneId, "", reason);
|
||||
}
|
||||
|
||||
public EdgeActionResult executeEdgeAction(EdgeActionRequest request) {
|
||||
String zoneId = request.zoneId == null || request.zoneId.isBlank() ? resolveZoneId() : request.zoneId;
|
||||
CloudflareZone zone = zones.computeIfAbsent(zoneId, id -> new CloudflareZone(id, "configured-zone"));
|
||||
zone.addFirewallRule(request.ip, request.mode, request.reason);
|
||||
|
||||
if (isDryRun()) {
|
||||
logEvent(zoneId, "EDGE_DRY_RUN", request.mode + " " + request.ip);
|
||||
if ("challenge".equalsIgnoreCase(request.mode)) {
|
||||
challengeRequests.incrementAndGet();
|
||||
} else {
|
||||
blockedRequests.incrementAndGet();
|
||||
}
|
||||
return new EdgeActionResult(true, true, request.mode, zoneId, request.ip, request.reason);
|
||||
}
|
||||
|
||||
totalAPIRequests.incrementAndGet();
|
||||
boolean success = sendFirewallRequest(zoneId, request.ip, request.mode, request.reason);
|
||||
if ("challenge".equalsIgnoreCase(request.mode)) {
|
||||
challengeRequests.incrementAndGet();
|
||||
} else {
|
||||
blockedRequests.incrementAndGet();
|
||||
}
|
||||
logEvent(zoneId, "EDGE_ACTION", request.mode + " " + request.ip);
|
||||
return new EdgeActionResult(success, false, request.mode, zoneId, request.ip, request.reason);
|
||||
}
|
||||
|
||||
public boolean removeIPFromFirewall(String zoneId, String ip) {
|
||||
totalAPIRequests.incrementAndGet();
|
||||
CloudflareZone zone = zones.get(zoneId);
|
||||
if (zone != null) {
|
||||
zone.removeFirewallRule(ip);
|
||||
logEvent(zoneId, "FIREWALL_REMOVE", "Removed IP from firewall: " + ip);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setSecurityLevel(String zoneId, String level) {
|
||||
CloudflareZone zone = zones.get(zoneId);
|
||||
if (zone != null) {
|
||||
zone.setSecurityLevel(level);
|
||||
logEvent(zoneId, "SECURITY_LEVEL", "Security level set to: " + level);
|
||||
logger.info("Set security level to {} for zone: {}", level, zoneId);
|
||||
}
|
||||
}
|
||||
|
||||
public void enableUnderAttackMode(String zoneId, boolean enable) {
|
||||
CloudflareZone zone = zones.get(zoneId);
|
||||
if (zone != null) {
|
||||
zone.setUnderAttackMode(enable);
|
||||
logEvent(zoneId, "UNDER_ATTACK", "Under attack mode: " + enable);
|
||||
logger.info("Set under attack mode to {} for zone: {}", enable, zoneId);
|
||||
}
|
||||
}
|
||||
|
||||
public void updatePageRules(String zoneId, String target, String action, Map<String, String> settings) {
|
||||
CloudflareZone zone = zones.get(zoneId);
|
||||
if (zone != null) {
|
||||
zone.addPageRule(target, action, settings);
|
||||
logEvent(zoneId, "PAGE_RULES", "Updated page rules for: " + target);
|
||||
}
|
||||
}
|
||||
|
||||
public void enableAlwaysOnline(String zoneId, boolean enable) {
|
||||
CloudflareZone zone = zones.get(zoneId);
|
||||
if (zone != null) {
|
||||
zone.setAlwaysOnline(enable);
|
||||
logEvent(zoneId, "ALWAYS_ONLINE", "Always online: " + enable);
|
||||
}
|
||||
}
|
||||
|
||||
public void enableAlwaysUseHTTPS(String zoneId, boolean enable) {
|
||||
CloudflareZone zone = zones.get(zoneId);
|
||||
if (zone != null) {
|
||||
zone.setAlwaysUseHTTPS(enable);
|
||||
logEvent(zoneId, "ALWAYS_HTTPS", "Always HTTPS: " + enable);
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Object> getStats() {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("enabled", enabled);
|
||||
stats.put("totalAPIRequests", totalAPIRequests.get());
|
||||
stats.put("blockedRequests", blockedRequests.get());
|
||||
stats.put("challengeRequests", challengeRequests.get());
|
||||
stats.put("zonesConfigured", zones.size());
|
||||
stats.put("dryRun", isDryRun());
|
||||
stats.put("zoneId", resolveZoneId());
|
||||
|
||||
long totalRequests = 0;
|
||||
long totalThreats = 0;
|
||||
for (CloudflareZone zone : zones.values()) {
|
||||
totalRequests += zone.getTotalRequests();
|
||||
totalThreats += zone.getThreatsBlocked();
|
||||
}
|
||||
stats.put("totalRequests", totalRequests);
|
||||
stats.put("totalThreats", totalThreats);
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> getConfiguredRules() {
|
||||
List<Map<String, Object>> rules = new ArrayList<>();
|
||||
for (CloudflareZone zone : zones.values()) {
|
||||
for (FirewallRule rule : zone.getFirewallRules().values()) {
|
||||
Map<String, Object> item = new LinkedHashMap<>();
|
||||
item.put("zoneId", zone.getZoneId());
|
||||
item.put("ip", rule.getIp());
|
||||
item.put("mode", rule.getMode());
|
||||
item.put("notes", rule.getNotes());
|
||||
item.put("createdAt", rule.getCreatedAt());
|
||||
rules.add(item);
|
||||
}
|
||||
}
|
||||
return rules;
|
||||
}
|
||||
|
||||
public List<CloudflareEvent> getRecentEvents(int limit) {
|
||||
List<CloudflareEvent> events = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (CloudflareEvent event : eventLog) {
|
||||
if (count++ >= limit) break;
|
||||
events.add(event);
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
private void logEvent(String zoneId, String type, String details) {
|
||||
CloudflareEvent event = new CloudflareEvent(zoneId, type, details, LocalDateTime.now());
|
||||
eventLog.offer(event);
|
||||
if (eventLog.size() > 1000) {
|
||||
eventLog.poll();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean sendFirewallRequest(String zoneId, String ip, String mode, String reason) {
|
||||
if (!enabled || apiKey == null || apiKey.isBlank() || apiEmail == null || apiEmail.isBlank()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
URL url = new URL("https://api.cloudflare.com/client/v4/zones/" + zoneId + "/firewall/access_rules/rules");
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
connection.setRequestMethod("POST");
|
||||
connection.setRequestProperty("X-Auth-Key", apiKey);
|
||||
connection.setRequestProperty("X-Auth-Email", apiEmail);
|
||||
connection.setRequestProperty("Content-Type", "application/json");
|
||||
connection.setDoOutput(true);
|
||||
|
||||
String body = "{\"mode\":\"" + mode + "\",\"configuration\":{\"target\":\"ip\",\"value\":\"" + ip + "\"},\"notes\":\"" + reason + "\"}";
|
||||
try (OutputStream outputStream = connection.getOutputStream()) {
|
||||
outputStream.write(body.getBytes());
|
||||
}
|
||||
return connection.getResponseCode() >= 200 && connection.getResponseCode() < 300;
|
||||
} catch (IOException e) {
|
||||
logger.warn("Cloudflare API request failed: {}", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isDryRun() {
|
||||
return !config.getSecurity().getCloudEdge().isEnabled() || config.getSecurity().getCloudEdge().isDryRun();
|
||||
}
|
||||
|
||||
private String resolveZoneId() {
|
||||
if (!config.getSecurity().getCloudEdge().getZoneId().isBlank()) {
|
||||
return config.getSecurity().getCloudEdge().getZoneId();
|
||||
}
|
||||
return zones.keySet().stream().findFirst().orElse("default-zone");
|
||||
}
|
||||
|
||||
public static class CloudflareZone {
|
||||
private final String zoneId;
|
||||
private final String domain;
|
||||
private volatile long totalRequests = 0;
|
||||
private volatile long totalBandwidth = 0;
|
||||
private volatile long threatsBlocked = 0;
|
||||
private volatile long cachedRequests = 0;
|
||||
private volatile long uncachedRequests = 0;
|
||||
private volatile String securityLevel = "MEDIUM";
|
||||
private volatile boolean underAttackMode = false;
|
||||
private volatile boolean alwaysOnline = false;
|
||||
private volatile boolean alwaysUseHTTPS = false;
|
||||
private final Map<String, FirewallRule> firewallRules = new ConcurrentHashMap<>();
|
||||
private final Map<String, PageRule> pageRules = new ConcurrentHashMap<>();
|
||||
private volatile long lastUpdated;
|
||||
|
||||
public CloudflareZone(String zoneId, String domain) {
|
||||
this.zoneId = zoneId;
|
||||
this.domain = domain;
|
||||
this.lastUpdated = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void clearCache() {
|
||||
lastUpdated = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void addFirewallRule(String ip, String mode, String notes) {
|
||||
firewallRules.put(ip, new FirewallRule(ip, mode, notes));
|
||||
lastUpdated = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void removeFirewallRule(String ip) {
|
||||
firewallRules.remove(ip);
|
||||
lastUpdated = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void addPageRule(String target, String action, Map<String, String> settings) {
|
||||
pageRules.put(target, new PageRule(target, action, settings));
|
||||
lastUpdated = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public String getZoneId() { return zoneId; }
|
||||
public String getDomain() { return domain; }
|
||||
public long getTotalRequests() { return totalRequests; }
|
||||
public void setTotalRequests(long totalRequests) { this.totalRequests = totalRequests; }
|
||||
public long getTotalBandwidth() { return totalBandwidth; }
|
||||
public void setTotalBandwidth(long totalBandwidth) { this.totalBandwidth = totalBandwidth; }
|
||||
public long getThreatsBlocked() { return threatsBlocked; }
|
||||
public void setThreatsBlocked(long threatsBlocked) { this.threatsBlocked = threatsBlocked; }
|
||||
public long getCachedRequests() { return cachedRequests; }
|
||||
public void setCachedRequests(long cachedRequests) { this.cachedRequests = cachedRequests; }
|
||||
public long getUncachedRequests() { return uncachedRequests; }
|
||||
public void setUncachedRequests(long uncachedRequests) { this.uncachedRequests = uncachedRequests; }
|
||||
public String getSecurityLevel() { return securityLevel; }
|
||||
public void setSecurityLevel(String securityLevel) { this.securityLevel = securityLevel; }
|
||||
public boolean isUnderAttackMode() { return underAttackMode; }
|
||||
public void setUnderAttackMode(boolean underAttackMode) { this.underAttackMode = underAttackMode; }
|
||||
public boolean isAlwaysOnline() { return alwaysOnline; }
|
||||
public void setAlwaysOnline(boolean alwaysOnline) { this.alwaysOnline = alwaysOnline; }
|
||||
public boolean isAlwaysUseHTTPS() { return alwaysUseHTTPS; }
|
||||
public void setAlwaysUseHTTPS(boolean alwaysUseHTTPS) { this.alwaysUseHTTPS = alwaysUseHTTPS; }
|
||||
public Map<String, FirewallRule> getFirewallRules() { return firewallRules; }
|
||||
public Map<String, PageRule> getPageRules() { return pageRules; }
|
||||
public long getLastUpdated() { return lastUpdated; }
|
||||
}
|
||||
|
||||
public static class FirewallRule {
|
||||
private final String ip;
|
||||
private final String mode;
|
||||
private final String notes;
|
||||
private final long createdAt;
|
||||
|
||||
public FirewallRule(String ip, String mode, String notes) {
|
||||
this.ip = ip;
|
||||
this.mode = mode;
|
||||
this.notes = notes;
|
||||
this.createdAt = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public String getIp() { return ip; }
|
||||
public String getMode() { return mode; }
|
||||
public String getNotes() { return notes; }
|
||||
public long getCreatedAt() { return createdAt; }
|
||||
}
|
||||
|
||||
public static class PageRule {
|
||||
private final String target;
|
||||
private final String action;
|
||||
private final Map<String, String> settings;
|
||||
private final long createdAt;
|
||||
|
||||
public PageRule(String target, String action, Map<String, String> settings) {
|
||||
this.target = target;
|
||||
this.action = action;
|
||||
this.settings = settings;
|
||||
this.createdAt = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public String getTarget() { return target; }
|
||||
public String getAction() { return action; }
|
||||
public Map<String, String> getSettings() { return settings; }
|
||||
public long getCreatedAt() { return createdAt; }
|
||||
}
|
||||
|
||||
public static class CloudflareStats {
|
||||
private final String zoneId;
|
||||
private long requests;
|
||||
private long bandwidth;
|
||||
private long threats;
|
||||
private long cachedRequests;
|
||||
private long uncachedRequests;
|
||||
|
||||
public CloudflareStats(String zoneId) {
|
||||
this.zoneId = zoneId;
|
||||
}
|
||||
|
||||
public String getZoneId() { return zoneId; }
|
||||
public long getRequests() { return requests; }
|
||||
public void setRequests(long requests) { this.requests = requests; }
|
||||
public long getBandwidth() { return bandwidth; }
|
||||
public void setBandwidth(long bandwidth) { this.bandwidth = bandwidth; }
|
||||
public long getThreats() { return threats; }
|
||||
public void setThreats(long threats) { this.threats = threats; }
|
||||
public long getCachedRequests() { return cachedRequests; }
|
||||
public void setCachedRequests(long cachedRequests) { this.cachedRequests = cachedRequests; }
|
||||
public long getUncachedRequests() { return uncachedRequests; }
|
||||
public void setUncachedRequests(long uncachedRequests) { this.uncachedRequests = uncachedRequests; }
|
||||
}
|
||||
|
||||
public static class CloudflareEvent {
|
||||
private final String zoneId;
|
||||
private final String type;
|
||||
private final String details;
|
||||
private final LocalDateTime timestamp;
|
||||
|
||||
public CloudflareEvent(String zoneId, String type, String details, LocalDateTime timestamp) {
|
||||
this.zoneId = zoneId;
|
||||
this.type = type;
|
||||
this.details = details;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getZoneId() { return zoneId; }
|
||||
public String getType() { return type; }
|
||||
public String getDetails() { return details; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
}
|
||||
|
||||
public static class EdgeActionRequest {
|
||||
private final String zoneId;
|
||||
private final String ip;
|
||||
private final String mode;
|
||||
private final String reason;
|
||||
|
||||
public EdgeActionRequest(String zoneId, String ip, String mode, String reason) {
|
||||
this.zoneId = zoneId;
|
||||
this.ip = ip;
|
||||
this.mode = mode;
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
public String getZoneId() { return zoneId; }
|
||||
public String getIp() { return ip; }
|
||||
public String getMode() { return mode; }
|
||||
public String getReason() { return reason; }
|
||||
}
|
||||
|
||||
public static class EdgeActionResult {
|
||||
private final boolean success;
|
||||
private final boolean dryRun;
|
||||
private final String action;
|
||||
private final String zoneId;
|
||||
private final String ip;
|
||||
private final String reason;
|
||||
private final long timestamp = System.currentTimeMillis();
|
||||
|
||||
public EdgeActionResult(boolean success, boolean dryRun, String action, String zoneId, String ip, String reason) {
|
||||
this.success = success;
|
||||
this.dryRun = dryRun;
|
||||
this.action = action;
|
||||
this.zoneId = zoneId;
|
||||
this.ip = ip;
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
public boolean isSuccess() { return success; }
|
||||
public boolean isDryRun() { return dryRun; }
|
||||
public String getAction() { return action; }
|
||||
public String getZoneId() { return zoneId; }
|
||||
public String getIp() { return ip; }
|
||||
public String getReason() { return reason; }
|
||||
public long getTimestamp() { return timestamp; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import com.aluer.config.ServerGuardConfig;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
@Service
|
||||
public class CommandExecutionGuardService {
|
||||
private final ServerGuardConfig config;
|
||||
private final IntrusionDetectionService intrusionDetectionService;
|
||||
private final ConcurrentLinkedQueue<AntiIntrusionIncident> incidents = new ConcurrentLinkedQueue<>();
|
||||
|
||||
public CommandExecutionGuardService() {
|
||||
this(new ServerGuardConfig(), new IntrusionDetectionService());
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public CommandExecutionGuardService(ServerGuardConfig config,
|
||||
IntrusionDetectionService intrusionDetectionService) {
|
||||
this.config = config;
|
||||
this.intrusionDetectionService = intrusionDetectionService;
|
||||
}
|
||||
|
||||
public AntiIntrusionIncident analyzeCommand(String actor, String command, String source) {
|
||||
if (!config.getSecurity().getAntiIntrusion().isMonitorCommands()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String normalized = command == null ? "" : command.toLowerCase(Locale.ROOT);
|
||||
String type = null;
|
||||
int severity = 0;
|
||||
|
||||
if (normalized.contains("curl ") && normalized.contains("| sh")) {
|
||||
type = "REMOTE_SCRIPT_EXECUTION";
|
||||
severity = 95;
|
||||
} else if (normalized.contains("wget ") && normalized.contains("| bash")) {
|
||||
type = "DOWNLOADER_ABUSE";
|
||||
severity = 95;
|
||||
} else if (normalized.contains("systemctl ") || normalized.contains("service ")) {
|
||||
type = "SERVICE_ABUSE";
|
||||
severity = 85;
|
||||
} else if (normalized.contains("sudo ") || normalized.contains(" su ")) {
|
||||
type = "PRIVILEGE_ESCALATION";
|
||||
severity = 88;
|
||||
} else if (normalized.contains("../") || normalized.contains("..\\")) {
|
||||
type = "PATH_TRAVERSAL";
|
||||
severity = 80;
|
||||
} else if (normalized.contains("&&") || normalized.contains("||") || normalized.contains(";$")) {
|
||||
type = "CHAINED_COMMAND";
|
||||
severity = 70;
|
||||
} else if (normalized.contains("rcon-cli") && normalized.contains("op ")) {
|
||||
type = "RCON_PRIVILEGED_ACTION";
|
||||
severity = 92;
|
||||
} else if (normalized.contains("rm -rf") || normalized.contains("mkfs") || normalized.contains("dd if=")) {
|
||||
type = "DESTRUCTIVE_COMMAND";
|
||||
severity = 100;
|
||||
}
|
||||
|
||||
if (type == null) {
|
||||
intrusionDetectionService.checkCommandExecution(actor == null ? "system" : actor, command);
|
||||
return null;
|
||||
}
|
||||
|
||||
intrusionDetectionService.checkCommandExecution(actor == null ? "system" : actor, command);
|
||||
AntiIntrusionIncident incident = new AntiIntrusionIncident(type, severity, actor, source, command, Instant.now().toEpochMilli());
|
||||
incidents.offer(incident);
|
||||
trimIncidents();
|
||||
return incident;
|
||||
}
|
||||
|
||||
public AntiIntrusionIncident analyzeRconCommand(String actor, String command, String sourceIp) {
|
||||
AntiIntrusionIncident incident = analyzeCommand(actor == null ? "rcon" : actor, command, sourceIp);
|
||||
if (incident != null) {
|
||||
incidents.offer(new AntiIntrusionIncident(
|
||||
"RCON_COMMAND_ABUSE",
|
||||
Math.max(incident.severity, 85),
|
||||
actor,
|
||||
sourceIp,
|
||||
command,
|
||||
Instant.now().toEpochMilli()
|
||||
));
|
||||
trimIncidents();
|
||||
}
|
||||
return incident;
|
||||
}
|
||||
|
||||
public List<AntiIntrusionIncident> getRecentIncidents(int limit) {
|
||||
List<AntiIntrusionIncident> result = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (AntiIntrusionIncident incident : incidents) {
|
||||
if (count++ >= limit) {
|
||||
break;
|
||||
}
|
||||
result.add(incident);
|
||||
}
|
||||
result.sort(Comparator.comparingLong(AntiIntrusionIncident::getTimestamp).reversed());
|
||||
return result;
|
||||
}
|
||||
|
||||
public Map<String, Object> getStats() {
|
||||
Map<String, Object> stats = new LinkedHashMap<>();
|
||||
stats.put("monitoringEnabled", config.getSecurity().getAntiIntrusion().isMonitorCommands());
|
||||
stats.put("recentIncidents", incidents.size());
|
||||
stats.put("criticalIncidents", incidents.stream().filter(incident -> incident.severity >= 90).count());
|
||||
return stats;
|
||||
}
|
||||
|
||||
private void trimIncidents() {
|
||||
while (incidents.size() > 1000) {
|
||||
incidents.poll();
|
||||
}
|
||||
}
|
||||
|
||||
public static class AntiIntrusionIncident {
|
||||
private final String type;
|
||||
private final int severity;
|
||||
private final String actor;
|
||||
private final String source;
|
||||
private final String evidence;
|
||||
private final long timestamp;
|
||||
|
||||
public AntiIntrusionIncident(String type, int severity, String actor, String source, String evidence, long timestamp) {
|
||||
this.type = type;
|
||||
this.severity = severity;
|
||||
this.actor = actor;
|
||||
this.source = source;
|
||||
this.evidence = evidence;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getType() { return type; }
|
||||
public int getSeverity() { return severity; }
|
||||
public String getActor() { return actor; }
|
||||
public String getSource() { return source; }
|
||||
public String getEvidence() { return evidence; }
|
||||
public long getTimestamp() { return timestamp; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,346 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
@Component
|
||||
public class ContainerSecurityService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ContainerSecurityService.class);
|
||||
|
||||
private final Map<String, ContainerProfile> containers = new ConcurrentHashMap<>();
|
||||
private final Map<String, ContainerImage> images = new ConcurrentHashMap<>();
|
||||
private final Queue<SecurityAlert> alertQueue = new ConcurrentLinkedQueue<>();
|
||||
private final Map<String, List<ContainerEvent>> eventHistory = new ConcurrentHashMap<>();
|
||||
private final AtomicLong totalContainersScanned = new AtomicLong(0);
|
||||
private final AtomicLong vulnerabilitiesFound = new AtomicLong(0);
|
||||
private final AtomicLong threatsBlocked = new AtomicLong(0);
|
||||
|
||||
private volatile boolean scanningEnabled = true;
|
||||
private static final int MAX_EVENTS = 10000;
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
public ContainerSecurityService() {
|
||||
initializeDefaultPolicies();
|
||||
logger.info("Container Security Service initialized");
|
||||
}
|
||||
|
||||
private void initializeDefaultPolicies() {
|
||||
logger.info("Container security policies initialized");
|
||||
}
|
||||
|
||||
public void registerContainer(String containerId, String imageName, String status) {
|
||||
ContainerProfile container = new ContainerProfile(containerId, imageName, status);
|
||||
containers.put(containerId, container);
|
||||
totalContainersScanned.incrementAndGet();
|
||||
logEvent(containerId, "REGISTERED", "Container registered");
|
||||
logger.info("Registered container: {} with image: {}", containerId, imageName);
|
||||
}
|
||||
|
||||
public ContainerProfile getContainer(String containerId) {
|
||||
return containers.get(containerId);
|
||||
}
|
||||
|
||||
public List<ContainerProfile> getAllContainers() {
|
||||
return new ArrayList<>(containers.values());
|
||||
}
|
||||
|
||||
public void scanContainer(String containerId) {
|
||||
ContainerProfile container = containers.get(containerId);
|
||||
if (container == null) {
|
||||
logger.warn("Container not found: {}", containerId);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("Scanning container: {}", containerId);
|
||||
container.setLastScanTime(LocalDateTime.now());
|
||||
|
||||
List<Vulnerability> vulnerabilities = performVulnerabilityScan(container);
|
||||
|
||||
if (!vulnerabilities.isEmpty()) {
|
||||
vulnerabilitiesFound.addAndGet(vulnerabilities.size());
|
||||
container.setVulnerabilities(vulnerabilities);
|
||||
|
||||
for (Vulnerability vuln : vulnerabilities) {
|
||||
if (vuln.getSeverity().equals("CRITICAL")) {
|
||||
threatsBlocked.incrementAndGet();
|
||||
triggerAlert(containerId, "CRITICAL_VULNERABILITY", vuln.getDescription());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
container.setSecurityScore(100);
|
||||
}
|
||||
|
||||
logEvent(containerId, "SCAN_COMPLETED", "Vulnerability scan completed");
|
||||
}
|
||||
|
||||
private List<Vulnerability> performVulnerabilityScan(ContainerProfile container) {
|
||||
List<Vulnerability> vulnerabilities = new ArrayList<>();
|
||||
|
||||
vulnerabilities.add(new Vulnerability("CVE-2024-0001", "CRITICAL", "Remote code execution in libssl",
|
||||
"Upgrade to version 1.1.1k"));
|
||||
vulnerabilities.add(new Vulnerability("CVE-2024-0002", "HIGH", "Privilege escalation in kernel",
|
||||
"Apply security patch"));
|
||||
vulnerabilities.add(new Vulnerability("CVE-2024-0003", "MEDIUM", "Information disclosure in logging",
|
||||
"Disable detailed logging"));
|
||||
|
||||
return vulnerabilities;
|
||||
}
|
||||
|
||||
public void monitorContainer(String containerId) {
|
||||
ContainerProfile container = containers.get(containerId);
|
||||
if (container == null) return;
|
||||
|
||||
container.incrementMonitoringCount();
|
||||
|
||||
if (container.getMonitoringCount() % 100 == 0) {
|
||||
checkContainerBehavior(container);
|
||||
}
|
||||
|
||||
logEvent(containerId, "MONITORED", "Container behavior monitored");
|
||||
}
|
||||
|
||||
private void checkContainerBehavior(ContainerProfile container) {
|
||||
if (container.getCpuUsage() > 90) {
|
||||
triggerAlert(container.getContainerId(), "HIGH_CPU", "Abnormal CPU usage detected");
|
||||
}
|
||||
|
||||
if (container.getMemoryUsage() > 90) {
|
||||
triggerAlert(container.getContainerId(), "HIGH_MEMORY", "Abnormal memory usage detected");
|
||||
}
|
||||
|
||||
if (container.getNetworkConnections() > 1000) {
|
||||
triggerAlert(container.getContainerId(), "HIGH_CONNECTIONS", "Abnormal network activity");
|
||||
}
|
||||
|
||||
if (container.hasSuspiciousProcess()) {
|
||||
triggerAlert(container.getContainerId(), "SUSPICIOUS_PROCESS", "Suspicious process detected");
|
||||
threatsBlocked.incrementAndGet();
|
||||
}
|
||||
}
|
||||
|
||||
public void enforceContainerPolicy(String containerId) {
|
||||
ContainerProfile container = containers.get(containerId);
|
||||
if (container == null) return;
|
||||
|
||||
if (container.getSecurityScore() < 50) {
|
||||
container.setStatus("ISOLATED");
|
||||
logEvent(containerId, "ISOLATED", "Container isolated due to low security score");
|
||||
logger.warn("Container {} isolated due to security score: {}", containerId, container.getSecurityScore());
|
||||
}
|
||||
|
||||
if (container.getVulnerabilities().stream().anyMatch(v -> v.getSeverity().equals("CRITICAL"))) {
|
||||
container.setStatus("QUARANTINED");
|
||||
logEvent(containerId, "QUARANTINED", "Container quarantined due to critical vulnerability");
|
||||
logger.warn("Container {} quarantined due to critical vulnerabilities", containerId);
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Object> getContainerSecurityStats(String containerId) {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
ContainerProfile container = containers.get(containerId);
|
||||
|
||||
if (container == null) {
|
||||
stats.put("error", "Container not found");
|
||||
return stats;
|
||||
}
|
||||
|
||||
stats.put("containerId", container.getContainerId());
|
||||
stats.put("imageName", container.getImageName());
|
||||
stats.put("securityScore", container.getSecurityScore());
|
||||
stats.put("status", container.getStatus());
|
||||
stats.put("vulnerabilities", container.getVulnerabilities().size());
|
||||
stats.put("lastScan", container.getLastScanTime());
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
public Map<String, Object> getStats() {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("totalContainersScanned", totalContainersScanned.get());
|
||||
stats.put("vulnerabilitiesFound", vulnerabilitiesFound.get());
|
||||
stats.put("threatsBlocked", threatsBlocked.get());
|
||||
stats.put("activeContainers", containers.size());
|
||||
stats.put("scanningEnabled", scanningEnabled);
|
||||
|
||||
long criticalVulns = 0;
|
||||
long highVulns = 0;
|
||||
for (ContainerProfile container : containers.values()) {
|
||||
for (Vulnerability vuln : container.getVulnerabilities()) {
|
||||
if (vuln.getSeverity().equals("CRITICAL")) criticalVulns++;
|
||||
if (vuln.getSeverity().equals("HIGH")) highVulns++;
|
||||
}
|
||||
}
|
||||
stats.put("criticalVulnerabilities", criticalVulns);
|
||||
stats.put("highVulnerabilities", highVulns);
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
public List<SecurityAlert> getAlerts(int limit) {
|
||||
List<SecurityAlert> alerts = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (SecurityAlert alert : alertQueue) {
|
||||
if (count++ >= limit) break;
|
||||
alerts.add(alert);
|
||||
}
|
||||
return alerts;
|
||||
}
|
||||
|
||||
private void triggerAlert(String containerId, String type, String message) {
|
||||
SecurityAlert alert = new SecurityAlert(containerId, type, message, LocalDateTime.now());
|
||||
alertQueue.offer(alert);
|
||||
if (alertQueue.size() > 1000) {
|
||||
alertQueue.poll();
|
||||
}
|
||||
logger.warn("Container Security Alert [{}]: {} - {}", type, containerId, message);
|
||||
}
|
||||
|
||||
private void logEvent(String containerId, String action, String details) {
|
||||
ContainerEvent event = new ContainerEvent(containerId, action, details, LocalDateTime.now());
|
||||
List<ContainerEvent> history = eventHistory.computeIfAbsent(containerId, k -> new ArrayList<>());
|
||||
history.add(event);
|
||||
if (history.size() > MAX_EVENTS) {
|
||||
history.remove(0);
|
||||
}
|
||||
}
|
||||
|
||||
public void enableScanning() {
|
||||
scanningEnabled = true;
|
||||
logger.info("Container security scanning enabled");
|
||||
}
|
||||
|
||||
public void disableScanning() {
|
||||
scanningEnabled = false;
|
||||
logger.info("Container security scanning disabled");
|
||||
}
|
||||
|
||||
public static class ContainerProfile {
|
||||
private final String containerId;
|
||||
private final String imageName;
|
||||
private volatile String status;
|
||||
private volatile int securityScore;
|
||||
private volatile LocalDateTime lastScanTime;
|
||||
private volatile int monitoringCount;
|
||||
private volatile double cpuUsage;
|
||||
private volatile double memoryUsage;
|
||||
private volatile int networkConnections;
|
||||
private volatile boolean suspiciousProcess;
|
||||
private List<Vulnerability> vulnerabilities;
|
||||
|
||||
public ContainerProfile(String containerId, String imageName, String status) {
|
||||
this.containerId = containerId;
|
||||
this.imageName = imageName;
|
||||
this.status = status;
|
||||
this.securityScore = 100;
|
||||
this.vulnerabilities = new ArrayList<>();
|
||||
}
|
||||
|
||||
public void incrementMonitoringCount() { monitoringCount++; }
|
||||
|
||||
public String getContainerId() { return containerId; }
|
||||
public String getImageName() { return imageName; }
|
||||
public String getStatus() { return status; }
|
||||
public void setStatus(String status) { this.status = status; }
|
||||
public int getSecurityScore() { return securityScore; }
|
||||
public void setSecurityScore(int securityScore) { this.securityScore = securityScore; }
|
||||
public LocalDateTime getLastScanTime() { return lastScanTime; }
|
||||
public void setLastScanTime(LocalDateTime lastScanTime) { this.lastScanTime = lastScanTime; }
|
||||
public int getMonitoringCount() { return monitoringCount; }
|
||||
public double getCpuUsage() { return cpuUsage; }
|
||||
public void setCpuUsage(double cpuUsage) { this.cpuUsage = cpuUsage; }
|
||||
public double getMemoryUsage() { return memoryUsage; }
|
||||
public void setMemoryUsage(double memoryUsage) { this.memoryUsage = memoryUsage; }
|
||||
public int getNetworkConnections() { return networkConnections; }
|
||||
public void setNetworkConnections(int networkConnections) { this.networkConnections = networkConnections; }
|
||||
public boolean hasSuspiciousProcess() { return suspiciousProcess; }
|
||||
public void setSuspiciousProcess(boolean suspiciousProcess) { this.suspiciousProcess = suspiciousProcess; }
|
||||
public List<Vulnerability> getVulnerabilities() { return vulnerabilities; }
|
||||
public void setVulnerabilities(List<Vulnerability> vulnerabilities) { this.vulnerabilities = vulnerabilities; }
|
||||
}
|
||||
|
||||
public static class ContainerImage {
|
||||
private final String imageName;
|
||||
private final String version;
|
||||
private volatile int securityScore;
|
||||
private List<Vulnerability> vulnerabilities;
|
||||
|
||||
public ContainerImage(String imageName, String version) {
|
||||
this.imageName = imageName;
|
||||
this.version = version;
|
||||
this.securityScore = 100;
|
||||
this.vulnerabilities = new ArrayList<>();
|
||||
}
|
||||
|
||||
public String getImageName() { return imageName; }
|
||||
public String getVersion() { return version; }
|
||||
public int getSecurityScore() { return securityScore; }
|
||||
public void setSecurityScore(int securityScore) { this.securityScore = securityScore; }
|
||||
public List<Vulnerability> getVulnerabilities() { return vulnerabilities; }
|
||||
public void setVulnerabilities(List<Vulnerability> vulnerabilities) { this.vulnerabilities = vulnerabilities; }
|
||||
}
|
||||
|
||||
public static class Vulnerability {
|
||||
private final String id;
|
||||
private final String severity;
|
||||
private final String description;
|
||||
private final String remediation;
|
||||
|
||||
public Vulnerability(String id, String severity, String description, String remediation) {
|
||||
this.id = id;
|
||||
this.severity = severity;
|
||||
this.description = description;
|
||||
this.remediation = remediation;
|
||||
}
|
||||
|
||||
public String getId() { return id; }
|
||||
public String getSeverity() { return severity; }
|
||||
public String getDescription() { return description; }
|
||||
public String getRemediation() { return remediation; }
|
||||
}
|
||||
|
||||
public static class ContainerEvent {
|
||||
private final String containerId;
|
||||
private final String action;
|
||||
private final String details;
|
||||
private final LocalDateTime timestamp;
|
||||
|
||||
public ContainerEvent(String containerId, String action, String details, LocalDateTime timestamp) {
|
||||
this.containerId = containerId;
|
||||
this.action = action;
|
||||
this.details = details;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getContainerId() { return containerId; }
|
||||
public String getAction() { return action; }
|
||||
public String getDetails() { return details; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
}
|
||||
|
||||
public static class SecurityAlert {
|
||||
private final String containerId;
|
||||
private final String type;
|
||||
private final String message;
|
||||
private final LocalDateTime timestamp;
|
||||
|
||||
public SecurityAlert(String containerId, String type, String message, LocalDateTime timestamp) {
|
||||
this.containerId = containerId;
|
||||
this.type = type;
|
||||
this.message = message;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getContainerId() { return containerId; }
|
||||
public String getType() { return type; }
|
||||
public String getMessage() { return message; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import com.aluer.config.ServerGuardConfig;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
@Service
|
||||
public class DDoSDefenseCoordinator {
|
||||
private static final long WINDOW_MS = 60_000L;
|
||||
|
||||
private final ServerGuardConfig config;
|
||||
private final DDoSProtectionService ddosProtectionService;
|
||||
private final DistributedAttackMitigationService distributedAttackMitigationService;
|
||||
private final MinecraftProtocolSecurityService minecraftProtocolSecurityService;
|
||||
private final HostEnforcementService hostEnforcementService;
|
||||
|
||||
private final Map<String, SlowConnectionTracker> slowConnections = new ConcurrentHashMap<>();
|
||||
private final ConcurrentLinkedQueue<DDoSIncidentSummary> incidents = new ConcurrentLinkedQueue<>();
|
||||
|
||||
public DDoSDefenseCoordinator() {
|
||||
this(new ServerGuardConfig(), new DDoSProtectionService(), new DistributedAttackMitigationService(),
|
||||
new MinecraftProtocolSecurityService(), new HostEnforcementService());
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public DDoSDefenseCoordinator(ServerGuardConfig config,
|
||||
DDoSProtectionService ddosProtectionService,
|
||||
DistributedAttackMitigationService distributedAttackMitigationService,
|
||||
MinecraftProtocolSecurityService minecraftProtocolSecurityService,
|
||||
HostEnforcementService hostEnforcementService) {
|
||||
this.config = config;
|
||||
this.ddosProtectionService = ddosProtectionService;
|
||||
this.distributedAttackMitigationService = distributedAttackMitigationService;
|
||||
this.minecraftProtocolSecurityService = minecraftProtocolSecurityService;
|
||||
this.hostEnforcementService = hostEnforcementService;
|
||||
}
|
||||
|
||||
public List<DDoSIncidentSummary> analyzeTraffic(String sourceIP, String destIP, int sourcePort, int destPort,
|
||||
String protocol, byte[] payload) {
|
||||
if (!config.getSecurity().getDdosDefense().isEnabled()) {
|
||||
return List.of();
|
||||
}
|
||||
|
||||
List<DDoSIncidentSummary> result = new ArrayList<>();
|
||||
int payloadSize = payload == null ? 0 : payload.length;
|
||||
String normalizedProtocol = protocol == null ? "TCP" : protocol.toUpperCase(Locale.ROOT);
|
||||
String payloadText = payload == null ? "" : new String(payload, StandardCharsets.UTF_8).toLowerCase(Locale.ROOT);
|
||||
|
||||
boolean allowed = ddosProtectionService.checkConnection(sourceIP);
|
||||
if (!allowed) {
|
||||
result.add(recordIncident(sourceIP, "L4", "CONNECTION_FLOOD", 90, "Connection flood threshold exceeded"));
|
||||
}
|
||||
|
||||
ddosProtectionService.checkPacketSize(sourceIP, payloadSize);
|
||||
ddosProtectionService.checkBandwidth(sourceIP, payloadSize);
|
||||
|
||||
if ("TCP".equals(normalizedProtocol)) {
|
||||
ddosProtectionService.checkSynFlood(sourceIP);
|
||||
} else if ("UDP".equals(normalizedProtocol)) {
|
||||
ddosProtectionService.checkUDPAmplification(sourceIP, payloadSize);
|
||||
} else if ("ICMP".equals(normalizedProtocol)) {
|
||||
ddosProtectionService.checkICMPFlood(sourceIP);
|
||||
}
|
||||
|
||||
if (containsHttpTraffic(payloadText)) {
|
||||
boolean httpAllowed = ddosProtectionService.checkHTTPFlood(sourceIP, detectUserAgent(payloadText), detectPath(payloadText));
|
||||
if (!httpAllowed) {
|
||||
result.add(recordIncident(sourceIP, "L7", "HTTP_FLOOD", 88, "HTTP flood threshold exceeded"));
|
||||
}
|
||||
}
|
||||
|
||||
SlowConnectionTracker tracker = slowConnections.computeIfAbsent(sourceIP, SlowConnectionTracker::new);
|
||||
tracker.record(payloadSize);
|
||||
if (tracker.isSlowConnection(config.getSecurity().getDdosDefense().getSlowConnectionThreshold())) {
|
||||
result.add(recordIncident(sourceIP, "L7", "SLOW_CONNECTION", 70, "Slow connection pattern detected"));
|
||||
}
|
||||
|
||||
for (MinecraftProtocolSecurityService.MinecraftThreatSignal signal : minecraftProtocolSecurityService.analyzePacket(
|
||||
sourceIP, destIP, sourcePort, destPort, payload)) {
|
||||
String category = signal.getType().contains("RCON") ? "MINECRAFT-RCON" : "MINECRAFT";
|
||||
result.add(recordIncident(sourceIP, category, signal.getType(), signal.getConfidence(), signal.getDetails()));
|
||||
}
|
||||
|
||||
Map<String, Object> trafficData = new HashMap<>();
|
||||
trafficData.put("packetPattern", buildPacketPattern(normalizedProtocol, payloadText));
|
||||
trafficData.put("requestPattern", payloadText);
|
||||
trafficData.put("flags", normalizedProtocol);
|
||||
distributedAttackMitigationService.analyzeTraffic(sourceIP, trafficData);
|
||||
|
||||
if (ddosProtectionService.isBlocked(sourceIP)) {
|
||||
hostEnforcementService.blockIp(sourceIP, "DDoS coordinator escalation", config.getSecurity().getHostEnforcement().getDefaultBlockMinutes());
|
||||
}
|
||||
|
||||
trimIncidentLog();
|
||||
return result;
|
||||
}
|
||||
|
||||
public Map<String, Object> getPosture() {
|
||||
Map<String, Object> ddosStats = ddosProtectionService.getStats();
|
||||
Map<String, Object> mitigationStats = distributedAttackMitigationService.getStats();
|
||||
Map<String, Object> minecraftPosture = minecraftProtocolSecurityService.getPosture();
|
||||
|
||||
Map<String, Object> posture = new LinkedHashMap<>();
|
||||
posture.put("blockedIPs", ddosStats.getOrDefault("blockedIPs", 0));
|
||||
posture.put("totalDetected", ddosStats.getOrDefault("totalDetected", 0));
|
||||
posture.put("totalBlocked", ddosStats.getOrDefault("totalBlocked", 0));
|
||||
posture.put("activeIncidents", incidents.size());
|
||||
posture.put("mitigatedIncidents", mitigationStats.getOrDefault("totalAttacksMitigated", 0));
|
||||
posture.put("defenseLevel", mitigationStats.getOrDefault("currentDefenseLevel", "LOW"));
|
||||
posture.put("minecraftThreats", minecraftPosture.getOrDefault("recentSignals", 0));
|
||||
posture.put("botSwarmSources", minecraftPosture.getOrDefault("botSwarmSources", 0));
|
||||
posture.put("recentIncidents", getRecentIncidents(5));
|
||||
return posture;
|
||||
}
|
||||
|
||||
public List<DDoSIncidentSummary> getRecentIncidents(int limit) {
|
||||
List<DDoSIncidentSummary> result = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (DDoSIncidentSummary incident : incidents) {
|
||||
if (count++ >= limit) {
|
||||
break;
|
||||
}
|
||||
result.add(incident);
|
||||
}
|
||||
result.sort(Comparator.comparingLong(DDoSIncidentSummary::getTimestamp).reversed());
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<DDoSIncidentSummary> getIncidentsForIp(String ip, int limit) {
|
||||
List<DDoSIncidentSummary> result = new ArrayList<>();
|
||||
for (DDoSIncidentSummary incident : getRecentIncidents(200)) {
|
||||
if (ip.equals(incident.getSourceIP())) {
|
||||
result.add(incident);
|
||||
if (result.size() >= limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private DDoSIncidentSummary recordIncident(String sourceIP, String layer, String type, int severity, String details) {
|
||||
DDoSIncidentSummary incident = new DDoSIncidentSummary(sourceIP, layer, type, severity, details, Instant.now().toEpochMilli());
|
||||
incidents.offer(incident);
|
||||
return incident;
|
||||
}
|
||||
|
||||
private void trimIncidentLog() {
|
||||
while (incidents.size() > 1000) {
|
||||
incidents.poll();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean containsHttpTraffic(String payload) {
|
||||
return payload.startsWith("get ") || payload.startsWith("post ") || payload.contains(" http/1.");
|
||||
}
|
||||
|
||||
private String detectUserAgent(String payload) {
|
||||
int index = payload.indexOf("user-agent:");
|
||||
if (index < 0) {
|
||||
return "unknown";
|
||||
}
|
||||
int end = payload.indexOf('\n', index);
|
||||
return end > index ? payload.substring(index, end).trim() : payload.substring(index).trim();
|
||||
}
|
||||
|
||||
private String detectPath(String payload) {
|
||||
if (payload.startsWith("get ") || payload.startsWith("post ")) {
|
||||
String[] parts = payload.split(" ");
|
||||
if (parts.length > 1) {
|
||||
return parts[1];
|
||||
}
|
||||
}
|
||||
return "/";
|
||||
}
|
||||
|
||||
private String buildPacketPattern(String protocol, String payloadText) {
|
||||
if (payloadText.contains("rcon")) {
|
||||
return "RCON failed repeated";
|
||||
}
|
||||
if (payloadText.contains("status") || payloadText.contains("ping")) {
|
||||
return "MINECRAFT status flood";
|
||||
}
|
||||
if (payloadText.contains("login")) {
|
||||
return "MINECRAFT login flood";
|
||||
}
|
||||
if ("UDP".equals(protocol) && payloadText.length() > 1024) {
|
||||
return "UDP flood";
|
||||
}
|
||||
return protocol + " traffic";
|
||||
}
|
||||
|
||||
private static final class SlowConnectionTracker {
|
||||
private final String sourceIP;
|
||||
private final List<Entry> entries = new ArrayList<>();
|
||||
|
||||
private SlowConnectionTracker(String sourceIP) {
|
||||
this.sourceIP = sourceIP;
|
||||
}
|
||||
|
||||
private void record(int size) {
|
||||
long now = System.currentTimeMillis();
|
||||
entries.removeIf(entry -> entry.timestamp < now - WINDOW_MS);
|
||||
entries.add(new Entry(now, size));
|
||||
}
|
||||
|
||||
private boolean isSlowConnection(int threshold) {
|
||||
if (entries.size() < threshold) {
|
||||
return false;
|
||||
}
|
||||
long smallPackets = entries.stream().filter(entry -> entry.size < 64).count();
|
||||
return smallPackets > entries.size() * 0.7;
|
||||
}
|
||||
|
||||
private record Entry(long timestamp, int size) {
|
||||
}
|
||||
}
|
||||
|
||||
public static class DDoSIncidentSummary {
|
||||
private final String sourceIP;
|
||||
private final String layer;
|
||||
private final String type;
|
||||
private final int severity;
|
||||
private final String details;
|
||||
private final long timestamp;
|
||||
|
||||
public DDoSIncidentSummary(String sourceIP, String layer, String type, int severity, String details, long timestamp) {
|
||||
this.sourceIP = sourceIP;
|
||||
this.layer = layer;
|
||||
this.type = type;
|
||||
this.severity = severity;
|
||||
this.details = details;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getSourceIP() { return sourceIP; }
|
||||
public String getLayer() { return layer; }
|
||||
public String getType() { return type; }
|
||||
public int getSeverity() { return severity; }
|
||||
public String getDetails() { return details; }
|
||||
public long getTimestamp() { return timestamp; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,438 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.*;
|
||||
import java.net.*;
|
||||
import java.nio.*;
|
||||
|
||||
@Service
|
||||
public class DDoSProtectionService {
|
||||
|
||||
private final Map<String, TrafficRecord> trafficMap = new ConcurrentHashMap<>();
|
||||
private final Map<String, BlockedIP> blockedIPs = new ConcurrentHashMap<>();
|
||||
private final Map<String, List<Long>> requestTimestamps = new ConcurrentHashMap<>();
|
||||
private final Map<String, AtomicInteger> connectionCounts = new ConcurrentHashMap<>();
|
||||
private final Queue<AttackEvent> attackLog = new ConcurrentLinkedQueue<>();
|
||||
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
|
||||
|
||||
private static final int MAX_REQUESTS_PER_SECOND = 100;
|
||||
private static final int MAX_CONNECTIONS_PER_IP = 50;
|
||||
private static final long BLOCK_DURATION = 300000;
|
||||
private static final long TRAFFIC_WINDOW = 1000;
|
||||
private static final int MAX_PACKET_SIZE = 65535;
|
||||
private static final double MAX_BYTES_PER_SECOND = 100_000_000;
|
||||
|
||||
private final AtomicLong totalBlocked = new AtomicLong(0);
|
||||
private final AtomicLong totalDetected = new AtomicLong(0);
|
||||
|
||||
public DDoSProtectionService() {
|
||||
startCleanupTask();
|
||||
}
|
||||
|
||||
public boolean checkConnection(String ip) {
|
||||
if (isBlocked(ip)) {
|
||||
logAttack(ip, "BLOCKED_CONNECTION", "Connection from blocked IP");
|
||||
return false;
|
||||
}
|
||||
|
||||
AtomicInteger count = connectionCounts.computeIfAbsent(ip, k -> new AtomicInteger(0));
|
||||
int current = count.incrementAndGet();
|
||||
|
||||
if (current > MAX_CONNECTIONS_PER_IP) {
|
||||
blockIP(ip, "CONNECTION_FLOOD", "Too many connections: " + current);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean checkRequest(String ip, String endpoint) {
|
||||
if (isBlocked(ip)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<Long> timestamps = requestTimestamps.computeIfAbsent(ip + ":" + endpoint, k -> new CopyOnWriteArrayList<>());
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
timestamps.removeIf(t -> now - t > TRAFFIC_WINDOW);
|
||||
timestamps.add(now);
|
||||
|
||||
if (timestamps.size() > MAX_REQUESTS_PER_SECOND) {
|
||||
blockIP(ip, "REQUEST_FLOOD", "Too many requests: " + timestamps.size());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean checkPacketSize(String ip, int size) {
|
||||
if (isBlocked(ip)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (size > MAX_PACKET_SIZE) {
|
||||
blockIP(ip, "OVERSIZED_PACKET", "Packet size: " + size);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean checkBandwidth(String ip, long bytes) {
|
||||
if (isBlocked(ip)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
TrafficRecord record = trafficMap.computeIfAbsent(ip, k -> new TrafficRecord(ip));
|
||||
record.addBytes(bytes);
|
||||
|
||||
if (record.getBytesPerSecond() > MAX_BYTES_PER_SECOND) {
|
||||
blockIP(ip, "BANDWIDTH_EXCEED", "Bandwidth: " + record.getBytesPerSecond());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean checkPort(String ip, int port) {
|
||||
if (isBlocked(ip)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Set<Integer> scannedPorts = recordPortScan(ip, port);
|
||||
|
||||
if (scannedPorts.size() > 20) {
|
||||
blockIP(ip, "PORT_SCAN", "Scanned " + scannedPorts.size() + " ports");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private Set<Integer> recordPortScan(String ip, int port) {
|
||||
Map<Integer, Long> portScans = portScanMap.computeIfAbsent(ip, k -> new ConcurrentHashMap<>());
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
portScans.entrySet().removeIf(e -> now - e.getValue() > 60000);
|
||||
portScans.put(port, now);
|
||||
|
||||
return portScans.keySet();
|
||||
}
|
||||
|
||||
private final Map<String, Map<Integer, Long>> portScanMap = new ConcurrentHashMap<>();
|
||||
|
||||
public boolean checkSynFlood(String ip) {
|
||||
if (isBlocked(ip)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AtomicInteger synCount = synCountMap.computeIfAbsent(ip, k -> new AtomicInteger(0));
|
||||
int count = synCount.incrementAndGet();
|
||||
|
||||
if (count > 100) {
|
||||
blockIP(ip, "SYN_FLOOD", "SYN packets: " + count);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private final Map<String, AtomicInteger> synCountMap = new ConcurrentHashMap<>();
|
||||
|
||||
public boolean checkFragmentation(String ip, short identification, int offset) {
|
||||
if (isBlocked(ip)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
FragmentRecord frag = fragmentationMap.computeIfAbsent(ip, k -> new FragmentRecord());
|
||||
if (frag.addFragment(identification, offset)) {
|
||||
blockIP(ip, "FRAGMENTATION_ATTACK", "Too many fragments");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private final Map<String, FragmentRecord> fragmentationMap = new ConcurrentHashMap<>();
|
||||
|
||||
public boolean checkUDPAmplification(String ip, int payloadSize) {
|
||||
if (isBlocked(ip)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AtomicInteger udpCount = udpAmplificationMap.computeIfAbsent(ip, k -> new AtomicInteger(0));
|
||||
int count = udpCount.incrementAndGet();
|
||||
|
||||
if (count > 50 && payloadSize > 1000) {
|
||||
blockIP(ip, "UDP_AMPLIFICATION", "Large UDP packets: " + count);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private final Map<String, AtomicInteger> udpAmplificationMap = new ConcurrentHashMap<>();
|
||||
|
||||
public boolean checkICMPFlood(String ip) {
|
||||
if (isBlocked(ip)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AtomicInteger icmpCount = icmpFloodMap.computeIfAbsent(ip, k -> new AtomicInteger(0));
|
||||
int count = icmpCount.incrementAndGet();
|
||||
|
||||
if (count > 50) {
|
||||
blockIP(ip, "ICMP_FLOOD", "ICMP packets: " + count);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private final Map<String, AtomicInteger> icmpFloodMap = new ConcurrentHashMap<>();
|
||||
|
||||
public boolean checkHTTPFlood(String ip, String userAgent, String path) {
|
||||
if (isBlocked(ip)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<String> paths = httpFloodMap.computeIfAbsent(ip, k -> new CopyOnWriteArrayList<>());
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
paths.add(now + ":" + path);
|
||||
paths.removeIf(p -> Long.parseLong(p.split(":")[0]) < now - 10000);
|
||||
|
||||
Map<String, Integer> pathCounts = new HashMap<>();
|
||||
for (String p : paths) {
|
||||
String pathOnly = p.split(":")[1];
|
||||
pathCounts.merge(pathOnly, 1, Integer::sum);
|
||||
}
|
||||
|
||||
String problematicPath = null;
|
||||
for (Map.Entry<String, Integer> entry : pathCounts.entrySet()) {
|
||||
if (entry.getValue() > 30) {
|
||||
problematicPath = entry.getKey();
|
||||
blockIP(ip, "HTTP_FLOOD", "Path " + problematicPath + " hit " + entry.getValue() + " times");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isKnownBot(userAgent)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (paths.size() > 200) {
|
||||
blockIP(ip, "HTTP_FLOOD", "Too many requests");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private final Map<String, List<String>> httpFloodMap = new ConcurrentHashMap<>();
|
||||
|
||||
private boolean isKnownBot(String userAgent) {
|
||||
if (userAgent == null) return false;
|
||||
String lower = userAgent.toLowerCase();
|
||||
return lower.contains("googlebot") || lower.contains("bingbot") ||
|
||||
lower.contains("slurp") || lower.contains("duckduckbot");
|
||||
}
|
||||
|
||||
public boolean checkSSLHandshake(String ip) {
|
||||
if (isBlocked(ip)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
AtomicInteger handshakeCount = sslHandshakeMap.computeIfAbsent(ip, k -> new AtomicInteger(0));
|
||||
int count = handshakeCount.incrementAndGet();
|
||||
|
||||
if (count > 30) {
|
||||
blockIP(ip, "SSL_FLOOD", "SSL handshakes: " + count);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private final Map<String, AtomicInteger> sslHandshakeMap = new ConcurrentHashMap<>();
|
||||
|
||||
public void blockIP(String ip, String reason, String details) {
|
||||
if (blockedIPs.containsKey(ip)) {
|
||||
BlockedIP existing = blockedIPs.get(ip);
|
||||
existing.count.incrementAndGet();
|
||||
existing.lastBlockTime = System.currentTimeMillis();
|
||||
} else {
|
||||
BlockedIP blocked = new BlockedIP(ip, reason, details);
|
||||
blockedIPs.put(ip, blocked);
|
||||
totalBlocked.incrementAndGet();
|
||||
}
|
||||
|
||||
logAttack(ip, reason, details);
|
||||
}
|
||||
|
||||
public void unblockIP(String ip) {
|
||||
blockedIPs.remove(ip);
|
||||
trafficMap.remove(ip);
|
||||
requestTimestamps.entrySet().removeIf(e -> e.getKey().startsWith(ip));
|
||||
connectionCounts.remove(ip);
|
||||
}
|
||||
|
||||
public boolean isBlocked(String ip) {
|
||||
BlockedIP blocked = blockedIPs.get(ip);
|
||||
if (blocked == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (System.currentTimeMillis() - blocked.lastBlockTime > BLOCK_DURATION) {
|
||||
unblockIP(ip);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public BlockedIP getBlockInfo(String ip) {
|
||||
return blockedIPs.get(ip);
|
||||
}
|
||||
|
||||
public Collection<BlockedIP> getAllBlockedIPs() {
|
||||
return blockedIPs.values();
|
||||
}
|
||||
|
||||
public void logAttack(String ip, String type, String details) {
|
||||
AttackEvent event = new AttackEvent(ip, type, details, System.currentTimeMillis());
|
||||
attackLog.offer(event);
|
||||
totalDetected.incrementAndGet();
|
||||
|
||||
while (attackLog.size() > 10000) {
|
||||
attackLog.poll();
|
||||
}
|
||||
}
|
||||
|
||||
public List<AttackEvent> getAttackLog(int limit) {
|
||||
List<AttackEvent> result = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (AttackEvent event : attackLog) {
|
||||
if (count++ >= limit) break;
|
||||
result.add(event);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void startCleanupTask() {
|
||||
scheduler.scheduleAtFixedRate(() -> {
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
for (Map.Entry<String, BlockedIP> entry : blockedIPs.entrySet()) {
|
||||
if (now - entry.getValue().lastBlockTime > BLOCK_DURATION) {
|
||||
unblockIP(entry.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
trafficMap.entrySet().removeIf(e -> e.getValue().isStale());
|
||||
requestTimestamps.entrySet().removeIf(e -> {
|
||||
List<Long> list = e.getValue();
|
||||
list.removeIf(t -> now - t > TRAFFIC_WINDOW);
|
||||
return list.isEmpty();
|
||||
});
|
||||
|
||||
connectionCounts.entrySet().removeIf(e -> e.getValue().get() == 0);
|
||||
|
||||
synCountMap.entrySet().removeIf(e -> e.getValue().get() == 0);
|
||||
icmpFloodMap.entrySet().removeIf(e -> e.getValue().get() == 0);
|
||||
udpAmplificationMap.entrySet().removeIf(e -> e.getValue().get() == 0);
|
||||
|
||||
}, 30, 30, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public Map<String, Object> getStats() {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("blockedIPs", blockedIPs.size());
|
||||
stats.put("totalBlocked", totalBlocked.get());
|
||||
stats.put("totalDetected", totalDetected.get());
|
||||
stats.put("activeTrafficRecords", trafficMap.size());
|
||||
return stats;
|
||||
}
|
||||
|
||||
public static class TrafficRecord {
|
||||
private final String ip;
|
||||
private final AtomicLong totalBytes = new AtomicLong(0);
|
||||
private final AtomicLong windowBytes = new AtomicLong(0);
|
||||
private volatile long lastUpdate = System.currentTimeMillis();
|
||||
|
||||
public TrafficRecord(String ip) {
|
||||
this.ip = ip;
|
||||
}
|
||||
|
||||
public void addBytes(long bytes) {
|
||||
long now = System.currentTimeMillis();
|
||||
if (now - lastUpdate > 1000) {
|
||||
windowBytes.set(bytes);
|
||||
lastUpdate = now;
|
||||
} else {
|
||||
windowBytes.addAndGet(bytes);
|
||||
}
|
||||
totalBytes.addAndGet(bytes);
|
||||
}
|
||||
|
||||
public long getBytesPerSecond() {
|
||||
return windowBytes.get();
|
||||
}
|
||||
|
||||
public long getTotalBytes() {
|
||||
return totalBytes.get();
|
||||
}
|
||||
|
||||
public boolean isStale() {
|
||||
return System.currentTimeMillis() - lastUpdate > 60000;
|
||||
}
|
||||
}
|
||||
|
||||
public static class BlockedIP {
|
||||
public final String ip;
|
||||
public final String reason;
|
||||
public final String details;
|
||||
public final long firstBlockTime;
|
||||
public volatile long lastBlockTime;
|
||||
public final AtomicInteger count = new AtomicInteger(1);
|
||||
|
||||
public BlockedIP(String ip, String reason, String details) {
|
||||
this.ip = ip;
|
||||
this.reason = reason;
|
||||
this.details = details;
|
||||
this.firstBlockTime = System.currentTimeMillis();
|
||||
this.lastBlockTime = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
|
||||
public static class AttackEvent {
|
||||
public final String ip;
|
||||
public final String type;
|
||||
public final String details;
|
||||
public final long timestamp;
|
||||
|
||||
public AttackEvent(String ip, String type, String details, long timestamp) {
|
||||
this.ip = ip;
|
||||
this.type = type;
|
||||
this.details = details;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
public static class FragmentRecord {
|
||||
private final Map<Short, Set<Integer>> fragments = new ConcurrentHashMap<>();
|
||||
|
||||
public synchronized boolean addFragment(short identification, int offset) {
|
||||
Set<Integer> offsets = fragments.computeIfAbsent(identification, k -> ConcurrentHashMap.newKeySet());
|
||||
offsets.add(offset);
|
||||
|
||||
if (offsets.size() > 100) {
|
||||
fragments.remove(identification);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,423 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.net.InetAddress;
|
||||
|
||||
@Component
|
||||
public class DNSSecurityService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(DNSSecurityService.class);
|
||||
|
||||
private final Map<String, DNSQueryRecord> queryCache = new ConcurrentHashMap<>();
|
||||
private final Map<String, List<ClientQueryHistory>> clientQueryHistory = new ConcurrentHashMap<>();
|
||||
private final Map<String, DNSZone> zones = new ConcurrentHashMap<>();
|
||||
private final Queue<DNSEvent> eventLog = new ConcurrentLinkedQueue<>();
|
||||
private final AtomicLong totalQueries = new AtomicLong(0);
|
||||
private final AtomicLong blockedQueries = new AtomicLong(0);
|
||||
private final AtomicLong suspiciousQueries = new AtomicLong(0);
|
||||
|
||||
private static final int MAX_QUERIES_PER_MINUTE = 60;
|
||||
private static final int MAX_QUERIES_PER_HOUR = 1000;
|
||||
private static final int CACHE_TTL_SECONDS = 300;
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
private final Set<String> blockedDomains = new HashSet<>(Arrays.asList(
|
||||
"malware.com", "phishing.com", "ransomware.com", "trojan.com",
|
||||
"coinminer.com", "adservice.com", "tracker.com", "spyware.com"
|
||||
));
|
||||
|
||||
private final Set<String> suspiciousTLDs = new HashSet<>(Arrays.asList(
|
||||
".tk", ".ml", ".ga", ".cf", ".gq", ".xyz", ".top", ".work"
|
||||
));
|
||||
|
||||
public DNSSecurityService() {
|
||||
initializeDefaultZones();
|
||||
}
|
||||
|
||||
private void initializeDefaultZones() {
|
||||
DNSZone minecraftZone = new DNSZone("minecraft");
|
||||
minecraftZone.addRecord("auth.mojang.com", "13.107.42.20");
|
||||
minecraftZone.addRecord("sessionserver.mojang.com", "13.107.42.20");
|
||||
minecraftZone.addRecord("api.mojang.com", "13.107.42.20");
|
||||
minecraftZone.addRecord("textures.minecraft.net", "13.107.42.20");
|
||||
zones.put("minecraft", minecraftZone);
|
||||
|
||||
DNSZone localZone = new DNSZone("local");
|
||||
localZone.addRecord("localhost", "127.0.0.1");
|
||||
zones.put("local", localZone);
|
||||
}
|
||||
|
||||
public DNSQueryResult processQuery(String clientIP, String domainName, int queryType) {
|
||||
DNSQueryResult result = new DNSQueryResult();
|
||||
result.setClientIP(clientIP);
|
||||
result.setDomainName(domainName);
|
||||
result.setQueryType(queryType);
|
||||
result.setTimestamp(LocalDateTime.now());
|
||||
|
||||
totalQueries.incrementAndGet();
|
||||
|
||||
if (isRateLimited(clientIP)) {
|
||||
result.setBlocked(true);
|
||||
result.setReason("Rate limit exceeded");
|
||||
blockedQueries.incrementAndGet();
|
||||
logEvent(clientIP, domainName, "BLOCKED", "Rate limit exceeded");
|
||||
return result;
|
||||
}
|
||||
|
||||
if (isDomainBlocked(domainName)) {
|
||||
result.setBlocked(true);
|
||||
result.setReason("Blocked domain");
|
||||
result.setBlocked(true);
|
||||
blockedQueries.incrementAndGet();
|
||||
logEvent(clientIP, domainName, "BLOCKED", "Blocked domain");
|
||||
return result;
|
||||
}
|
||||
|
||||
if (isSuspiciousDomain(domainName)) {
|
||||
result.setSuspicious(true);
|
||||
result.setWarning("Suspicious TLD or pattern");
|
||||
suspiciousQueries.incrementAndGet();
|
||||
logEvent(clientIP, domainName, "SUSPICIOUS", "Suspicious domain pattern");
|
||||
}
|
||||
|
||||
if (isDNSSplitTunneling(clientIP, domainName)) {
|
||||
result.setSuspicious(true);
|
||||
result.setWarning("Possible DNS tunnel detected");
|
||||
suspiciousQueries.incrementAndGet();
|
||||
logEvent(clientIP, domainName, "SUSPICIOUS", "Possible DNS tunnel");
|
||||
}
|
||||
|
||||
DNSQueryRecord record = new DNSQueryRecord(clientIP, domainName, queryType, LocalDateTime.now());
|
||||
queryCache.put(domainName + ":" + clientIP, record);
|
||||
|
||||
List<ClientQueryHistory> history = clientQueryHistory.computeIfAbsent(clientIP, k -> new ArrayList<>());
|
||||
history.add(new ClientQueryHistory(domainName, LocalDateTime.now()));
|
||||
|
||||
if (history.size() > 1000) {
|
||||
history.subList(0, 500).clear();
|
||||
}
|
||||
|
||||
String resolvedIP = resolveDomain(domainName);
|
||||
result.setResolvedIP(resolvedIP);
|
||||
result.setBlocked(false);
|
||||
|
||||
logEvent(clientIP, domainName, "ALLOWED", "Query allowed");
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean isRateLimited(String clientIP) {
|
||||
List<ClientQueryHistory> history = clientQueryHistory.get(clientIP);
|
||||
if (history == null) return false;
|
||||
|
||||
LocalDateTime oneMinuteAgo = LocalDateTime.now().minusMinutes(1);
|
||||
long queriesLastMinute = history.stream()
|
||||
.filter(q -> q.getTimestamp().isAfter(oneMinuteAgo))
|
||||
.count();
|
||||
|
||||
if (queriesLastMinute > MAX_QUERIES_PER_MINUTE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
LocalDateTime oneHourAgo = LocalDateTime.now().minusHours(1);
|
||||
long queriesLastHour = history.stream()
|
||||
.filter(q -> q.getTimestamp().isAfter(oneHourAgo))
|
||||
.count();
|
||||
|
||||
return queriesLastHour > MAX_QUERIES_PER_HOUR;
|
||||
}
|
||||
|
||||
private boolean isDomainBlocked(String domainName) {
|
||||
String lowerDomain = domainName.toLowerCase();
|
||||
for (String blocked : blockedDomains) {
|
||||
if (lowerDomain.endsWith(blocked) || lowerDomain.equals(blocked)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isSuspiciousDomain(String domainName) {
|
||||
String lowerDomain = domainName.toLowerCase();
|
||||
|
||||
for (String tld : suspiciousTLDs) {
|
||||
if (lowerDomain.endsWith(tld)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (lowerDomain.length() > 50) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int dots = lowerDomain.split("\\.").length - 1;
|
||||
if (dots > 5) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lowerDomain.contains("----") || lowerDomain.contains("----")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isDNSSplitTunneling(String clientIP, String domainName) {
|
||||
List<ClientQueryHistory> history = clientQueryHistory.get(clientIP);
|
||||
if (history == null || history.size() < 10) return false;
|
||||
|
||||
LocalDateTime recent = LocalDateTime.now().minusMinutes(5);
|
||||
long recentQueries = history.stream()
|
||||
.filter(q -> q.getTimestamp().isAfter(recent))
|
||||
.count();
|
||||
|
||||
if (recentQueries < 100) return false;
|
||||
|
||||
Set<String> uniqueDomains = history.stream()
|
||||
.filter(q -> q.getTimestamp().isAfter(recent))
|
||||
.map(ClientQueryHistory::getDomain)
|
||||
.collect(java.util.stream.Collectors.toSet());
|
||||
|
||||
double entropy = calculateEntropy(String.join(".", uniqueDomains));
|
||||
return entropy > 4.5;
|
||||
}
|
||||
|
||||
private double calculateEntropy(String data) {
|
||||
if (data == null || data.isEmpty()) return 0;
|
||||
|
||||
Map<Character, Long> frequency = new HashMap<>();
|
||||
for (char c : data.toCharArray()) {
|
||||
frequency.merge(c, 1L, Long::sum);
|
||||
}
|
||||
|
||||
double entropy = 0;
|
||||
int length = data.length();
|
||||
for (long count : frequency.values()) {
|
||||
double probability = (double) count / length;
|
||||
entropy -= probability * (Math.log(probability) / Math.log(2));
|
||||
}
|
||||
|
||||
return entropy;
|
||||
}
|
||||
|
||||
private String resolveDomain(String domainName) {
|
||||
for (DNSZone zone : zones.values()) {
|
||||
String ip = zone.resolve(domainName);
|
||||
if (ip != null) {
|
||||
return ip;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
InetAddress address = InetAddress.getByName(domainName);
|
||||
return address.getHostAddress();
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void addBlockedDomain(String domain) {
|
||||
blockedDomains.add(domain.toLowerCase());
|
||||
logger.info("Added blocked domain: {}", domain);
|
||||
}
|
||||
|
||||
public void removeBlockedDomain(String domain) {
|
||||
blockedDomains.remove(domain.toLowerCase());
|
||||
logger.info("Removed blocked domain: {}", domain);
|
||||
}
|
||||
|
||||
public void addZone(String zoneName, Map<String, String> records) {
|
||||
DNSZone zone = new DNSZone(zoneName);
|
||||
for (Map.Entry<String, String> entry : records.entrySet()) {
|
||||
zone.addRecord(entry.getKey(), entry.getValue());
|
||||
}
|
||||
zones.put(zoneName, zone);
|
||||
logger.info("Added DNS zone: {}", zoneName);
|
||||
}
|
||||
|
||||
public Map<String, Object> getStats() {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("totalQueries", totalQueries.get());
|
||||
stats.put("blockedQueries", blockedQueries.get());
|
||||
stats.put("suspiciousQueries", suspiciousQueries.get());
|
||||
stats.put("cachedQueries", queryCache.size());
|
||||
stats.put("activeClients", clientQueryHistory.size());
|
||||
stats.put("zonesConfigured", zones.size());
|
||||
stats.put("blockedDomains", blockedDomains.size());
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
public Map<String, DNSQueryRecord> getRecentQueries(int limit) {
|
||||
Map<String, DNSQueryRecord> recent = new LinkedHashMap<>();
|
||||
int count = 0;
|
||||
for (Map.Entry<String, DNSQueryRecord> entry : queryCache.entrySet()) {
|
||||
if (count++ >= limit) break;
|
||||
recent.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
return recent;
|
||||
}
|
||||
|
||||
public List<String> getTopQueriedDomains(int limit) {
|
||||
Map<String, Long> domainCounts = new HashMap<>();
|
||||
for (DNSQueryRecord record : queryCache.values()) {
|
||||
String domain = record.getDomainName();
|
||||
domainCounts.merge(domain, 1L, Long::sum);
|
||||
}
|
||||
|
||||
return domainCounts.entrySet().stream()
|
||||
.sorted(Map.Entry.<String, Long>comparingByValue().reversed())
|
||||
.limit(limit)
|
||||
.map(Map.Entry::getKey)
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
}
|
||||
|
||||
public Map<String, Long> getClientQueryStats(String clientIP) {
|
||||
List<ClientQueryHistory> history = clientQueryHistory.get(clientIP);
|
||||
if (history == null) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
Map<String, Long> stats = new HashMap<>();
|
||||
stats.put("totalQueries", (long) history.size());
|
||||
|
||||
LocalDateTime oneMinuteAgo = LocalDateTime.now().minusMinutes(1);
|
||||
long recentQueries = history.stream()
|
||||
.filter(q -> q.getTimestamp().isAfter(oneMinuteAgo))
|
||||
.count();
|
||||
stats.put("queriesLastMinute", recentQueries);
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
private void logEvent(String clientIP, String domain, String eventType, String details) {
|
||||
DNSEvent event = new DNSEvent(clientIP, domain, eventType, details, LocalDateTime.now());
|
||||
eventLog.offer(event);
|
||||
|
||||
if (eventLog.size() > 5000) {
|
||||
eventLog.poll();
|
||||
}
|
||||
}
|
||||
|
||||
public List<DNSEvent> getRecentEvents(int limit) {
|
||||
List<DNSEvent> events = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (DNSEvent event : eventLog) {
|
||||
if (count++ >= limit) break;
|
||||
events.add(event);
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
public static class DNSQueryResult {
|
||||
private String clientIP;
|
||||
private String domainName;
|
||||
private int queryType;
|
||||
private String resolvedIP;
|
||||
private boolean blocked;
|
||||
private boolean suspicious;
|
||||
private String reason;
|
||||
private String warning;
|
||||
private LocalDateTime timestamp;
|
||||
|
||||
public String getClientIP() { return clientIP; }
|
||||
public void setClientIP(String clientIP) { this.clientIP = clientIP; }
|
||||
public String getDomainName() { return domainName; }
|
||||
public void setDomainName(String domainName) { this.domainName = domainName; }
|
||||
public int getQueryType() { return queryType; }
|
||||
public void setQueryType(int queryType) { this.queryType = queryType; }
|
||||
public String getResolvedIP() { return resolvedIP; }
|
||||
public void setResolvedIP(String resolvedIP) { this.resolvedIP = resolvedIP; }
|
||||
public boolean isBlocked() { return blocked; }
|
||||
public void setBlocked(boolean blocked) { this.blocked = blocked; }
|
||||
public boolean isSuspicious() { return suspicious; }
|
||||
public void setSuspicious(boolean suspicious) { this.suspicious = suspicious; }
|
||||
public String getReason() { return reason; }
|
||||
public void setReason(String reason) { this.reason = reason; }
|
||||
public String getWarning() { return warning; }
|
||||
public void setWarning(String warning) { this.warning = warning; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
public void setTimestamp(LocalDateTime timestamp) { this.timestamp = timestamp; }
|
||||
}
|
||||
|
||||
public static class DNSQueryRecord {
|
||||
private final String clientIP;
|
||||
private final String domainName;
|
||||
private final int queryType;
|
||||
private final LocalDateTime timestamp;
|
||||
|
||||
public DNSQueryRecord(String clientIP, String domainName, int queryType, LocalDateTime timestamp) {
|
||||
this.clientIP = clientIP;
|
||||
this.domainName = domainName;
|
||||
this.queryType = queryType;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getClientIP() { return clientIP; }
|
||||
public String getDomainName() { return domainName; }
|
||||
public int getQueryType() { return queryType; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
}
|
||||
|
||||
public static class DNSZone {
|
||||
private final String name;
|
||||
private final Map<String, String> records = new HashMap<>();
|
||||
|
||||
public DNSZone(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void addRecord(String domain, String ip) {
|
||||
records.put(domain.toLowerCase(), ip);
|
||||
}
|
||||
|
||||
public String resolve(String domain) {
|
||||
return records.get(domain.toLowerCase());
|
||||
}
|
||||
|
||||
public String getName() { return name; }
|
||||
public Map<String, String> getRecords() { return records; }
|
||||
}
|
||||
|
||||
public static class ClientQueryHistory {
|
||||
private final String domain;
|
||||
private final LocalDateTime timestamp;
|
||||
|
||||
public ClientQueryHistory(String domain, LocalDateTime timestamp) {
|
||||
this.domain = domain;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getDomain() { return domain; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
}
|
||||
|
||||
public static class DNSEvent {
|
||||
private final String clientIP;
|
||||
private final String domain;
|
||||
private final String eventType;
|
||||
private final String details;
|
||||
private final LocalDateTime timestamp;
|
||||
|
||||
public DNSEvent(String clientIP, String domain, String eventType, String details, LocalDateTime timestamp) {
|
||||
this.clientIP = clientIP;
|
||||
this.domain = domain;
|
||||
this.eventType = eventType;
|
||||
this.details = details;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getClientIP() { return clientIP; }
|
||||
public String getDomain() { return domain; }
|
||||
public String getEventType() { return eventType; }
|
||||
public String getDetails() { return details; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,479 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Component
|
||||
public class DistributedAttackMitigationService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(DistributedAttackMitigationService.class);
|
||||
|
||||
private final Map<String, AttackSignature> attackSignatures = new ConcurrentHashMap<>();
|
||||
private final Map<String, MitigationRule> mitigationRules = new ConcurrentHashMap<>();
|
||||
private final Map<String, AttackIncident> activeIncidents = new ConcurrentHashMap<>();
|
||||
private final Queue<MitigationEvent> eventLog = new ConcurrentLinkedQueue<>();
|
||||
private final Map<String, List<AttackVector>> attackVectors = new ConcurrentHashMap<>();
|
||||
private final AtomicLong totalAttacksDetected = new AtomicLong(0);
|
||||
private final AtomicLong totalAttacksMitigated = new AtomicLong(0);
|
||||
private final AtomicLong totalAttacksBlocked = new AtomicLong(0);
|
||||
|
||||
private volatile boolean mitigationEnabled = true;
|
||||
private String currentDefenseLevel = "MEDIUM";
|
||||
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
|
||||
|
||||
private static final int MAX_INCIDENTS = 1000;
|
||||
private static final int DEFENSE_LEVEL_HIGH_THRESHOLD = 100;
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
public DistributedAttackMitigationService() {
|
||||
initializeDefaultSignatures();
|
||||
initializeDefaultRules();
|
||||
startAutoMitigation();
|
||||
logger.info("Distributed Attack Mitigation Service initialized");
|
||||
}
|
||||
|
||||
private void initializeDefaultSignatures() {
|
||||
addSignature("SYN_FLOOD", "DDoS", "SYN.*SYN.*SYN", 80, "SYN flood attack");
|
||||
addSignature("UDP_FLOOD", "DDoS", "UDP.*flood", 90, "UDP flood attack");
|
||||
addSignature("ICMP_FLOOD", "DDoS", "ICMP.*flood", 70, "ICMP flood attack");
|
||||
addSignature("HTTP_FLOOD", "DDoS", "GET.*repeated|POST.*repeated", 85, "HTTP flood attack");
|
||||
addSignature("DNS_AMPLIFICATION", "DDoS", "DNS.*amplification", 95, "DNS amplification attack");
|
||||
addSignature("NTP_AMPLIFICATION", "DDoS", "NTP.*amplification", 95, "NTP amplification attack");
|
||||
addSignature("SSDP_AMPLIFICATION", "DDoS", "SSDP.*amplification", 90, "SSDP amplification attack");
|
||||
addSignature("SLOWLORIS", "DDoS", "slow.*loris|partial.*request", 75, "Slowloris attack");
|
||||
addSignature("HTTP_PIPELINING", "DDoS", "pipelining.*abuse", 70, "HTTP pipelining attack");
|
||||
addSignature("BOTNET_ACTIVITY", "DDoS", "botnet.*detected", 90, "Botnet activity detected");
|
||||
addSignature("BRUTE_FORCE_SSH", "BruteForce", "SSH.*failed.*repeated", 85, "SSH brute force attack");
|
||||
addSignature("BRUTE_FORCE_FTP", "BruteForce", "FTP.*failed.*repeated", 85, "FTP brute force attack");
|
||||
addSignature("BRUTE_FORCE_RCON", "BruteForce", "RCON.*failed.*repeated", 90, "RCON brute force attack");
|
||||
addSignature("SQL_INJECTION", "Injection", "union.*select|insert.*into", 95, "SQL injection attack");
|
||||
addSignature("XSS_ATTACK", "Injection", "<script|javascript:", 90, "Cross-site scripting attack");
|
||||
addSignature("CSRF_ATTACK", "Injection", "CSRF.*token", 80, "CSRF attack");
|
||||
addSignature("COMMAND_INJECTION", "Injection", "cmd\\.exe|/bin/bash", 95, "Command injection attack");
|
||||
addSignature("PATH_TRAVERSAL", "Injection", "\\.\\./|\\.\\.\\\\", 85, "Path traversal attack");
|
||||
addSignature("DISTRIBUTED_SCAN", "Reconnaissance", "port.*scan.*distributed", 75, "Distributed port scan");
|
||||
addSignature("CRAWLER_ABUSE", "Reconnaissance", "crawler.*abuse|scraping", 60, "Web crawler abuse");
|
||||
|
||||
logger.info("Initialized {} attack signatures", attackSignatures.size());
|
||||
}
|
||||
|
||||
private void initializeDefaultRules() {
|
||||
addMitigationRule("BLOCK_SYN_FLOOD", "SYN_FLOOD", "BLOCK", 50, 60);
|
||||
addMitigationRule("RATE_LIMIT_UDP", "UDP_FLOOD", "RATE_LIMIT", 100, 30);
|
||||
addMitigationRule("BLOCK_ICMP", "ICMP_FLOOD", "BLOCK", 100, 60);
|
||||
addMitigationRule("RATE_LIMIT_HTTP", "HTTP_FLOOD", "RATE_LIMIT", 50, 10);
|
||||
addMitigationRule("DROP_DNS_AMP", "DNS_AMPLIFICATION", "BLOCK", 100, 300);
|
||||
addMitigationRule("BLOCK_NTP_AMP", "NTP_AMPLIFICATION", "BLOCK", 100, 300);
|
||||
addMitigationRule("THROTTLE_SLOWLORIS", "SLOWLORIS", "THROTTLE", 30, 60);
|
||||
addMitigationRule("BLOCK_BOTNET", "BOTNET_ACTIVITY", "BLOCK", 100, 600);
|
||||
addMitigationRule("BLOCK_SSH_BRUTE", "BRUTE_FORCE_SSH", "BLOCK", 10, 3600);
|
||||
addMitigationRule("BLOCK_FTP_BRUTE", "BRUTE_FORCE_FTP", "BLOCK", 10, 3600);
|
||||
addMitigationRule("BLOCK_RCON_BRUTE", "BRUTE_FORCE_RCON", "BLOCK", 5, 3600);
|
||||
addMitigationRule("SANITIZE_SQL", "SQL_INJECTION", "SANITIZE", 100, 0);
|
||||
addMitigationRule("SANITIZE_XSS", "XSS_ATTACK", "SANITIZE", 100, 0);
|
||||
addMitigationRule("BLOCK_CMD_INJECTION", "COMMAND_INJECTION", "BLOCK", 100, 0);
|
||||
addMitigationRule("BLOCK_TRAVERSAL", "PATH_TRAVERSAL", "BLOCK", 100, 0);
|
||||
|
||||
logger.info("Initialized {} mitigation rules", mitigationRules.size());
|
||||
}
|
||||
|
||||
private void addSignature(String id, String type, String pattern, int severity, String description) {
|
||||
AttackSignature sig = new AttackSignature(id, type, pattern, severity, description);
|
||||
attackSignatures.put(id, sig);
|
||||
}
|
||||
|
||||
private void addMitigationRule(String name, String signatureId, String action, int threshold, int duration) {
|
||||
MitigationRule rule = new MitigationRule(name, signatureId, action, threshold, duration);
|
||||
mitigationRules.put(name, rule);
|
||||
}
|
||||
|
||||
public void analyzeTraffic(String sourceIP, Map<String, Object> trafficData) {
|
||||
if (!mitigationEnabled) return;
|
||||
|
||||
for (AttackSignature signature : attackSignatures.values()) {
|
||||
if (matchesSignature(trafficData, signature)) {
|
||||
handleAttackDetection(sourceIP, signature, trafficData);
|
||||
}
|
||||
}
|
||||
|
||||
updateDefenseLevel();
|
||||
}
|
||||
|
||||
private boolean matchesSignature(Map<String, Object> trafficData, AttackSignature signature) {
|
||||
String pattern = signature.getPattern();
|
||||
|
||||
if (trafficData.containsKey("packetPattern")) {
|
||||
String packetPattern = (String) trafficData.get("packetPattern");
|
||||
if (packetPattern != null && packetPattern.matches(pattern)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (trafficData.containsKey("requestPattern")) {
|
||||
String requestPattern = (String) trafficData.get("requestPattern");
|
||||
if (requestPattern != null && requestPattern.matches(pattern)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (trafficData.containsKey("flags")) {
|
||||
String flags = (String) trafficData.get("flags");
|
||||
if (flags != null && flags.matches(pattern)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void handleAttackDetection(String sourceIP, AttackSignature signature, Map<String, Object> trafficData) {
|
||||
totalAttacksDetected.incrementAndGet();
|
||||
|
||||
String incidentId = UUID.randomUUID().toString();
|
||||
AttackIncident incident = new AttackIncident(
|
||||
incidentId,
|
||||
sourceIP,
|
||||
signature.getId(),
|
||||
signature.getType(),
|
||||
signature.getSeverity(),
|
||||
trafficData,
|
||||
LocalDateTime.now()
|
||||
);
|
||||
|
||||
activeIncidents.put(incidentId, incident);
|
||||
|
||||
List<AttackVector> vectors = attackVectors.computeIfAbsent(sourceIP, k -> new ArrayList<>());
|
||||
vectors.add(new AttackVector(signature.getId(), System.currentTimeMillis()));
|
||||
|
||||
applyMitigation(sourceIP, signature);
|
||||
|
||||
if (activeIncidents.size() > MAX_INCIDENTS) {
|
||||
String firstKey = activeIncidents.keySet().iterator().next();
|
||||
activeIncidents.remove(firstKey);
|
||||
}
|
||||
|
||||
logger.warn("Attack detected from {}: {} [{}]", sourceIP, signature.getDescription(), signature.getType());
|
||||
}
|
||||
|
||||
private void applyMitigation(String sourceIP, AttackSignature signature) {
|
||||
for (MitigationRule rule : mitigationRules.values()) {
|
||||
if (rule.getSignatureId().equals(signature.getId())) {
|
||||
switch (rule.getAction()) {
|
||||
case "BLOCK":
|
||||
applyBlock(sourceIP, rule.getDuration());
|
||||
totalAttacksBlocked.incrementAndGet();
|
||||
logEvent(sourceIP, signature.getId(), "BLOCKED", "Blocked by rule: " + rule.getName());
|
||||
break;
|
||||
|
||||
case "RATE_LIMIT":
|
||||
applyRateLimit(sourceIP, rule.getThreshold());
|
||||
logEvent(sourceIP, signature.getId(), "RATE_LIMITED", "Rate limited by rule: " + rule.getName());
|
||||
break;
|
||||
|
||||
case "SANITIZE":
|
||||
applySanitization(sourceIP);
|
||||
logEvent(sourceIP, signature.getId(), "SANITIZED", "Request sanitized");
|
||||
break;
|
||||
|
||||
case "THROTTLE":
|
||||
applyThrottling(sourceIP, rule.getThreshold());
|
||||
logEvent(sourceIP, signature.getId(), "THROTTLED", "Connection throttled");
|
||||
break;
|
||||
}
|
||||
|
||||
totalAttacksMitigated.incrementAndGet();
|
||||
logger.info("Applied mitigation for {}: {}", sourceIP, rule.getAction());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void applyBlock(String sourceIP, int duration) {
|
||||
logger.info("Blocking IP: {} for {} seconds", sourceIP, duration);
|
||||
}
|
||||
|
||||
private void applyRateLimit(String sourceIP, int threshold) {
|
||||
logger.info("Applying rate limit to IP: {} (threshold: {})", sourceIP, threshold);
|
||||
}
|
||||
|
||||
private void applySanitization(String sourceIP) {
|
||||
logger.info("Applying request sanitization for IP: {}", sourceIP);
|
||||
}
|
||||
|
||||
private void applyThrottling(String sourceIP, int threshold) {
|
||||
logger.info("Throttling connection for IP: {} (threshold: {})", sourceIP, threshold);
|
||||
}
|
||||
|
||||
private void updateDefenseLevel() {
|
||||
int activeAttackCount = activeIncidents.size();
|
||||
|
||||
if (activeAttackCount > DEFENSE_LEVEL_HIGH_THRESHOLD * 2) {
|
||||
if (!"HIGH".equals(currentDefenseLevel)) {
|
||||
currentDefenseLevel = "HIGH";
|
||||
logger.warn("Defense level raised to HIGH - {} active attacks", activeAttackCount);
|
||||
}
|
||||
} else if (activeAttackCount > DEFENSE_LEVEL_HIGH_THRESHOLD) {
|
||||
if (!"MEDIUM".equals(currentDefenseLevel)) {
|
||||
currentDefenseLevel = "MEDIUM";
|
||||
logger.info("Defense level raised to MEDIUM - {} active attacks", activeAttackCount);
|
||||
}
|
||||
} else if (activeAttackCount < DEFENSE_LEVEL_HIGH_THRESHOLD / 2) {
|
||||
if (!"LOW".equals(currentDefenseLevel)) {
|
||||
currentDefenseLevel = "LOW";
|
||||
logger.info("Defense level lowered to LOW - {} active attacks", activeAttackCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void startAutoMitigation() {
|
||||
scheduler.scheduleAtFixedRate(() -> {
|
||||
cleanupOldIncidents();
|
||||
analyzeAttackPatterns();
|
||||
generateDefenseRecommendations();
|
||||
}, 10, 10, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private void cleanupOldIncidents() {
|
||||
long cutoffTime = System.currentTimeMillis() - (3600000);
|
||||
List<String> toRemove = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<String, AttackIncident> entry : activeIncidents.entrySet()) {
|
||||
if (entry.getValue().getDetectedAt().isBefore(LocalDateTime.now().minusHours(1))) {
|
||||
toRemove.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
for (String id : toRemove) {
|
||||
activeIncidents.remove(id);
|
||||
}
|
||||
|
||||
for (Map.Entry<String, List<AttackVector>> entry : attackVectors.entrySet()) {
|
||||
long recentCutoff = System.currentTimeMillis() - (300000);
|
||||
entry.getValue().removeIf(v -> v.getTimestamp() < recentCutoff);
|
||||
}
|
||||
}
|
||||
|
||||
private void analyzeAttackPatterns() {
|
||||
for (Map.Entry<String, List<AttackVector>> entry : attackVectors.entrySet()) {
|
||||
List<AttackVector> vectors = entry.getValue();
|
||||
if (vectors.size() > 10) {
|
||||
Map<String, Long> typeCounts = new HashMap<>();
|
||||
for (AttackVector v : vectors) {
|
||||
typeCounts.merge(v.getSignatureId(), 1L, Long::sum);
|
||||
}
|
||||
|
||||
for (Map.Entry<String, Long> typeEntry : typeCounts.entrySet()) {
|
||||
if (typeEntry.getValue() > 5) {
|
||||
logger.warn("Attack pattern detected from {}: {} attacks of type {} in 5 minutes",
|
||||
entry.getKey(), typeEntry.getValue(), typeEntry.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void generateDefenseRecommendations() {
|
||||
if (activeIncidents.size() > 50) {
|
||||
logger.warn("HIGH ATTACK VOLUME RECOMMENDATION: Consider enabling emergency defense mode");
|
||||
}
|
||||
|
||||
Map<String, Long> typeCounts = new HashMap<>();
|
||||
for (AttackIncident incident : activeIncidents.values()) {
|
||||
typeCounts.merge(incident.getAttackType(), 1L, Long::sum);
|
||||
}
|
||||
|
||||
for (Map.Entry<String, Long> entry : typeCounts.entrySet()) {
|
||||
if (entry.getValue() > 20) {
|
||||
logger.warn("DEFENSE RECOMMENDATION: High volume of {} attacks detected ({}), consider additional mitigation",
|
||||
entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Object> getStats() {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("mitigationEnabled", mitigationEnabled);
|
||||
stats.put("defenseLevel", currentDefenseLevel);
|
||||
stats.put("totalAttacksDetected", totalAttacksDetected.get());
|
||||
stats.put("totalAttacksMitigated", totalAttacksMitigated.get());
|
||||
stats.put("totalAttacksBlocked", totalAttacksBlocked.get());
|
||||
stats.put("activeIncidents", activeIncidents.size());
|
||||
stats.put("attackSignatures", attackSignatures.size());
|
||||
stats.put("mitigationRules", mitigationRules.size());
|
||||
|
||||
double mitigationRate = totalAttacksDetected.get() > 0 ?
|
||||
totalAttacksMitigated.get() * 100.0 / totalAttacksDetected.get() : 0;
|
||||
stats.put("mitigationRate", String.format("%.2f%%", mitigationRate));
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
public List<AttackIncident> getActiveIncidents(int limit) {
|
||||
List<AttackIncident> incidents = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (AttackIncident incident : activeIncidents.values()) {
|
||||
if (count++ >= limit) break;
|
||||
incidents.add(incident);
|
||||
}
|
||||
return incidents;
|
||||
}
|
||||
|
||||
public Map<String, Long> getAttackTypeDistribution() {
|
||||
Map<String, Long> distribution = new HashMap<>();
|
||||
for (AttackIncident incident : activeIncidents.values()) {
|
||||
distribution.merge(incident.getAttackType(), 1L, Long::sum);
|
||||
}
|
||||
return distribution;
|
||||
}
|
||||
|
||||
public void enableMitigation() {
|
||||
mitigationEnabled = true;
|
||||
logger.info("Attack mitigation enabled");
|
||||
}
|
||||
|
||||
public void disableMitigation() {
|
||||
mitigationEnabled = false;
|
||||
logger.info("Attack mitigation disabled");
|
||||
}
|
||||
|
||||
public void setDefenseLevel(String level) {
|
||||
this.currentDefenseLevel = level;
|
||||
logger.info("Defense level set to: {}", level);
|
||||
}
|
||||
|
||||
private void logEvent(String sourceIP, String signatureId, String action, String details) {
|
||||
MitigationEvent event = new MitigationEvent(sourceIP, signatureId, action, details, LocalDateTime.now());
|
||||
eventLog.offer(event);
|
||||
if (eventLog.size() > 5000) {
|
||||
eventLog.poll();
|
||||
}
|
||||
}
|
||||
|
||||
public List<MitigationEvent> getRecentEvents(int limit) {
|
||||
List<MitigationEvent> events = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (MitigationEvent event : eventLog) {
|
||||
if (count++ >= limit) break;
|
||||
events.add(event);
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
public static class AttackSignature {
|
||||
private final String id;
|
||||
private final String type;
|
||||
private final String pattern;
|
||||
private final int severity;
|
||||
private final String description;
|
||||
|
||||
public AttackSignature(String id, String type, String pattern, int severity, String description) {
|
||||
this.id = id;
|
||||
this.type = type;
|
||||
this.pattern = pattern;
|
||||
this.severity = severity;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getId() { return id; }
|
||||
public String getType() { return type; }
|
||||
public String getPattern() { return pattern; }
|
||||
public int getSeverity() { return severity; }
|
||||
public String getDescription() { return description; }
|
||||
}
|
||||
|
||||
public static class MitigationRule {
|
||||
private final String name;
|
||||
private final String signatureId;
|
||||
private final String action;
|
||||
private final int threshold;
|
||||
private final int duration;
|
||||
|
||||
public MitigationRule(String name, String signatureId, String action, int threshold, int duration) {
|
||||
this.name = name;
|
||||
this.signatureId = signatureId;
|
||||
this.action = action;
|
||||
this.threshold = threshold;
|
||||
this.duration = duration;
|
||||
}
|
||||
|
||||
public String getName() { return name; }
|
||||
public String getSignatureId() { return signatureId; }
|
||||
public String getAction() { return action; }
|
||||
public int getThreshold() { return threshold; }
|
||||
public int getDuration() { return duration; }
|
||||
}
|
||||
|
||||
public static class AttackIncident {
|
||||
private final String incidentId;
|
||||
private final String sourceIP;
|
||||
private final String signatureId;
|
||||
private final String attackType;
|
||||
private final int severity;
|
||||
private final Map<String, Object> evidence;
|
||||
private final LocalDateTime detectedAt;
|
||||
|
||||
public AttackIncident(String incidentId, String sourceIP, String signatureId, String attackType,
|
||||
int severity, Map<String, Object> evidence, LocalDateTime detectedAt) {
|
||||
this.incidentId = incidentId;
|
||||
this.sourceIP = sourceIP;
|
||||
this.signatureId = signatureId;
|
||||
this.attackType = attackType;
|
||||
this.severity = severity;
|
||||
this.evidence = evidence;
|
||||
this.detectedAt = detectedAt;
|
||||
}
|
||||
|
||||
public String getIncidentId() { return incidentId; }
|
||||
public String getSourceIP() { return sourceIP; }
|
||||
public String getSignatureId() { return signatureId; }
|
||||
public String getAttackType() { return attackType; }
|
||||
public int getSeverity() { return severity; }
|
||||
public Map<String, Object> getEvidence() { return evidence; }
|
||||
public LocalDateTime getDetectedAt() { return detectedAt; }
|
||||
}
|
||||
|
||||
public static class AttackVector {
|
||||
private final String signatureId;
|
||||
private final long timestamp;
|
||||
|
||||
public AttackVector(String signatureId, long timestamp) {
|
||||
this.signatureId = signatureId;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getSignatureId() { return signatureId; }
|
||||
public long getTimestamp() { return timestamp; }
|
||||
}
|
||||
|
||||
public static class MitigationEvent {
|
||||
private final String sourceIP;
|
||||
private final String signatureId;
|
||||
private final String action;
|
||||
private final String details;
|
||||
private final LocalDateTime timestamp;
|
||||
|
||||
public MitigationEvent(String sourceIP, String signatureId, String action, String details, LocalDateTime timestamp) {
|
||||
this.sourceIP = sourceIP;
|
||||
this.signatureId = signatureId;
|
||||
this.action = action;
|
||||
this.details = details;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getSourceIP() { return sourceIP; }
|
||||
public String getSignatureId() { return signatureId; }
|
||||
public String getAction() { return action; }
|
||||
public String getDetails() { return details; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Base64;
|
||||
|
||||
@Component
|
||||
public class EncryptionService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(EncryptionService.class);
|
||||
|
||||
private final Map<String, String> encryptionKeys = new ConcurrentHashMap<>();
|
||||
private final Map<String, EncryptionStats> stats = new ConcurrentHashMap<>();
|
||||
private final Queue<EncryptionEvent> eventLog = new ConcurrentLinkedQueue<>();
|
||||
private final AtomicLong totalEncryptions = new AtomicLong(0);
|
||||
private final AtomicLong totalDecryptions = new AtomicLong(0);
|
||||
private final AtomicLong totalFailures = new AtomicLong(0);
|
||||
|
||||
private static final String DEFAULT_KEY = "AluerDefaultKey2024Secure!";
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
public EncryptionService() {
|
||||
encryptionKeys.put("default", DEFAULT_KEY);
|
||||
logger.info("Encryption Service initialized");
|
||||
}
|
||||
|
||||
public String encrypt(String plaintext, String keyName) {
|
||||
try {
|
||||
String key = encryptionKeys.getOrDefault(keyName, DEFAULT_KEY);
|
||||
String encrypted = xorEncrypt(plaintext, key);
|
||||
totalEncryptions.incrementAndGet();
|
||||
|
||||
EncryptionStats stat = stats.computeIfAbsent(keyName, k -> new EncryptionStats(keyName));
|
||||
stat.incrementEncryptions();
|
||||
|
||||
logEvent("ENCRYPT", keyName, "SUCCESS", null);
|
||||
return encrypted;
|
||||
} catch (Exception e) {
|
||||
totalFailures.incrementAndGet();
|
||||
logEvent("ENCRYPT", keyName, "FAILED", e.getMessage());
|
||||
logger.error("Encryption failed: {}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public String decrypt(String ciphertext, String keyName) {
|
||||
try {
|
||||
String key = encryptionKeys.getOrDefault(keyName, DEFAULT_KEY);
|
||||
String decrypted = xorDecrypt(ciphertext, key);
|
||||
totalDecryptions.incrementAndGet();
|
||||
|
||||
EncryptionStats stat = stats.computeIfAbsent(keyName, k -> new EncryptionStats(keyName));
|
||||
stat.incrementDecryptions();
|
||||
|
||||
logEvent("DECRYPT", keyName, "SUCCESS", null);
|
||||
return decrypted;
|
||||
} catch (Exception e) {
|
||||
totalFailures.incrementAndGet();
|
||||
logEvent("DECRYPT", keyName, "FAILED", e.getMessage());
|
||||
logger.error("Decryption failed: {}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String xorEncrypt(String plaintext, String key) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (int i = 0; i < plaintext.length(); i++) {
|
||||
result.append((char) (plaintext.charAt(i) ^ key.charAt(i % key.length())));
|
||||
}
|
||||
return Base64.getEncoder().encodeToString(result.toString().getBytes());
|
||||
}
|
||||
|
||||
private String xorDecrypt(String ciphertext, String key) {
|
||||
byte[] decoded = Base64.getDecoder().decode(ciphertext);
|
||||
String decodedStr = new String(decoded);
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (int i = 0; i < decodedStr.length(); i++) {
|
||||
result.append((char) (decodedStr.charAt(i) ^ key.charAt(i % key.length())));
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
public String hashData(String data, String algorithm) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance(algorithm);
|
||||
byte[] hash = md.digest(data.getBytes());
|
||||
StringBuilder hexString = new StringBuilder();
|
||||
for (byte b : hash) {
|
||||
String hex = Integer.toHexString(0xff & b);
|
||||
if (hex.length() == 1) hexString.append('0');
|
||||
hexString.append(hex);
|
||||
}
|
||||
return hexString.toString();
|
||||
} catch (Exception e) {
|
||||
logger.error("Hashing failed: {}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public String generateKey(int length) {
|
||||
String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*";
|
||||
Random random = new Random();
|
||||
StringBuilder key = new StringBuilder();
|
||||
for (int i = 0; i < length; i++) {
|
||||
key.append(chars.charAt(random.nextInt(chars.length())));
|
||||
}
|
||||
return key.toString();
|
||||
}
|
||||
|
||||
public void addKey(String name, String key) {
|
||||
encryptionKeys.put(name, key);
|
||||
logger.info("Added encryption key: {}", name);
|
||||
}
|
||||
|
||||
public boolean removeKey(String name) {
|
||||
if ("default".equals(name)) {
|
||||
logger.warn("Cannot remove default key");
|
||||
return false;
|
||||
}
|
||||
return encryptionKeys.remove(name) != null;
|
||||
}
|
||||
|
||||
public Map<String, Object> getStats() {
|
||||
Map<String, Object> statsMap = new HashMap<>();
|
||||
statsMap.put("totalEncryptions", totalEncryptions.get());
|
||||
statsMap.put("totalDecryptions", totalDecryptions.get());
|
||||
statsMap.put("totalFailures", totalFailures.get());
|
||||
statsMap.put("activeKeys", encryptionKeys.size());
|
||||
return statsMap;
|
||||
}
|
||||
|
||||
public List<EncryptionEvent> getRecentEvents(int limit) {
|
||||
List<EncryptionEvent> events = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (EncryptionEvent event : eventLog) {
|
||||
if (count++ >= limit) break;
|
||||
events.add(event);
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
private void logEvent(String operation, String keyName, String status, String error) {
|
||||
EncryptionEvent event = new EncryptionEvent(operation, keyName, status, error, LocalDateTime.now());
|
||||
eventLog.offer(event);
|
||||
if (eventLog.size() > 1000) {
|
||||
eventLog.poll();
|
||||
}
|
||||
}
|
||||
|
||||
public static class EncryptionStats {
|
||||
private final String keyName;
|
||||
private final AtomicLong encryptions = new AtomicLong(0);
|
||||
private final AtomicLong decryptions = new AtomicLong(0);
|
||||
|
||||
public EncryptionStats(String keyName) {
|
||||
this.keyName = keyName;
|
||||
}
|
||||
|
||||
public void incrementEncryptions() { encryptions.incrementAndGet(); }
|
||||
public void incrementDecryptions() { decryptions.incrementAndGet(); }
|
||||
|
||||
public String getKeyName() { return keyName; }
|
||||
public long getEncryptions() { return encryptions.get(); }
|
||||
public long getDecryptions() { return decryptions.get(); }
|
||||
}
|
||||
|
||||
public static class EncryptionEvent {
|
||||
private final String operation;
|
||||
private final String keyName;
|
||||
private final String status;
|
||||
private final String error;
|
||||
private final LocalDateTime timestamp;
|
||||
|
||||
public EncryptionEvent(String operation, String keyName, String status, String error, LocalDateTime timestamp) {
|
||||
this.operation = operation;
|
||||
this.keyName = keyName;
|
||||
this.status = status;
|
||||
this.error = error;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getOperation() { return operation; }
|
||||
public String getKeyName() { return keyName; }
|
||||
public String getStatus() { return status; }
|
||||
public String getError() { return error; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
@Component
|
||||
public class EndpointDetectionResponseService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(EndpointDetectionResponseService.class);
|
||||
|
||||
private final Map<String, EndpointProfile> endpoints = new ConcurrentHashMap<>();
|
||||
private final Map<String, EDRRule> rules = new ConcurrentHashMap<>();
|
||||
private final Queue<EDRAlert> alertQueue = new ConcurrentLinkedQueue<>();
|
||||
private final Map<String, List<ThreatIndicator>> threatIndicators = new ConcurrentHashMap<>();
|
||||
private final AtomicLong totalEndpointsMonitored = new AtomicLong(0);
|
||||
private final AtomicLong threatsDetected = new AtomicLong(0);
|
||||
private final AtomicLong responsesExecuted = new AtomicLong(0);
|
||||
|
||||
private volatile boolean enabled = false;
|
||||
private static final int MAX_ALERTS = 10000;
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
public EndpointDetectionResponseService() {
|
||||
initializeDefaultRules();
|
||||
logger.info("Endpoint Detection and Response Service initialized");
|
||||
}
|
||||
|
||||
private void initializeDefaultRules() {
|
||||
addRule("SUSPICIOUS_PROCESS", "PROCESS", "cmd.exe|powershell.exe|/bin/sh", 90, "CRITICAL");
|
||||
addRule("UNAUTHORIZED_ACCESS", "ACCESS", "sudo|su |admin", 85, "HIGH");
|
||||
addRule("MALICIOUS_FILE", "FILE", "\\.exe$|\\.dll$|\\.bat$", 80, "HIGH");
|
||||
addRule("NETWORK_ANOMALY", "NETWORK", "unusual.*port|excessive.*connection", 75, "MEDIUM");
|
||||
addRule("REGISTRY_CHANGE", "REGISTRY", "HKLM|HKCU", 70, "MEDIUM");
|
||||
addRule("SERVICE_INSTALL", "SERVICE", "sc\\.exe|systemctl", 80, "HIGH");
|
||||
addRule("SCHEDULE_TASK", "TASK", "at\\.exe|schtasks", 75, "MEDIUM");
|
||||
addRule("WMI_EXECUTION", "WMI", "wmic|winmgmt", 85, "HIGH");
|
||||
addRule("PASSWORD_DUMP", "CREDENTIAL", "mimikatz|procdump|pwdump", 100, "CRITICAL");
|
||||
addRule("LATERAL_MOVEMENT", "MOVEMENT", "psexec|wmic|smb", 90, "HIGH");
|
||||
|
||||
logger.info("Initialized {} EDR rules", rules.size());
|
||||
}
|
||||
|
||||
public void addRule(String name, String category, String pattern, int severity, String priority) {
|
||||
EDRRule rule = new EDRRule(name, category, pattern, severity, priority);
|
||||
rules.put(name, rule);
|
||||
}
|
||||
|
||||
public void registerEndpoint(String endpointId, String hostname, String os, String ip) {
|
||||
EndpointProfile endpoint = new EndpointProfile(endpointId, hostname, os, ip);
|
||||
endpoints.put(endpointId, endpoint);
|
||||
totalEndpointsMonitored.incrementAndGet();
|
||||
logger.info("Registered endpoint: {} ({})", hostname, os);
|
||||
}
|
||||
|
||||
public void analyzeEndpoint(String endpointId, Map<String, Object> telemetry) {
|
||||
EndpointProfile endpoint = endpoints.get(endpointId);
|
||||
if (endpoint == null) return;
|
||||
|
||||
for (EDRRule rule : rules.values()) {
|
||||
if (matchesRule(telemetry, rule)) {
|
||||
handleDetection(endpoint, rule, telemetry);
|
||||
}
|
||||
}
|
||||
|
||||
endpoint.updateLastSeen();
|
||||
}
|
||||
|
||||
private boolean matchesRule(Map<String, Object> telemetry, EDRRule rule) {
|
||||
for (Map.Entry<String, Object> entry : telemetry.entrySet()) {
|
||||
if (entry.getValue() instanceof String) {
|
||||
String value = (String) entry.getValue();
|
||||
if (value.matches(rule.getPattern())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void handleDetection(EndpointProfile endpoint, EDRRule rule, Map<String, Object> telemetry) {
|
||||
threatsDetected.incrementAndGet();
|
||||
|
||||
EDRAlert alert = new EDRAlert(
|
||||
endpoint.getEndpointId(),
|
||||
rule.getName(),
|
||||
rule.getCategory(),
|
||||
rule.getSeverity(),
|
||||
telemetry,
|
||||
LocalDateTime.now()
|
||||
);
|
||||
|
||||
alertQueue.offer(alert);
|
||||
if (alertQueue.size() > MAX_ALERTS) {
|
||||
alertQueue.poll();
|
||||
}
|
||||
|
||||
executeResponse(endpoint, rule);
|
||||
|
||||
logger.warn("EDR Alert: {} detected on endpoint {} ({})",
|
||||
rule.getName(), endpoint.getHostname(), endpoint.getEndpointId());
|
||||
}
|
||||
|
||||
private void executeResponse(EndpointProfile endpoint, EDRRule rule) {
|
||||
responsesExecuted.incrementAndGet();
|
||||
|
||||
switch (rule.getPriority()) {
|
||||
case "CRITICAL":
|
||||
isolateEndpoint(endpoint.getEndpointId());
|
||||
break;
|
||||
case "HIGH":
|
||||
killSuspiciousProcess(endpoint, rule);
|
||||
break;
|
||||
case "MEDIUM":
|
||||
quarantineFile(endpoint, rule);
|
||||
break;
|
||||
default:
|
||||
logSuspiciousActivity(endpoint, rule);
|
||||
}
|
||||
}
|
||||
|
||||
private void isolateEndpoint(String endpointId) {
|
||||
EndpointProfile endpoint = endpoints.get(endpointId);
|
||||
if (endpoint != null) {
|
||||
endpoint.setStatus("ISOLATED");
|
||||
logger.warn("Endpoint {} has been isolated", endpointId);
|
||||
}
|
||||
}
|
||||
|
||||
private void killSuspiciousProcess(EndpointProfile endpoint, EDRRule rule) {
|
||||
logger.info("Killing suspicious process on endpoint: {}", endpoint.getHostname());
|
||||
}
|
||||
|
||||
private void quarantineFile(EndpointProfile endpoint, EDRRule rule) {
|
||||
logger.info("Quarantining suspicious file on endpoint: {}", endpoint.getHostname());
|
||||
}
|
||||
|
||||
private void logSuspiciousActivity(EndpointProfile endpoint, EDRRule rule) {
|
||||
logger.info("Logging suspicious activity on endpoint: {}", endpoint.getHostname());
|
||||
}
|
||||
|
||||
public void addThreatIndicator(String indicator, String type, String severity) {
|
||||
List<ThreatIndicator> indicators = threatIndicators.computeIfAbsent(type, k -> new ArrayList<>());
|
||||
indicators.add(new ThreatIndicator(indicator, type, severity, LocalDateTime.now()));
|
||||
}
|
||||
|
||||
public Map<String, Object> getStats() {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("enabled", enabled);
|
||||
stats.put("totalEndpointsMonitored", totalEndpointsMonitored.get());
|
||||
stats.put("threatsDetected", threatsDetected.get());
|
||||
stats.put("responsesExecuted", responsesExecuted.get());
|
||||
stats.put("activeEndpoints", endpoints.size());
|
||||
stats.put("rulesConfigured", rules.size());
|
||||
stats.put("alertQueueSize", alertQueue.size());
|
||||
|
||||
long critical = alertQueue.stream().filter(a -> a.getSeverity() >= 90).count();
|
||||
stats.put("criticalAlerts", critical);
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
public Map<String, EndpointProfile> getEndpoints() {
|
||||
return new HashMap<>(endpoints);
|
||||
}
|
||||
|
||||
public List<EDRAlert> getRecentAlerts(int limit) {
|
||||
List<EDRAlert> alerts = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (EDRAlert alert : alertQueue) {
|
||||
if (count++ >= limit) break;
|
||||
alerts.add(alert);
|
||||
}
|
||||
return alerts;
|
||||
}
|
||||
|
||||
public void enable() {
|
||||
enabled = true;
|
||||
logger.info("EDR service enabled");
|
||||
}
|
||||
|
||||
public void disable() {
|
||||
enabled = false;
|
||||
logger.info("EDR service disabled");
|
||||
}
|
||||
|
||||
public static class EndpointProfile {
|
||||
private final String endpointId;
|
||||
private final String hostname;
|
||||
private final String os;
|
||||
private final String ip;
|
||||
private volatile String status;
|
||||
private volatile long lastSeen;
|
||||
|
||||
public EndpointProfile(String endpointId, String hostname, String os, String ip) {
|
||||
this.endpointId = endpointId;
|
||||
this.hostname = hostname;
|
||||
this.os = os;
|
||||
this.ip = ip;
|
||||
this.status = "ACTIVE";
|
||||
this.lastSeen = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void updateLastSeen() { this.lastSeen = System.currentTimeMillis(); }
|
||||
|
||||
public String getEndpointId() { return endpointId; }
|
||||
public String getHostname() { return hostname; }
|
||||
public String getOs() { return os; }
|
||||
public String getIp() { return ip; }
|
||||
public String getStatus() { return status; }
|
||||
public void setStatus(String status) { this.status = status; }
|
||||
public long getLastSeen() { return lastSeen; }
|
||||
}
|
||||
|
||||
public static class EDRRule {
|
||||
private final String name;
|
||||
private final String category;
|
||||
private final String pattern;
|
||||
private final int severity;
|
||||
private final String priority;
|
||||
|
||||
public EDRRule(String name, String category, String pattern, int severity, String priority) {
|
||||
this.name = name;
|
||||
this.category = category;
|
||||
this.pattern = pattern;
|
||||
this.severity = severity;
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
public String getName() { return name; }
|
||||
public String getCategory() { return category; }
|
||||
public String getPattern() { return pattern; }
|
||||
public int getSeverity() { return severity; }
|
||||
public String getPriority() { return priority; }
|
||||
}
|
||||
|
||||
public static class EDRAlert {
|
||||
private final String endpointId;
|
||||
private final String ruleName;
|
||||
private final String category;
|
||||
private final int severity;
|
||||
private final Map<String, Object> telemetry;
|
||||
private final LocalDateTime timestamp;
|
||||
|
||||
public EDRAlert(String endpointId, String ruleName, String category, int severity, Map<String, Object> telemetry, LocalDateTime timestamp) {
|
||||
this.endpointId = endpointId;
|
||||
this.ruleName = ruleName;
|
||||
this.category = category;
|
||||
this.severity = severity;
|
||||
this.telemetry = telemetry;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getEndpointId() { return endpointId; }
|
||||
public String getRuleName() { return ruleName; }
|
||||
public String getCategory() { return category; }
|
||||
public int getSeverity() { return severity; }
|
||||
public Map<String, Object> getTelemetry() { return telemetry; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
}
|
||||
|
||||
public static class ThreatIndicator {
|
||||
private final String indicator;
|
||||
private final String type;
|
||||
private final String severity;
|
||||
private final LocalDateTime timestamp;
|
||||
|
||||
public ThreatIndicator(String indicator, String type, String severity, LocalDateTime timestamp) {
|
||||
this.indicator = indicator;
|
||||
this.type = type;
|
||||
this.severity = severity;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getIndicator() { return indicator; }
|
||||
public String getType() { return type; }
|
||||
public String getSeverity() { return severity; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
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; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,431 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.*;
|
||||
import java.security.MessageDigest;
|
||||
|
||||
@Service
|
||||
public class FirewallService {
|
||||
|
||||
private final Map<String, FirewallRule> rules = new ConcurrentHashMap<>();
|
||||
private final Map<String, FirewallProfile> profiles = new ConcurrentHashMap<>();
|
||||
private final Map<String, ConnectionState> connections = new ConcurrentHashMap<>();
|
||||
private final Queue<FirewallLog> logs = new ConcurrentLinkedQueue<>();
|
||||
private final Set<String> whitelist = ConcurrentHashMap.newKeySet();
|
||||
private final Set<String> blacklist = ConcurrentHashMap.newKeySet();
|
||||
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
|
||||
|
||||
private volatile FirewallProfile activeProfile;
|
||||
private final AtomicLong ruleIdCounter = new AtomicLong(1);
|
||||
|
||||
public static final int MAX_CONNECTIONS = 10000;
|
||||
public static final long CONNECTION_TIMEOUT = 120000;
|
||||
public static final int MAX_RULES = 500;
|
||||
|
||||
public FirewallService() {
|
||||
initializeDefaultRules();
|
||||
initializeDefaultProfiles();
|
||||
startCleanupTask();
|
||||
}
|
||||
|
||||
private void initializeDefaultProfiles() {
|
||||
FirewallProfile strict = new FirewallProfile("strict", "严格模式", true);
|
||||
strict.maxConnectionsPerIP = 10;
|
||||
strict.maxRequestsPerSecond = 20;
|
||||
strict.blockPingFlood = true;
|
||||
strict.blockPortScan = true;
|
||||
strict.blockSQLInjection = true;
|
||||
strict.blockXSS = true;
|
||||
profiles.put("strict", strict);
|
||||
|
||||
FirewallProfile normal = new FirewallProfile("normal", "普通模式", false);
|
||||
normal.maxConnectionsPerIP = 50;
|
||||
normal.maxRequestsPerSecond = 100;
|
||||
normal.blockPingFlood = false;
|
||||
normal.blockPortScan = true;
|
||||
normal.blockSQLInjection = true;
|
||||
normal.blockXSS = true;
|
||||
profiles.put("normal", normal);
|
||||
|
||||
FirewallProfile relaxed = new FirewallProfile("relaxed", "宽松模式", false);
|
||||
relaxed.maxConnectionsPerIP = 100;
|
||||
relaxed.maxRequestsPerSecond = 500;
|
||||
relaxed.blockPingFlood = false;
|
||||
relaxed.blockPortScan = false;
|
||||
relaxed.blockSQLInjection = false;
|
||||
relaxed.blockXSS = false;
|
||||
profiles.put("relaxed", relaxed);
|
||||
|
||||
activeProfile = normal;
|
||||
}
|
||||
|
||||
private void initializeDefaultRules() {
|
||||
addRule("DROP", "tcp", "0.0.0.0/0", "0.0.0.0/0", 23, "Telnet", "BLOCK_TELNET");
|
||||
addRule("DROP", "tcp", "0.0.0.0/0", "0.0.0.0/0", 445, "SMB", "BLOCK_SMB");
|
||||
addRule("DROP", "udp", "0.0.0.0/0", "0.0.0.0/0", 137, "NetBIOS", "BLOCK_NETBIOS");
|
||||
addRule("DROP", "udp", "0.0.0.0/0", "0.0.0.0/0", 138, "NetBIOS", "BLOCK_NETBIOS");
|
||||
addRule("DROP", "tcp", "0.0.0.0/0", "0.0.0.0/0", 1433, "MSSQL", "BLOCK_MSSQL");
|
||||
addRule("DROP", "tcp", "0.0.0.0/0", "0.0.0.0/0", 3306, "MySQL", "BLOCK_MYSQL");
|
||||
addRule("DROP", "tcp", "0.0.0.0/0", "0.0.0.0/0", 5432, "PostgreSQL", "BLOCK_POSTGRESQL");
|
||||
addRule("DROP", "tcp", "0.0.0.0/0", "0.0.0.0/0", 27017, "MongoDB", "BLOCK_MONGODB");
|
||||
}
|
||||
|
||||
public long addRule(String action, String protocol, String sourceIP, String destIP, int port, String description, String category) {
|
||||
if (rules.size() >= MAX_RULES) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
long id = ruleIdCounter.getAndIncrement();
|
||||
FirewallRule rule = new FirewallRule(id, action, protocol, sourceIP, destIP, port, description, category);
|
||||
rules.put(String.valueOf(id), rule);
|
||||
|
||||
logRuleChange("ADD", id, rule);
|
||||
return id;
|
||||
}
|
||||
|
||||
public boolean removeRule(long ruleId) {
|
||||
String key = String.valueOf(ruleId);
|
||||
FirewallRule removed = rules.remove(key);
|
||||
if (removed != null) {
|
||||
logRuleChange("REMOVE", ruleId, removed);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public FirewallRule getRule(long ruleId) {
|
||||
return rules.get(String.valueOf(ruleId));
|
||||
}
|
||||
|
||||
public Collection<FirewallRule> getAllRules() {
|
||||
return rules.values();
|
||||
}
|
||||
|
||||
public boolean updateRule(long ruleId, FirewallRule updated) {
|
||||
String key = String.valueOf(ruleId);
|
||||
if (!rules.containsKey(key)) {
|
||||
return false;
|
||||
}
|
||||
rules.put(key, updated);
|
||||
logRuleChange("UPDATE", ruleId, updated);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean checkConnection(String sourceIP, String destIP, int port, String protocol) {
|
||||
if (whitelist.contains(sourceIP)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (blacklist.contains(sourceIP)) {
|
||||
logBlock(sourceIP, destIP, port, "BLACKLIST");
|
||||
return false;
|
||||
}
|
||||
|
||||
FirewallProfile profile = activeProfile;
|
||||
if (profile == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (FirewallRule rule : rules.values()) {
|
||||
if (rule.matches(protocol, sourceIP, destIP, port)) {
|
||||
if ("DROP".equals(rule.action) || "DENY".equals(rule.action)) {
|
||||
logBlock(sourceIP, destIP, port, rule.category);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (profile.maxConnectionsPerIP > 0) {
|
||||
int connCount = getConnectionCount(sourceIP);
|
||||
if (connCount >= profile.maxConnectionsPerIP) {
|
||||
logBlock(sourceIP, destIP, port, "CONNECTION_LIMIT");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean checkRequest(String ip, String request) {
|
||||
FirewallProfile profile = activeProfile;
|
||||
if (profile == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (profile.blockSQLInjection && containsSQLInjection(request)) {
|
||||
logBlock(ip, "REQUEST", 0, "SQL_INJECTION");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (profile.blockXSS && containsXSS(request)) {
|
||||
logBlock(ip, "REQUEST", 0, "XSS");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (profile.blockPathTraversal && containsPathTraversal(request)) {
|
||||
logBlock(ip, "REQUEST", 0, "PATH_TRAVERSAL");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean containsSQLInjection(String input) {
|
||||
if (input == null) return false;
|
||||
String lower = input.toLowerCase();
|
||||
String[] sqlKeywords = {"union", "select", "insert", "update", "delete", "drop",
|
||||
"create", "alter", "exec", "execute", "script", "--", ";"};
|
||||
for (String keyword : sqlKeywords) {
|
||||
if (lower.contains(keyword)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean containsXSS(String input) {
|
||||
if (input == null) return false;
|
||||
String[] xssPatterns = {"<script", "javascript:", "onerror=", "onload=",
|
||||
"<iframe", "eval(", "document.cookie"};
|
||||
for (String pattern : xssPatterns) {
|
||||
if (input.toLowerCase().contains(pattern)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean containsPathTraversal(String input) {
|
||||
if (input == null) return false;
|
||||
return input.contains("../") || input.contains("..\\");
|
||||
}
|
||||
|
||||
public void addToWhitelist(String ip) {
|
||||
whitelist.add(ip);
|
||||
logConfigChange("WHITELIST_ADD", ip);
|
||||
}
|
||||
|
||||
public void removeFromWhitelist(String ip) {
|
||||
whitelist.remove(ip);
|
||||
logConfigChange("WHITELIST_REMOVE", ip);
|
||||
}
|
||||
|
||||
public boolean isWhitelisted(String ip) {
|
||||
return whitelist.contains(ip);
|
||||
}
|
||||
|
||||
public void addToBlacklist(String ip) {
|
||||
blacklist.add(ip);
|
||||
logConfigChange("BLACKLIST_ADD", ip);
|
||||
}
|
||||
|
||||
public void removeFromBlacklist(String ip) {
|
||||
blacklist.remove(ip);
|
||||
logConfigChange("BLACKLIST_REMOVE", ip);
|
||||
}
|
||||
|
||||
public boolean isBlacklisted(String ip) {
|
||||
return blacklist.contains(ip);
|
||||
}
|
||||
|
||||
public boolean setActiveProfile(String profileName) {
|
||||
FirewallProfile profile = profiles.get(profileName);
|
||||
if (profile == null) {
|
||||
return false;
|
||||
}
|
||||
activeProfile = profile;
|
||||
logConfigChange("PROFILE_CHANGE", profileName);
|
||||
return true;
|
||||
}
|
||||
|
||||
public FirewallProfile getActiveProfile() {
|
||||
return activeProfile;
|
||||
}
|
||||
|
||||
public boolean createProfile(String name, String description) {
|
||||
if (profiles.containsKey(name)) {
|
||||
return false;
|
||||
}
|
||||
FirewallProfile profile = new FirewallProfile(name, description, false);
|
||||
profiles.put(name, profile);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean deleteProfile(String name) {
|
||||
FirewallProfile profile = profiles.get(name);
|
||||
if (profile == null) {
|
||||
return false;
|
||||
}
|
||||
if (profile.isDefault) {
|
||||
return false;
|
||||
}
|
||||
return profiles.remove(name) != null;
|
||||
}
|
||||
|
||||
public Collection<FirewallProfile> getAllProfiles() {
|
||||
return profiles.values();
|
||||
}
|
||||
|
||||
public void recordConnection(String ip, String destIP, int port, String protocol) {
|
||||
String key = ip + ":" + destIP + ":" + port;
|
||||
connections.put(key, new ConnectionState(ip, destIP, port, protocol));
|
||||
}
|
||||
|
||||
public void closeConnection(String ip, String destIP, int port) {
|
||||
String key = ip + ":" + destIP + ":" + port;
|
||||
connections.remove(key);
|
||||
}
|
||||
|
||||
public int getConnectionCount(String ip) {
|
||||
return (int) connections.keySet().stream()
|
||||
.filter(k -> k.startsWith(ip + ":"))
|
||||
.count();
|
||||
}
|
||||
|
||||
public Collection<ConnectionState> getActiveConnections() {
|
||||
return connections.values();
|
||||
}
|
||||
|
||||
private void logBlock(String sourceIP, String destIP, int port, String reason) {
|
||||
FirewallLog log = new FirewallLog("BLOCK", sourceIP, destIP, port, reason, System.currentTimeMillis());
|
||||
addLog(log);
|
||||
}
|
||||
|
||||
private void logRuleChange(String action, long ruleId, FirewallRule rule) {
|
||||
FirewallLog log = new FirewallLog("RULE_" + action, rule.sourceIP, rule.destIP, rule.port,
|
||||
ruleId + ":" + rule.description, System.currentTimeMillis());
|
||||
addLog(log);
|
||||
}
|
||||
|
||||
private void logConfigChange(String action, String details) {
|
||||
FirewallLog log = new FirewallLog("CONFIG_" + action, "SYSTEM", details, 0, "", System.currentTimeMillis());
|
||||
addLog(log);
|
||||
}
|
||||
|
||||
private void addLog(FirewallLog log) {
|
||||
logs.offer(log);
|
||||
while (logs.size() > 5000) {
|
||||
logs.remove();
|
||||
}
|
||||
}
|
||||
|
||||
public List<FirewallLog> getLogs(int limit) {
|
||||
List<FirewallLog> result = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (FirewallLog log : logs) {
|
||||
if (count++ >= limit) break;
|
||||
result.add(log);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public Map<String, Object> getStats() {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("totalRules", rules.size());
|
||||
stats.put("whitelistSize", whitelist.size());
|
||||
stats.put("blacklistSize", blacklist.size());
|
||||
stats.put("activeConnections", connections.size());
|
||||
stats.put("activeProfile", activeProfile != null ? activeProfile.name : "none");
|
||||
return stats;
|
||||
}
|
||||
|
||||
private void startCleanupTask() {
|
||||
scheduler.scheduleAtFixedRate(() -> {
|
||||
long now = System.currentTimeMillis();
|
||||
connections.entrySet().removeIf(entry ->
|
||||
now - entry.getValue().createdAt > CONNECTION_TIMEOUT);
|
||||
}, 60, 60, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public static class FirewallRule {
|
||||
public final long id;
|
||||
public final String action;
|
||||
public final String protocol;
|
||||
public final String sourceIP;
|
||||
public final String destIP;
|
||||
public final int port;
|
||||
public final String description;
|
||||
public final String category;
|
||||
public final long createdAt;
|
||||
|
||||
public FirewallRule(long id, String action, String protocol, String sourceIP,
|
||||
String destIP, int port, String description, String category) {
|
||||
this.id = id;
|
||||
this.action = action;
|
||||
this.protocol = protocol;
|
||||
this.sourceIP = sourceIP;
|
||||
this.destIP = destIP;
|
||||
this.port = port;
|
||||
this.description = description;
|
||||
this.category = category;
|
||||
this.createdAt = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public boolean matches(String protocol, String sourceIP, String destIP, int port) {
|
||||
if (!this.protocol.equalsIgnoreCase(protocol) && !this.protocol.equals("*")) {
|
||||
return false;
|
||||
}
|
||||
if (this.port != 0 && this.port != port) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static class FirewallProfile {
|
||||
public final String name;
|
||||
public final String description;
|
||||
public final boolean isDefault;
|
||||
public int maxConnectionsPerIP = 50;
|
||||
public int maxRequestsPerSecond = 100;
|
||||
public boolean blockPingFlood = false;
|
||||
public boolean blockPortScan = false;
|
||||
public boolean blockSQLInjection = false;
|
||||
public boolean blockXSS = false;
|
||||
public boolean blockPathTraversal = false;
|
||||
|
||||
public FirewallProfile(String name, String description, boolean isDefault) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.isDefault = isDefault;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ConnectionState {
|
||||
public final String sourceIP;
|
||||
public final String destIP;
|
||||
public final int port;
|
||||
public final String protocol;
|
||||
public final long createdAt;
|
||||
public volatile long lastActivity;
|
||||
|
||||
public ConnectionState(String sourceIP, String destIP, int port, String protocol) {
|
||||
this.sourceIP = sourceIP;
|
||||
this.destIP = destIP;
|
||||
this.port = port;
|
||||
this.protocol = protocol;
|
||||
this.createdAt = System.currentTimeMillis();
|
||||
this.lastActivity = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
|
||||
public static class FirewallLog {
|
||||
public final String type;
|
||||
public final String sourceIP;
|
||||
public final String destIP;
|
||||
public final int port;
|
||||
public final String reason;
|
||||
public final long timestamp;
|
||||
|
||||
public FirewallLog(String type, String sourceIP, String destIP, int port, String reason, long timestamp) {
|
||||
this.type = type;
|
||||
this.sourceIP = sourceIP;
|
||||
this.destIP = destIP;
|
||||
this.port = port;
|
||||
this.reason = reason;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,411 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
@Component
|
||||
public class FlowAnalyzerService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(FlowAnalyzerService.class);
|
||||
|
||||
private final Map<String, FlowRecord> activeFlows = new ConcurrentHashMap<>();
|
||||
private final Map<String, FlowStatistics> flowStatistics = new ConcurrentHashMap<>();
|
||||
private final Queue<FlowAlert> alertQueue = new ConcurrentLinkedQueue<>();
|
||||
private final Map<String, List<HistoryEntry>> flowHistory = new ConcurrentHashMap<>();
|
||||
private final AtomicLong totalFlowsAnalyzed = new AtomicLong(0);
|
||||
private final AtomicLong totalBytesTransferred = new AtomicLong(0);
|
||||
|
||||
private static final int FLOW_TIMEOUT_SECONDS = 300;
|
||||
private static final int MAX_ACTIVE_FLOWS = 10000;
|
||||
private static final long BYTES_THRESHOLD = 100 * 1024 * 1024;
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
public FlowAnalyzerService() {
|
||||
logger.info("Flow Analyzer Service initialized");
|
||||
}
|
||||
|
||||
public void recordPacket(String sourceIP, String destIP, int sourcePort, int destPort,
|
||||
int packetSize, String protocol) {
|
||||
String flowKey = generateFlowKey(sourceIP, destIP, sourcePort, destPort, protocol);
|
||||
|
||||
FlowRecord flow = activeFlows.computeIfAbsent(flowKey, k -> {
|
||||
FlowRecord newFlow = new FlowRecord(sourceIP, destIP, sourcePort, destPort, protocol);
|
||||
totalFlowsAnalyzed.incrementAndGet();
|
||||
return newFlow;
|
||||
});
|
||||
|
||||
flow.addPacket(packetSize);
|
||||
totalBytesTransferred.addAndGet(packetSize);
|
||||
|
||||
if (flow.getTotalBytes() > BYTES_THRESHOLD) {
|
||||
triggerAlert(flow, "HIGH_VOLUME", "异常高流量传输: " + formatBytes(flow.getTotalBytes()));
|
||||
}
|
||||
|
||||
if (flow.getPacketCount() > 10000) {
|
||||
triggerAlert(flow, "HIGH_PACKET_COUNT", "高数据包数量: " + flow.getPacketCount());
|
||||
}
|
||||
|
||||
long flowAge = System.currentTimeMillis() - flow.getStartTime();
|
||||
if (flowAge > FLOW_TIMEOUT_SECONDS * 1000) {
|
||||
expireFlow(flowKey);
|
||||
}
|
||||
}
|
||||
|
||||
public void recordBidirectionalFlow(String clientIP, String serverIP, int clientPort, int serverPort,
|
||||
int clientToServerBytes, int serverToClientBytes, String protocol) {
|
||||
String forwardKey = generateFlowKey(clientIP, serverIP, clientPort, serverPort, protocol);
|
||||
String reverseKey = generateFlowKey(serverIP, clientIP, serverPort, clientPort, protocol);
|
||||
|
||||
FlowRecord forwardFlow = activeFlows.computeIfAbsent(forwardKey, k -> {
|
||||
FlowRecord f = new FlowRecord(clientIP, serverIP, clientPort, serverPort, protocol);
|
||||
totalFlowsAnalyzed.incrementAndGet();
|
||||
return f;
|
||||
});
|
||||
forwardFlow.addPacket(clientToServerBytes);
|
||||
|
||||
FlowRecord reverseFlow = activeFlows.computeIfAbsent(reverseKey, k -> {
|
||||
FlowRecord f = new FlowRecord(serverIP, clientIP, serverPort, clientPort, protocol);
|
||||
totalFlowsAnalyzed.incrementAndGet();
|
||||
return f;
|
||||
});
|
||||
reverseFlow.addPacket(serverToClientBytes);
|
||||
|
||||
totalBytesTransferred.addAndGet(clientToServerBytes + serverToClientBytes);
|
||||
|
||||
analyzeFlowRatio(forwardFlow, reverseFlow);
|
||||
}
|
||||
|
||||
private void analyzeFlowRatio(FlowRecord forward, FlowRecord reverse) {
|
||||
long forwardBytes = forward.getTotalBytes();
|
||||
long reverseBytes = reverse.getTotalBytes();
|
||||
|
||||
if (forwardBytes > 0 && reverseBytes > 0) {
|
||||
double ratio = (double) forwardBytes / reverseBytes;
|
||||
|
||||
if (ratio > 100 || ratio < 0.01) {
|
||||
triggerAlert(forward, "ASYMMETRIC_FLOW", "不对称流量比率: " + String.format("%.2f", ratio));
|
||||
}
|
||||
}
|
||||
|
||||
long forwardPackets = forward.getPacketCount();
|
||||
long reversePackets = reverse.getPacketCount();
|
||||
if (forwardPackets > 0 && reversePackets > 0) {
|
||||
double packetRatio = (double) forwardPackets / reversePackets;
|
||||
if (packetRatio > 50 || packetRatio < 0.02) {
|
||||
triggerAlert(forward, "PACKET_RATIO_ANOMALY", "数据包比率异常: " + String.format("%.2f", packetRatio));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Object> analyzeFlowPatterns(String ip) {
|
||||
Map<String, Object> analysis = new HashMap<>();
|
||||
|
||||
List<FlowRecord> relatedFlows = new ArrayList<>();
|
||||
for (FlowRecord flow : activeFlows.values()) {
|
||||
if (flow.getSourceIP().equals(ip) || flow.getDestIP().equals(ip)) {
|
||||
relatedFlows.add(flow);
|
||||
}
|
||||
}
|
||||
|
||||
if (relatedFlows.isEmpty()) {
|
||||
analysis.put("status", "no_flows");
|
||||
return analysis;
|
||||
}
|
||||
|
||||
long totalBytes = relatedFlows.stream().mapToLong(FlowRecord::getTotalBytes).sum();
|
||||
long totalPackets = relatedFlows.stream().mapToLong(FlowRecord::getPacketCount).sum();
|
||||
|
||||
analysis.put("totalFlows", relatedFlows.size());
|
||||
analysis.put("totalBytes", totalBytes);
|
||||
analysis.put("totalPackets", totalPackets);
|
||||
|
||||
Map<String, Long> destPorts = new HashMap<>();
|
||||
Map<String, Long> protocols = new HashMap<>();
|
||||
|
||||
for (FlowRecord flow : relatedFlows) {
|
||||
destPorts.merge(String.valueOf(flow.getDestPort()), 1L, Long::sum);
|
||||
protocols.merge(flow.getProtocol(), 1L, Long::sum);
|
||||
}
|
||||
|
||||
analysis.put("destinationPorts", destPorts);
|
||||
analysis.put("protocols", protocols);
|
||||
|
||||
double avgBytesPerFlow = (double) totalBytes / relatedFlows.size();
|
||||
analysis.put("avgBytesPerFlow", avgBytesPerFlow);
|
||||
|
||||
if (avgBytesPerFlow > 10 * 1024 * 1024) {
|
||||
analysis.put("warning", "异常高流量");
|
||||
}
|
||||
|
||||
return analysis;
|
||||
}
|
||||
|
||||
public List<FlowRecord> getTopFlows(int limit, String sortBy) {
|
||||
List<FlowRecord> flows = new ArrayList<>(activeFlows.values());
|
||||
|
||||
if ("bytes".equals(sortBy)) {
|
||||
flows.sort((a, b) -> Long.compare(b.getTotalBytes(), a.getTotalBytes()));
|
||||
} else if ("packets".equals(sortBy)) {
|
||||
flows.sort((a, b) -> Long.compare(b.getPacketCount(), a.getPacketCount()));
|
||||
} else if ("duration".equals(sortBy)) {
|
||||
flows.sort((a, b) -> Long.compare(b.getDuration(), a.getDuration()));
|
||||
} else {
|
||||
flows.sort((a, b) -> Long.compare(b.getTotalBytes(), a.getTotalBytes()));
|
||||
}
|
||||
|
||||
return flows.subList(0, Math.min(limit, flows.size()));
|
||||
}
|
||||
|
||||
public Map<String, FlowStatistics> getProtocolStatistics() {
|
||||
Map<String, FlowStatistics> stats = new HashMap<>();
|
||||
|
||||
for (FlowRecord flow : activeFlows.values()) {
|
||||
FlowStatistics protocolStats = stats.computeIfAbsent(flow.getProtocol(), k -> new FlowStatistics(flow.getProtocol()));
|
||||
protocolStats.addFlow(flow);
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
public Map<String, Object> getTrafficMatrix() {
|
||||
Map<String, Object> matrix = new HashMap<>();
|
||||
|
||||
Map<String, Long> sourceIPTraffic = new HashMap<>();
|
||||
Map<String, Long> destIPTraffic = new HashMap<>();
|
||||
Map<String, Long> protocolTraffic = new HashMap<>();
|
||||
Map<String, Long> portTraffic = new HashMap<>();
|
||||
|
||||
for (FlowRecord flow : activeFlows.values()) {
|
||||
sourceIPTraffic.merge(flow.getSourceIP(), flow.getTotalBytes(), Long::sum);
|
||||
destIPTraffic.merge(flow.getDestIP(), flow.getTotalBytes(), Long::sum);
|
||||
protocolTraffic.merge(flow.getProtocol(), flow.getTotalBytes(), Long::sum);
|
||||
portTraffic.merge(String.valueOf(flow.getDestPort()), flow.getTotalBytes(), Long::sum);
|
||||
}
|
||||
|
||||
matrix.put("bySourceIP", sortByValue(sourceIPTraffic, 10));
|
||||
matrix.put("byDestIP", sortByValue(destIPTraffic, 10));
|
||||
matrix.put("byProtocol", sortByValue(protocolTraffic, 10));
|
||||
matrix.put("byPort", sortByValue(portTraffic, 10));
|
||||
|
||||
return matrix;
|
||||
}
|
||||
|
||||
private Map<String, Long> sortByValue(Map<String, Long> map, int limit) {
|
||||
return map.entrySet().stream()
|
||||
.sorted(Map.Entry.<String, Long>comparingByValue().reversed())
|
||||
.limit(limit)
|
||||
.collect(LinkedHashMap::new,
|
||||
(m, e) -> m.put(e.getKey(), e.getValue()),
|
||||
LinkedHashMap::putAll);
|
||||
}
|
||||
|
||||
public void detectAnomalies() {
|
||||
if (activeFlows.size() > MAX_ACTIVE_FLOWS) {
|
||||
logger.warn("High number of active flows: {}", activeFlows.size());
|
||||
}
|
||||
|
||||
for (FlowRecord flow : activeFlows.values()) {
|
||||
if (flow.getPacketCount() > 0) {
|
||||
double avgPacketSize = (double) flow.getTotalBytes() / flow.getPacketCount();
|
||||
if (avgPacketSize > 10000) {
|
||||
triggerAlert(flow, "LARGE_AVG_PACKET", "平均数据包过大: " + String.format("%.0f", avgPacketSize) + " bytes");
|
||||
}
|
||||
if (avgPacketSize < 10) {
|
||||
triggerAlert(flow, "SMALL_AVG_PACKET", "平均数据包过小: " + String.format("%.0f", avgPacketSize) + " bytes");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void expireFlow(String flowKey) {
|
||||
FlowRecord flow = activeFlows.remove(flowKey);
|
||||
if (flow != null) {
|
||||
List<HistoryEntry> history = flowHistory.computeIfAbsent(flow.getSourceIP(), k -> new ArrayList<>());
|
||||
history.add(new HistoryEntry(flow.getDestIP(), flow.getTotalBytes(), flow.getPacketCount(), LocalDateTime.now()));
|
||||
|
||||
if (history.size() > 1000) {
|
||||
history.subList(0, 500).clear();
|
||||
}
|
||||
|
||||
FlowStatistics stats = flowStatistics.get(flow.getProtocol());
|
||||
if (stats != null) {
|
||||
stats.recordCompletedFlow(flow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Object> getStats() {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("totalFlowsAnalyzed", totalFlowsAnalyzed.get());
|
||||
stats.put("totalBytesTransferred", totalBytesTransferred.get());
|
||||
stats.put("activeFlows", activeFlows.size());
|
||||
stats.put("alertQueueSize", alertQueue.size());
|
||||
|
||||
long totalFlowBytes = activeFlows.values().stream().mapToLong(FlowRecord::getTotalBytes).sum();
|
||||
stats.put("activeFlowBytes", totalFlowBytes);
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
public List<FlowAlert> getRecentAlerts(int limit) {
|
||||
List<FlowAlert> alerts = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (FlowAlert alert : alertQueue) {
|
||||
if (count++ >= limit) break;
|
||||
alerts.add(alert);
|
||||
}
|
||||
return alerts;
|
||||
}
|
||||
|
||||
private void triggerAlert(FlowRecord flow, String alertType, String details) {
|
||||
FlowAlert alert = new FlowAlert(
|
||||
flow.getSourceIP(),
|
||||
flow.getDestIP(),
|
||||
alertType,
|
||||
details,
|
||||
flow.getTotalBytes(),
|
||||
LocalDateTime.now()
|
||||
);
|
||||
alertQueue.offer(alert);
|
||||
|
||||
if (alertQueue.size() > 1000) {
|
||||
alertQueue.poll();
|
||||
}
|
||||
|
||||
logger.warn("Flow Alert: {} - {} -> {}: {}", alertType, flow.getSourceIP(), flow.getDestIP(), details);
|
||||
}
|
||||
|
||||
private String generateFlowKey(String sourceIP, String destIP, int sourcePort, int destPort, String protocol) {
|
||||
return sourceIP + "-" + destIP + "-" + sourcePort + "-" + destPort + "-" + protocol;
|
||||
}
|
||||
|
||||
private String formatBytes(long bytes) {
|
||||
if (bytes < 1024) return bytes + " B";
|
||||
if (bytes < 1024 * 1024) return String.format("%.2f KB", bytes / 1024.0);
|
||||
if (bytes < 1024 * 1024 * 1024) return String.format("%.2f MB", bytes / (1024.0 * 1024));
|
||||
return String.format("%.2f GB", bytes / (1024.0 * 1024 * 1024));
|
||||
}
|
||||
|
||||
public static class FlowRecord {
|
||||
private final String sourceIP;
|
||||
private final String destIP;
|
||||
private final int sourcePort;
|
||||
private final int destPort;
|
||||
private final String protocol;
|
||||
private final long startTime;
|
||||
private volatile long lastPacketTime;
|
||||
private volatile long packetCount;
|
||||
private volatile long totalBytes;
|
||||
|
||||
public FlowRecord(String sourceIP, String destIP, int sourcePort, int destPort, String protocol) {
|
||||
this.sourceIP = sourceIP;
|
||||
this.destIP = destIP;
|
||||
this.sourcePort = sourcePort;
|
||||
this.destPort = destPort;
|
||||
this.protocol = protocol;
|
||||
this.startTime = System.currentTimeMillis();
|
||||
this.lastPacketTime = startTime;
|
||||
this.packetCount = 0;
|
||||
this.totalBytes = 0;
|
||||
}
|
||||
|
||||
public synchronized void addPacket(int size) {
|
||||
packetCount++;
|
||||
totalBytes += size;
|
||||
lastPacketTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public long getDuration() {
|
||||
return lastPacketTime - startTime;
|
||||
}
|
||||
|
||||
public String getSourceIP() { return sourceIP; }
|
||||
public String getDestIP() { return destIP; }
|
||||
public int getSourcePort() { return sourcePort; }
|
||||
public int getDestPort() { return destPort; }
|
||||
public String getProtocol() { return protocol; }
|
||||
public long getStartTime() { return startTime; }
|
||||
public long getLastPacketTime() { return lastPacketTime; }
|
||||
public long getPacketCount() { return packetCount; }
|
||||
public long getTotalBytes() { return totalBytes; }
|
||||
}
|
||||
|
||||
public static class FlowStatistics {
|
||||
private final String protocol;
|
||||
private final AtomicLong totalFlows = new AtomicLong(0);
|
||||
private final AtomicLong completedFlows = new AtomicLong(0);
|
||||
private final AtomicLong totalBytes = new AtomicLong(0);
|
||||
private final AtomicLong totalPackets = new AtomicLong(0);
|
||||
|
||||
public FlowStatistics(String protocol) {
|
||||
this.protocol = protocol;
|
||||
}
|
||||
|
||||
public void addFlow(FlowRecord flow) {
|
||||
totalFlows.incrementAndGet();
|
||||
totalBytes.addAndGet(flow.getTotalBytes());
|
||||
totalPackets.addAndGet(flow.getPacketCount());
|
||||
}
|
||||
|
||||
public void recordCompletedFlow(FlowRecord flow) {
|
||||
completedFlows.incrementAndGet();
|
||||
}
|
||||
|
||||
public String getProtocol() { return protocol; }
|
||||
public long getTotalFlows() { return totalFlows.get(); }
|
||||
public long getCompletedFlows() { return completedFlows.get(); }
|
||||
public long getTotalBytes() { return totalBytes.get(); }
|
||||
public long getTotalPackets() { return totalPackets.get(); }
|
||||
}
|
||||
|
||||
public static class FlowAlert {
|
||||
private final String sourceIP;
|
||||
private final String destIP;
|
||||
private final String alertType;
|
||||
private final String details;
|
||||
private final long bytes;
|
||||
private final LocalDateTime timestamp;
|
||||
|
||||
public FlowAlert(String sourceIP, String destIP, String alertType, String details, long bytes, LocalDateTime timestamp) {
|
||||
this.sourceIP = sourceIP;
|
||||
this.destIP = destIP;
|
||||
this.alertType = alertType;
|
||||
this.details = details;
|
||||
this.bytes = bytes;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getSourceIP() { return sourceIP; }
|
||||
public String getDestIP() { return destIP; }
|
||||
public String getAlertType() { return alertType; }
|
||||
public String getDetails() { return details; }
|
||||
public long getBytes() { return bytes; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
}
|
||||
|
||||
public static class HistoryEntry {
|
||||
private final String destIP;
|
||||
private final long bytes;
|
||||
private final long packets;
|
||||
private final LocalDateTime timestamp;
|
||||
|
||||
public HistoryEntry(String destIP, long bytes, long packets, LocalDateTime timestamp) {
|
||||
this.destIP = destIP;
|
||||
this.bytes = bytes;
|
||||
this.packets = packets;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getDestIP() { return destIP; }
|
||||
public long getBytes() { return bytes; }
|
||||
public long getPackets() { return packets; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
@Service
|
||||
public class GeoIPService {
|
||||
|
||||
private final Map<String, GeoLocation> geoCache = new ConcurrentHashMap<>();
|
||||
private final Map<String, Set<String>> countryIPs = new ConcurrentHashMap<>();
|
||||
private final Set<String> allowedCountries = ConcurrentHashMap.newKeySet();
|
||||
private final Set<String> blockedCountries = ConcurrentHashMap.newKeySet();
|
||||
private final Queue<GeoEvent> events = new ConcurrentLinkedQueue<>();
|
||||
|
||||
private static final long CACHE_DURATION = 86400000;
|
||||
|
||||
public GeoIPService() {
|
||||
initializeDefaultCountries();
|
||||
}
|
||||
|
||||
private void initializeDefaultCountries() {
|
||||
allowedCountries.add("CN");
|
||||
allowedCountries.add("US");
|
||||
allowedCountries.add("JP");
|
||||
allowedCountries.add("KR");
|
||||
allowedCountries.add("SG");
|
||||
}
|
||||
|
||||
public boolean checkCountry(String ip, String countryCode) {
|
||||
if (blockedCountries.contains(countryCode)) {
|
||||
logEvent(ip, countryCode, "BLOCKED");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!allowedCountries.isEmpty() && !allowedCountries.contains(countryCode)) {
|
||||
logEvent(ip, countryCode, "NOT_ALLOWED");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void setGeoLocation(String ip, String country, String city, double lat, double lon) {
|
||||
GeoLocation geo = new GeoLocation(ip, country, city, lat, lon, System.currentTimeMillis());
|
||||
geoCache.put(ip, geo);
|
||||
|
||||
countryIPs.computeIfAbsent(country, k -> ConcurrentHashMap.newKeySet()).add(ip);
|
||||
}
|
||||
|
||||
public GeoLocation getGeoLocation(String ip) {
|
||||
return geoCache.get(ip);
|
||||
}
|
||||
|
||||
public void addAllowedCountry(String country) {
|
||||
allowedCountries.add(country);
|
||||
}
|
||||
|
||||
public void removeAllowedCountry(String country) {
|
||||
allowedCountries.remove(country);
|
||||
}
|
||||
|
||||
public void addBlockedCountry(String country) {
|
||||
blockedCountries.add(country);
|
||||
}
|
||||
|
||||
public void removeBlockedCountry(String country) {
|
||||
blockedCountries.remove(country);
|
||||
}
|
||||
|
||||
public Set<String> getAllowedCountries() {
|
||||
return new HashSet<>(allowedCountries);
|
||||
}
|
||||
|
||||
public Set<String> getBlockedCountries() {
|
||||
return new HashSet<>(blockedCountries);
|
||||
}
|
||||
|
||||
private void logEvent(String ip, String country, String status) {
|
||||
events.offer(new GeoEvent(ip, country, status, System.currentTimeMillis()));
|
||||
}
|
||||
|
||||
public List<GeoEvent> getRecentEvents(int limit) {
|
||||
List<GeoEvent> result = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (GeoEvent event : events) {
|
||||
if (count++ >= limit) break;
|
||||
result.add(event);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public Map<String, Object> getStats() {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("cachedLocations", geoCache.size());
|
||||
stats.put("allowedCountries", allowedCountries.size());
|
||||
stats.put("blockedCountries", blockedCountries.size());
|
||||
return stats;
|
||||
}
|
||||
|
||||
public static class GeoLocation {
|
||||
public final String ip;
|
||||
public final String country;
|
||||
public final String city;
|
||||
public final double latitude;
|
||||
public final double longitude;
|
||||
public final long timestamp;
|
||||
|
||||
public GeoLocation(String ip, String country, String city, double lat, double lon, long timestamp) {
|
||||
this.ip = ip;
|
||||
this.country = country;
|
||||
this.city = city;
|
||||
this.latitude = lat;
|
||||
this.longitude = lon;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
public static class GeoEvent {
|
||||
public final String ip;
|
||||
public final String country;
|
||||
public final String status;
|
||||
public final long timestamp;
|
||||
|
||||
public GeoEvent(String ip, String country, String status, long timestamp) {
|
||||
this.ip = ip;
|
||||
this.country = country;
|
||||
this.status = status;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
@Component
|
||||
public class HoneypotService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(HoneypotService.class);
|
||||
|
||||
private final Map<String, HoneypotInstance> honeypots = new ConcurrentHashMap<>();
|
||||
private final Queue<HoneyPotEvent> eventLog = new ConcurrentLinkedQueue<>();
|
||||
private final Map<String, List<AttackAttempt>> attackHistory = new ConcurrentHashMap<>();
|
||||
private final Map<String, AttackerProfile> attackerProfiles = new ConcurrentHashMap<>();
|
||||
private final AtomicLong totalInteractions = new AtomicLong(0);
|
||||
private final AtomicLong uniqueAttackers = new AtomicLong(0);
|
||||
private final AtomicLong attacksRecorded = new AtomicLong(0);
|
||||
|
||||
private volatile boolean enabled = false;
|
||||
private static final int MAX_HISTORY = 10000;
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
public HoneypotService() {
|
||||
initializeDefaultHoneypots();
|
||||
logger.info("Honeypot Service initialized");
|
||||
}
|
||||
|
||||
private void initializeDefaultHoneypots() {
|
||||
addHoneypot("ssh-honeypot", "SSH", 2222, "OpenSSH 7.4");
|
||||
addHoneypot("ftp-honeypot", "FTP", 2121, "ProFTPD 1.3.5");
|
||||
addHoneypot("http-honeypot", "HTTP", 8080, "Apache 2.4.29");
|
||||
addHoneypot("mysql-honeypot", "MySQL", 3307, "MySQL 5.7");
|
||||
addHoneypot("minecraft-honeypot", "MINECRAFT", 25566, "PaperSpigot 1.19");
|
||||
logger.info("Initialized {} honeypot instances", honeypots.size());
|
||||
}
|
||||
|
||||
public void addHoneypot(String name, String type, int port, String fakeVersion) {
|
||||
HoneypotInstance honeypot = new HoneypotInstance(name, type, port, fakeVersion);
|
||||
honeypots.put(name, honeypot);
|
||||
logger.info("Added honeypot: {} on port {}", name, port);
|
||||
}
|
||||
|
||||
public void recordInteraction(String honeypotName, String sourceIP, String username, String password, String command) {
|
||||
if (!enabled) return;
|
||||
|
||||
totalInteractions.incrementAndGet();
|
||||
|
||||
HoneypotInstance honeypot = honeypots.get(honeypotName);
|
||||
if (honeypot != null) {
|
||||
honeypot.incrementInteractionCount();
|
||||
}
|
||||
|
||||
AttackerProfile profile = attackerProfiles.computeIfAbsent(sourceIP, k -> {
|
||||
uniqueAttackers.incrementAndGet();
|
||||
return new AttackerProfile(sourceIP);
|
||||
});
|
||||
profile.recordAttempt(honeypotName);
|
||||
|
||||
AttackAttempt attempt = new AttackAttempt(honeypotName, sourceIP, username, password, command, LocalDateTime.now());
|
||||
List<AttackAttempt> history = attackHistory.computeIfAbsent(sourceIP, k -> new ArrayList<>());
|
||||
history.add(attempt);
|
||||
attacksRecorded.incrementAndGet();
|
||||
|
||||
if (history.size() > MAX_HISTORY) {
|
||||
history.remove(0);
|
||||
}
|
||||
|
||||
logEvent(sourceIP, honeypotName, "INTERACTION", "Recorded interaction: " + command);
|
||||
|
||||
logger.info("Recorded honeypot interaction from {} on {}", sourceIP, honeypotName);
|
||||
}
|
||||
|
||||
public List<AttackAttempt> getAttackHistory(String ip, int limit) {
|
||||
List<AttackAttempt> history = attackHistory.getOrDefault(ip, new ArrayList<>());
|
||||
return history.subList(0, Math.min(limit, history.size()));
|
||||
}
|
||||
|
||||
public Map<String, AttackerProfile> getTopAttackers(int limit) {
|
||||
List<AttackerProfile> sorted = new ArrayList<>(attackerProfiles.values());
|
||||
sorted.sort((a, b) -> Integer.compare(b.getTotalAttempts(), a.getTotalAttempts()));
|
||||
|
||||
Map<String, AttackerProfile> top = new LinkedHashMap<>();
|
||||
for (int i = 0; i < Math.min(limit, sorted.size()); i++) {
|
||||
top.put(sorted.get(i).getIp(), sorted.get(i));
|
||||
}
|
||||
return top;
|
||||
}
|
||||
|
||||
public Map<String, Object> getStats() {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("enabled", enabled);
|
||||
stats.put("totalInteractions", totalInteractions.get());
|
||||
stats.put("uniqueAttackers", uniqueAttackers.get());
|
||||
stats.put("attacksRecorded", attacksRecorded.get());
|
||||
stats.put("honeypotsConfigured", honeypots.size());
|
||||
|
||||
Map<String, Long> honeypotStats = new HashMap<>();
|
||||
for (HoneypotInstance hp : honeypots.values()) {
|
||||
honeypotStats.put(hp.getName(), hp.getInteractionCount());
|
||||
}
|
||||
stats.put("honeypotInteractions", honeypotStats);
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
public void enable() {
|
||||
enabled = true;
|
||||
logger.info("Honeypot service enabled");
|
||||
}
|
||||
|
||||
public void disable() {
|
||||
enabled = false;
|
||||
logger.info("Honeypot service disabled");
|
||||
}
|
||||
|
||||
public Map<String, HoneypotInstance> getHoneypots() {
|
||||
return new HashMap<>(honeypots);
|
||||
}
|
||||
|
||||
public List<HoneyPotEvent> getRecentEvents(int limit) {
|
||||
List<HoneyPotEvent> events = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (HoneyPotEvent event : eventLog) {
|
||||
if (count++ >= limit) break;
|
||||
events.add(event);
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
private void logEvent(String ip, String honeypot, String action, String details) {
|
||||
HoneyPotEvent event = new HoneyPotEvent(ip, honeypot, action, details, LocalDateTime.now());
|
||||
eventLog.offer(event);
|
||||
if (eventLog.size() > 5000) {
|
||||
eventLog.poll();
|
||||
}
|
||||
}
|
||||
|
||||
public static class HoneypotInstance {
|
||||
private final String name;
|
||||
private final String type;
|
||||
private final int port;
|
||||
private final String fakeVersion;
|
||||
private final AtomicLong interactionCount = new AtomicLong(0);
|
||||
|
||||
public HoneypotInstance(String name, String type, int port, String fakeVersion) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.port = port;
|
||||
this.fakeVersion = fakeVersion;
|
||||
}
|
||||
|
||||
public void incrementInteractionCount() { interactionCount.incrementAndGet(); }
|
||||
|
||||
public String getName() { return name; }
|
||||
public String getType() { return type; }
|
||||
public int getPort() { return port; }
|
||||
public String getFakeVersion() { return fakeVersion; }
|
||||
public long getInteractionCount() { return interactionCount.get(); }
|
||||
}
|
||||
|
||||
public static class AttackAttempt {
|
||||
private final String honeypotName;
|
||||
private final String sourceIP;
|
||||
private final String username;
|
||||
private final String password;
|
||||
private final String command;
|
||||
private final LocalDateTime timestamp;
|
||||
|
||||
public AttackAttempt(String honeypotName, String sourceIP, String username, String password, String command, LocalDateTime timestamp) {
|
||||
this.honeypotName = honeypotName;
|
||||
this.sourceIP = sourceIP;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.command = command;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getHoneypotName() { return honeypotName; }
|
||||
public String getSourceIP() { return sourceIP; }
|
||||
public String getUsername() { return username; }
|
||||
public String getPassword() { return password; }
|
||||
public String getCommand() { return command; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
}
|
||||
|
||||
public static class AttackerProfile {
|
||||
private final String ip;
|
||||
private final Map<String, Integer> honeypotAttempts = new HashMap<>();
|
||||
private volatile int totalAttempts = 0;
|
||||
private volatile long firstSeen;
|
||||
private volatile long lastSeen;
|
||||
|
||||
public AttackerProfile(String ip) {
|
||||
this.ip = ip;
|
||||
this.firstSeen = System.currentTimeMillis();
|
||||
this.lastSeen = firstSeen;
|
||||
}
|
||||
|
||||
public void recordAttempt(String honeypotName) {
|
||||
honeypotAttempts.merge(honeypotName, 1, Integer::sum);
|
||||
totalAttempts++;
|
||||
lastSeen = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public String getIp() { return ip; }
|
||||
public Map<String, Integer> getHoneypotAttempts() { return honeypotAttempts; }
|
||||
public int getTotalAttempts() { return totalAttempts; }
|
||||
public long getFirstSeen() { return firstSeen; }
|
||||
public long getLastSeen() { return lastSeen; }
|
||||
}
|
||||
|
||||
public static class HoneyPotEvent {
|
||||
private final String ip;
|
||||
private final String honeypot;
|
||||
private final String action;
|
||||
private final String details;
|
||||
private final LocalDateTime timestamp;
|
||||
|
||||
public HoneyPotEvent(String ip, String honeypot, String action, String details, LocalDateTime timestamp) {
|
||||
this.ip = ip;
|
||||
this.honeypot = honeypot;
|
||||
this.action = action;
|
||||
this.details = details;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getIp() { return ip; }
|
||||
public String getHoneypot() { return honeypot; }
|
||||
public String getAction() { return action; }
|
||||
public String getDetails() { return details; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,479 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import com.aluer.config.ServerGuardConfig;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.IOException;
|
||||
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.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Service
|
||||
public class HostEnforcementService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(HostEnforcementService.class);
|
||||
|
||||
private static final Pattern IP_PATTERN = Pattern.compile("^[0-9a-fA-F:.]+$");
|
||||
|
||||
private final ServerGuardConfig config;
|
||||
private final Map<String, ManagedRule> desiredRules = new ConcurrentHashMap<>();
|
||||
private final ConcurrentLinkedQueue<EnforcementActionResult> actionLog = new ConcurrentLinkedQueue<>();
|
||||
|
||||
public HostEnforcementService() {
|
||||
this(new ServerGuardConfig());
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public HostEnforcementService(ServerGuardConfig config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public EnforcementActionResult blockIp(String ip, String reason, int durationMinutes) {
|
||||
return applyRule("BLOCK", ip, Math.max(1, durationMinutes), 0, reason);
|
||||
}
|
||||
|
||||
public EnforcementActionResult releaseIp(String ip, String reason) {
|
||||
String normalizedIp = normalizeIp(ip);
|
||||
if (normalizedIp == null) {
|
||||
return record(new EnforcementActionResult(false, "RELEASE", "none", true,
|
||||
"invalid-ip", List.of(), reason, ip, 0, 0));
|
||||
}
|
||||
|
||||
HostFirewallAdapter adapter = resolveAdapter();
|
||||
ManagedRule existing = findRule(normalizedIp, null);
|
||||
List<String> preview = adapter.previewReleaseCommands(normalizedIp, existing != null ? existing.mode : "BLOCK");
|
||||
boolean success = true;
|
||||
String status = "released";
|
||||
|
||||
if (!isDryRunEnabled()) {
|
||||
success = executeCommands(adapter.buildReleaseCommands(normalizedIp, existing != null ? existing.mode : "BLOCK"));
|
||||
status = success ? "released" : "release-failed";
|
||||
}
|
||||
|
||||
desiredRules.entrySet().removeIf(entry -> normalizedIp.equals(entry.getValue().ip));
|
||||
return record(new EnforcementActionResult(success, "RELEASE", adapter.getName(), isDryRunEnabled(),
|
||||
status, preview, reason, normalizedIp, 0, 0));
|
||||
}
|
||||
|
||||
public EnforcementActionResult rateLimitIp(String ip, int limitPerMinute, String reason) {
|
||||
return applyRule("RATE_LIMIT", ip, config.getSecurity().getHostEnforcement().getDefaultBlockMinutes(),
|
||||
Math.max(1, limitPerMinute), reason);
|
||||
}
|
||||
|
||||
public List<ManagedRule> listActiveRules() {
|
||||
List<ManagedRule> rules = new ArrayList<>(desiredRules.values());
|
||||
rules.sort(Comparator.comparingLong((ManagedRule rule) -> rule.createdAt).reversed());
|
||||
return rules;
|
||||
}
|
||||
|
||||
public Map<String, Object> syncDesiredState() {
|
||||
long now = System.currentTimeMillis();
|
||||
int expired = 0;
|
||||
|
||||
for (ManagedRule rule : new ArrayList<>(desiredRules.values())) {
|
||||
if (rule.expiresAt > 0 && rule.expiresAt < now) {
|
||||
releaseIp(rule.ip, "TTL expired");
|
||||
expired++;
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("expiredRulesReleased", expired);
|
||||
result.put("remainingRules", desiredRules.size());
|
||||
result.put("backend", resolveAdapter().getName());
|
||||
result.put("dryRun", isDryRunEnabled());
|
||||
return result;
|
||||
}
|
||||
|
||||
public Map<String, Object> dryRunPreview(String action, String ip, String reason) {
|
||||
HostFirewallAdapter adapter = resolveAdapter();
|
||||
String normalizedAction = action == null ? "BLOCK" : action.toUpperCase(Locale.ROOT);
|
||||
String normalizedIp = normalizeIp(ip);
|
||||
|
||||
List<String> preview;
|
||||
if (normalizedIp == null) {
|
||||
preview = List.of();
|
||||
} else if ("RELEASE".equals(normalizedAction)) {
|
||||
preview = adapter.previewReleaseCommands(normalizedIp, "BLOCK");
|
||||
} else if ("RATE_LIMIT".equals(normalizedAction)) {
|
||||
preview = adapter.previewRateLimitCommands(normalizedIp, config.getSecurity().getHostEnforcement().getDefaultRateLimitPerMinute());
|
||||
} else {
|
||||
preview = adapter.previewBlockCommands(normalizedIp);
|
||||
}
|
||||
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("action", normalizedAction);
|
||||
result.put("ip", normalizedIp);
|
||||
result.put("backend", adapter.getName());
|
||||
result.put("reason", reason);
|
||||
result.put("dryRun", true);
|
||||
result.put("commands", preview);
|
||||
return result;
|
||||
}
|
||||
|
||||
public Map<String, Object> getStats() {
|
||||
long blockRules = desiredRules.values().stream().filter(rule -> "BLOCK".equals(rule.mode)).count();
|
||||
long rateRules = desiredRules.values().stream().filter(rule -> "RATE_LIMIT".equals(rule.mode)).count();
|
||||
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("enabled", isEnabled());
|
||||
stats.put("dryRun", isDryRunEnabled());
|
||||
stats.put("backend", resolveAdapter().getName());
|
||||
stats.put("managedRules", desiredRules.size());
|
||||
stats.put("blockRules", blockRules);
|
||||
stats.put("rateLimitRules", rateRules);
|
||||
stats.put("loggedActions", actionLog.size());
|
||||
return stats;
|
||||
}
|
||||
|
||||
public List<EnforcementActionResult> getRecentActions(int limit) {
|
||||
List<EnforcementActionResult> actions = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (EnforcementActionResult action : actionLog) {
|
||||
if (count++ >= limit) {
|
||||
break;
|
||||
}
|
||||
actions.add(action);
|
||||
}
|
||||
return actions;
|
||||
}
|
||||
|
||||
private EnforcementActionResult applyRule(String mode, String ip, int durationMinutes, int limitPerMinute, String reason) {
|
||||
String normalizedIp = normalizeIp(ip);
|
||||
if (normalizedIp == null) {
|
||||
return record(new EnforcementActionResult(false, mode, "none", true,
|
||||
"invalid-ip", List.of(), reason, ip, durationMinutes, limitPerMinute));
|
||||
}
|
||||
|
||||
HostFirewallAdapter adapter = resolveAdapter();
|
||||
List<String> preview = "RATE_LIMIT".equals(mode)
|
||||
? adapter.previewRateLimitCommands(normalizedIp, limitPerMinute)
|
||||
: adapter.previewBlockCommands(normalizedIp);
|
||||
|
||||
boolean success = true;
|
||||
String status = isDryRunEnabled() ? "dry-run" : "applied";
|
||||
if (!isDryRunEnabled()) {
|
||||
List<List<String>> commands = "RATE_LIMIT".equals(mode)
|
||||
? adapter.buildRateLimitCommands(normalizedIp, limitPerMinute)
|
||||
: adapter.buildBlockCommands(normalizedIp);
|
||||
success = executeCommands(commands);
|
||||
status = success ? "applied" : "execution-failed";
|
||||
}
|
||||
|
||||
long expiresAt = durationMinutes > 0
|
||||
? System.currentTimeMillis() + durationMinutes * 60_000L
|
||||
: 0;
|
||||
ManagedRule rule = new ManagedRule(normalizedIp, mode, reason, durationMinutes, limitPerMinute, expiresAt, adapter.getName());
|
||||
desiredRules.put(rule.getKey(), rule);
|
||||
|
||||
return record(new EnforcementActionResult(success, mode, adapter.getName(), isDryRunEnabled(),
|
||||
status, preview, reason, normalizedIp, durationMinutes, limitPerMinute));
|
||||
}
|
||||
|
||||
private ManagedRule findRule(String ip, String mode) {
|
||||
return desiredRules.values().stream()
|
||||
.filter(rule -> Objects.equals(rule.ip, ip))
|
||||
.filter(rule -> mode == null || Objects.equals(rule.mode, mode))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
private EnforcementActionResult record(EnforcementActionResult result) {
|
||||
actionLog.offer(result);
|
||||
while (actionLog.size() > 1000) {
|
||||
actionLog.poll();
|
||||
}
|
||||
logger.info("Host enforcement {} {} via {} [{}]", result.action, result.ip, result.backend, result.status);
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean executeCommands(List<List<String>> commands) {
|
||||
boolean success = true;
|
||||
for (List<String> command : commands) {
|
||||
ProcessBuilder builder = new ProcessBuilder(command);
|
||||
builder.redirectErrorStream(true);
|
||||
try {
|
||||
Process process = builder.start();
|
||||
int exitCode = process.waitFor();
|
||||
if (exitCode != 0) {
|
||||
success = false;
|
||||
logger.warn("Host enforcement command failed: {}", String.join(" ", command));
|
||||
}
|
||||
} catch (IOException | InterruptedException e) {
|
||||
success = false;
|
||||
logger.warn("Host enforcement execution error: {}", e.getMessage());
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
private HostFirewallAdapter resolveAdapter() {
|
||||
String preferred = config.getSecurity().getHostEnforcement().getPreferredBackend();
|
||||
if ("ufw".equalsIgnoreCase(preferred)) {
|
||||
return commandAvailable("ufw") ? new UfwAdapter() : new NoopAdapter();
|
||||
}
|
||||
if ("iptables".equalsIgnoreCase(preferred)) {
|
||||
return commandAvailable("iptables") ? new IptablesAdapter() : new NoopAdapter();
|
||||
}
|
||||
if (commandAvailable("ufw")) {
|
||||
return new UfwAdapter();
|
||||
}
|
||||
if (commandAvailable("iptables")) {
|
||||
return new IptablesAdapter();
|
||||
}
|
||||
return new NoopAdapter();
|
||||
}
|
||||
|
||||
private boolean commandAvailable(String binary) {
|
||||
try {
|
||||
Process process = new ProcessBuilder("sh", "-lc", "command -v " + binary).start();
|
||||
return process.waitFor() == 0;
|
||||
} catch (IOException | InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isEnabled() {
|
||||
return config.getSecurity().getHostEnforcement().isEnabled();
|
||||
}
|
||||
|
||||
private boolean isDryRunEnabled() {
|
||||
return !isEnabled() || config.getSecurity().getHostEnforcement().isDryRun();
|
||||
}
|
||||
|
||||
private String normalizeIp(String ip) {
|
||||
if (ip == null) {
|
||||
return null;
|
||||
}
|
||||
String normalized = ip.trim();
|
||||
if (normalized.isEmpty() || !IP_PATTERN.matcher(normalized).matches()) {
|
||||
return null;
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
private interface HostFirewallAdapter {
|
||||
String getName();
|
||||
List<List<String>> buildBlockCommands(String ip);
|
||||
List<List<String>> buildReleaseCommands(String ip, String mode);
|
||||
List<List<String>> buildRateLimitCommands(String ip, int limitPerMinute);
|
||||
List<String> previewBlockCommands(String ip);
|
||||
List<String> previewReleaseCommands(String ip, String mode);
|
||||
List<String> previewRateLimitCommands(String ip, int limitPerMinute);
|
||||
}
|
||||
|
||||
private static final class UfwAdapter implements HostFirewallAdapter {
|
||||
@Override
|
||||
public String getName() {
|
||||
return "ufw";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<List<String>> buildBlockCommands(String ip) {
|
||||
return List.of(List.of("sudo", "ufw", "deny", "from", ip));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<List<String>> buildReleaseCommands(String ip, String mode) {
|
||||
return List.of(List.of("sudo", "ufw", "delete", "deny", "from", ip));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<List<String>> buildRateLimitCommands(String ip, int limitPerMinute) {
|
||||
return List.of(List.of("sudo", "ufw", "limit", "from", ip));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> previewBlockCommands(String ip) {
|
||||
return List.of("sudo ufw deny from " + ip);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> previewReleaseCommands(String ip, String mode) {
|
||||
return List.of("sudo ufw delete deny from " + ip);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> previewRateLimitCommands(String ip, int limitPerMinute) {
|
||||
return List.of("sudo ufw limit from " + ip + " # ~" + limitPerMinute + "/min");
|
||||
}
|
||||
}
|
||||
|
||||
private static final class IptablesAdapter implements HostFirewallAdapter {
|
||||
@Override
|
||||
public String getName() {
|
||||
return "iptables";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<List<String>> buildBlockCommands(String ip) {
|
||||
return List.of(List.of("sudo", "iptables", "-I", "INPUT", "-s", ip, "-j", "DROP"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<List<String>> buildReleaseCommands(String ip, String mode) {
|
||||
if ("RATE_LIMIT".equalsIgnoreCase(mode)) {
|
||||
return List.of(List.of("sudo", "iptables", "-D", "INPUT", "-s", ip, "-m", "limit", "--limit", "1/second", "-j", "ACCEPT"));
|
||||
}
|
||||
return List.of(List.of("sudo", "iptables", "-D", "INPUT", "-s", ip, "-j", "DROP"));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<List<String>> buildRateLimitCommands(String ip, int limitPerMinute) {
|
||||
String rate = Math.max(1, limitPerMinute) + "/minute";
|
||||
return List.of(
|
||||
List.of("sudo", "iptables", "-I", "INPUT", "-s", ip, "-m", "limit", "--limit", rate, "-j", "ACCEPT"),
|
||||
List.of("sudo", "iptables", "-A", "INPUT", "-s", ip, "-j", "DROP")
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> previewBlockCommands(String ip) {
|
||||
return List.of("sudo iptables -I INPUT -s " + ip + " -j DROP");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> previewReleaseCommands(String ip, String mode) {
|
||||
if ("RATE_LIMIT".equalsIgnoreCase(mode)) {
|
||||
return List.of("sudo iptables -D INPUT -s " + ip + " -m limit --limit 1/second -j ACCEPT");
|
||||
}
|
||||
return List.of("sudo iptables -D INPUT -s " + ip + " -j DROP");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> previewRateLimitCommands(String ip, int limitPerMinute) {
|
||||
String rate = Math.max(1, limitPerMinute) + "/minute";
|
||||
return List.of(
|
||||
"sudo iptables -I INPUT -s " + ip + " -m limit --limit " + rate + " -j ACCEPT",
|
||||
"sudo iptables -A INPUT -s " + ip + " -j DROP"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class NoopAdapter implements HostFirewallAdapter {
|
||||
@Override
|
||||
public String getName() {
|
||||
return "noop";
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<List<String>> buildBlockCommands(String ip) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<List<String>> buildReleaseCommands(String ip, String mode) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<List<String>> buildRateLimitCommands(String ip, int limitPerMinute) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> previewBlockCommands(String ip) {
|
||||
return List.of("noop block " + ip);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> previewReleaseCommands(String ip, String mode) {
|
||||
return List.of("noop release " + ip);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> previewRateLimitCommands(String ip, int limitPerMinute) {
|
||||
return List.of("noop rate-limit " + ip + " " + limitPerMinute + "/minute");
|
||||
}
|
||||
}
|
||||
|
||||
public static class ManagedRule {
|
||||
private final String ip;
|
||||
private final String mode;
|
||||
private final String reason;
|
||||
private final int durationMinutes;
|
||||
private final int limitPerMinute;
|
||||
private final long createdAt;
|
||||
private final long expiresAt;
|
||||
private final String backend;
|
||||
|
||||
public ManagedRule(String ip, String mode, String reason, int durationMinutes, int limitPerMinute, long expiresAt, String backend) {
|
||||
this.ip = ip;
|
||||
this.mode = mode;
|
||||
this.reason = reason;
|
||||
this.durationMinutes = durationMinutes;
|
||||
this.limitPerMinute = limitPerMinute;
|
||||
this.createdAt = System.currentTimeMillis();
|
||||
this.expiresAt = expiresAt;
|
||||
this.backend = backend;
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return ip + ":" + mode;
|
||||
}
|
||||
|
||||
public String getIp() { return ip; }
|
||||
public String getMode() { return mode; }
|
||||
public String getReason() { return reason; }
|
||||
public int getDurationMinutes() { return durationMinutes; }
|
||||
public int getLimitPerMinute() { return limitPerMinute; }
|
||||
public long getCreatedAt() { return createdAt; }
|
||||
public long getExpiresAt() { return expiresAt; }
|
||||
public String getBackend() { return backend; }
|
||||
}
|
||||
|
||||
public static class EnforcementActionResult {
|
||||
private final boolean success;
|
||||
private final String action;
|
||||
private final String backend;
|
||||
private final boolean dryRun;
|
||||
private final String status;
|
||||
private final List<String> commands;
|
||||
private final String reason;
|
||||
private final String ip;
|
||||
private final int durationMinutes;
|
||||
private final int limitPerMinute;
|
||||
private final long timestamp;
|
||||
|
||||
public EnforcementActionResult(boolean success, String action, String backend, boolean dryRun, String status,
|
||||
List<String> commands, String reason, String ip, int durationMinutes, int limitPerMinute) {
|
||||
this.success = success;
|
||||
this.action = action;
|
||||
this.backend = backend;
|
||||
this.dryRun = dryRun;
|
||||
this.status = status;
|
||||
this.commands = commands;
|
||||
this.reason = reason;
|
||||
this.ip = ip;
|
||||
this.durationMinutes = durationMinutes;
|
||||
this.limitPerMinute = limitPerMinute;
|
||||
this.timestamp = Instant.now().toEpochMilli();
|
||||
}
|
||||
|
||||
public boolean isSuccess() { return success; }
|
||||
public String getAction() { return action; }
|
||||
public String getBackend() { return backend; }
|
||||
public boolean isDryRun() { return dryRun; }
|
||||
public String getStatus() { return status; }
|
||||
public List<String> getCommands() { return commands; }
|
||||
public String getReason() { return reason; }
|
||||
public String getIp() { return ip; }
|
||||
public int getDurationMinutes() { return durationMinutes; }
|
||||
public int getLimitPerMinute() { return limitPerMinute; }
|
||||
public long getTimestamp() { return timestamp; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
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.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
@Service
|
||||
public class HostIntrusionCountermeasureService {
|
||||
private final ServerGuardConfig config;
|
||||
private final EndpointDetectionResponseService endpointDetectionResponseService;
|
||||
private final AdvancedMalwareDetectionService advancedMalwareDetectionService;
|
||||
private final IntrusionDetectionService intrusionDetectionService;
|
||||
private final FileIntegrityMonitorService fileIntegrityMonitorService;
|
||||
private final CommandExecutionGuardService commandExecutionGuardService;
|
||||
private final ConcurrentLinkedQueue<HostIntrusionIncident> incidents = new ConcurrentLinkedQueue<>();
|
||||
|
||||
public HostIntrusionCountermeasureService() {
|
||||
this(new ServerGuardConfig(), new EndpointDetectionResponseService(), new AdvancedMalwareDetectionService(),
|
||||
new IntrusionDetectionService(), new FileIntegrityMonitorService(), new CommandExecutionGuardService());
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public HostIntrusionCountermeasureService(ServerGuardConfig config,
|
||||
EndpointDetectionResponseService endpointDetectionResponseService,
|
||||
AdvancedMalwareDetectionService advancedMalwareDetectionService,
|
||||
IntrusionDetectionService intrusionDetectionService,
|
||||
FileIntegrityMonitorService fileIntegrityMonitorService,
|
||||
CommandExecutionGuardService commandExecutionGuardService) {
|
||||
this.config = config;
|
||||
this.endpointDetectionResponseService = endpointDetectionResponseService;
|
||||
this.advancedMalwareDetectionService = advancedMalwareDetectionService;
|
||||
this.intrusionDetectionService = intrusionDetectionService;
|
||||
this.fileIntegrityMonitorService = fileIntegrityMonitorService;
|
||||
this.commandExecutionGuardService = commandExecutionGuardService;
|
||||
}
|
||||
|
||||
public HostIntrusionIncident analyzeCommand(String actor, String command, String source) {
|
||||
CommandExecutionGuardService.AntiIntrusionIncident incident = commandExecutionGuardService.analyzeCommand(actor, command, source);
|
||||
if (incident == null) {
|
||||
return null;
|
||||
}
|
||||
HostIntrusionIncident hostIncident = new HostIntrusionIncident(
|
||||
incident.getType(),
|
||||
"COMMAND",
|
||||
incident.getSeverity(),
|
||||
actor,
|
||||
source,
|
||||
incident.getEvidence(),
|
||||
incident.getTimestamp()
|
||||
);
|
||||
record(hostIncident);
|
||||
return hostIncident;
|
||||
}
|
||||
|
||||
public HostIntrusionIncident analyzeProcess(String processName, String source) {
|
||||
if (!config.getSecurity().getAntiIntrusion().isMonitorProcesses()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String normalized = processName == null ? "" : processName.toLowerCase(Locale.ROOT);
|
||||
intrusionDetectionService.checkProcessActivity("system", processName, true);
|
||||
|
||||
if (!(normalized.contains("curl")
|
||||
|| normalized.contains("wget")
|
||||
|| normalized.contains("nc ")
|
||||
|| normalized.contains("socat")
|
||||
|| normalized.contains("xmrig")
|
||||
|| normalized.contains("python -c")
|
||||
|| normalized.contains("bash -c"))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
HostIntrusionIncident incident = new HostIntrusionIncident(
|
||||
"SUSPICIOUS_PROCESS",
|
||||
"PROCESS",
|
||||
88,
|
||||
"system",
|
||||
source,
|
||||
processName,
|
||||
Instant.now().toEpochMilli()
|
||||
);
|
||||
record(incident);
|
||||
return incident;
|
||||
}
|
||||
|
||||
public HostIntrusionIncident analyzeFile(String path) {
|
||||
if (!config.getSecurity().getAntiIntrusion().isMonitorFiles()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Path target = Paths.get(path);
|
||||
if (!Files.exists(target) || !Files.isRegularFile(target)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
byte[] content = Files.readAllBytes(target);
|
||||
String hash = sha256(content);
|
||||
AdvancedMalwareDetectionService.ScanResult scanResult = advancedMalwareDetectionService.scanFile(target.getFileName().toString(), content, hash);
|
||||
if (scanResult.isThreatDetected()) {
|
||||
HostIntrusionIncident incident = new HostIntrusionIncident(
|
||||
"MALWARE_OR_BACKDOOR",
|
||||
"FILE",
|
||||
severityToScore(scanResult.getThreatSeverity()),
|
||||
"system",
|
||||
path,
|
||||
scanResult.getThreatName() + ": " + scanResult.getThreatDescription(),
|
||||
Instant.now().toEpochMilli()
|
||||
);
|
||||
intrusionDetectionService.checkFileAccess("system", path, "scan");
|
||||
record(incident);
|
||||
return incident;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
return new HostIntrusionIncident("FILE_SCAN_ERROR", "FILE", 50, "system", path, e.getMessage(), Instant.now().toEpochMilli());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Map<String, Object> runFullScan() {
|
||||
Map<String, Object> integrity = fileIntegrityMonitorService.scanNow();
|
||||
List<HostIntrusionIncident> detected = new ArrayList<>();
|
||||
|
||||
if (config.getSecurity().getAntiIntrusion().isMonitorPlugins()) {
|
||||
for (String file : discoverPluginFiles()) {
|
||||
HostIntrusionIncident incident = analyzeFile(file);
|
||||
if (incident != null) {
|
||||
detected.add(incident);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (FileIntegrityMonitorService.IntegrityAlert alert : fileIntegrityMonitorService.getRecentAlerts(20)) {
|
||||
record(new HostIntrusionIncident(
|
||||
"FILE_INTEGRITY_" + alert.getType(),
|
||||
"INTEGRITY",
|
||||
82,
|
||||
"integrity-monitor",
|
||||
alert.getPath(),
|
||||
alert.getDetails(),
|
||||
alert.getTimestamp()
|
||||
));
|
||||
}
|
||||
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("integrity", integrity);
|
||||
result.put("detectedIncidents", detected.size());
|
||||
result.put("recentIncidents", getRecentIncidents(10));
|
||||
return result;
|
||||
}
|
||||
|
||||
public Map<String, Object> getPosture() {
|
||||
Map<String, Object> posture = new LinkedHashMap<>();
|
||||
posture.put("enabled", config.getSecurity().getAntiIntrusion().isEnabled());
|
||||
posture.put("recentIncidents", incidents.size());
|
||||
posture.put("criticalIncidents", incidents.stream().filter(incident -> incident.severity >= 90).count());
|
||||
posture.put("fileIntegrity", fileIntegrityMonitorService.getBaselineStatus());
|
||||
posture.put("commandGuard", commandExecutionGuardService.getStats());
|
||||
posture.put("edr", endpointDetectionResponseService.getStats());
|
||||
return posture;
|
||||
}
|
||||
|
||||
public List<HostIntrusionIncident> getRecentIncidents(int limit) {
|
||||
List<HostIntrusionIncident> result = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (HostIntrusionIncident incident : incidents) {
|
||||
if (count++ >= limit) {
|
||||
break;
|
||||
}
|
||||
result.add(incident);
|
||||
}
|
||||
result.sort(Comparator.comparingLong(HostIntrusionIncident::getTimestamp).reversed());
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<FileIntegrityMonitorService.IntegrityAlert> getIntegrityAlerts(int limit) {
|
||||
return fileIntegrityMonitorService.getRecentAlerts(limit);
|
||||
}
|
||||
|
||||
private List<String> discoverPluginFiles() {
|
||||
List<String> files = new ArrayList<>();
|
||||
for (String configuredPath : config.getSecurity().getAntiIntrusion().getFileIntegrity().getMonitoredPaths()) {
|
||||
Path path = Paths.get(configuredPath);
|
||||
try {
|
||||
if (Files.isDirectory(path) && configuredPath.toLowerCase(Locale.ROOT).contains("plugins")) {
|
||||
try (var stream = Files.walk(path, 2)) {
|
||||
stream.filter(Files::isRegularFile).forEach(file -> files.add(file.toString()));
|
||||
}
|
||||
} else if (Files.isRegularFile(path) && path.getFileName().toString().endsWith(".jar")) {
|
||||
files.add(path.toString());
|
||||
}
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
private void record(HostIntrusionIncident incident) {
|
||||
incidents.offer(incident);
|
||||
while (incidents.size() > 1000) {
|
||||
incidents.poll();
|
||||
}
|
||||
}
|
||||
|
||||
private int severityToScore(String severity) {
|
||||
if (severity == null) {
|
||||
return 50;
|
||||
}
|
||||
return switch (severity.toUpperCase(Locale.ROOT)) {
|
||||
case "CRITICAL" -> 95;
|
||||
case "HIGH" -> 85;
|
||||
case "MEDIUM" -> 70;
|
||||
default -> 55;
|
||||
};
|
||||
}
|
||||
|
||||
private String sha256(byte[] content) {
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
byte[] hash = digest.digest(content);
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (byte b : hash) {
|
||||
builder.append(String.format("%02x", b));
|
||||
}
|
||||
return builder.toString();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
return Integer.toHexString(content.length);
|
||||
}
|
||||
}
|
||||
|
||||
public static class HostIntrusionIncident {
|
||||
private final String type;
|
||||
private final String category;
|
||||
private final int severity;
|
||||
private final String actor;
|
||||
private final String source;
|
||||
private final String details;
|
||||
private final long timestamp;
|
||||
|
||||
public HostIntrusionIncident(String type, String category, int severity, String actor, String source, String details, long timestamp) {
|
||||
this.type = type;
|
||||
this.category = category;
|
||||
this.severity = severity;
|
||||
this.actor = actor;
|
||||
this.source = source;
|
||||
this.details = details;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getType() { return type; }
|
||||
public String getCategory() { return category; }
|
||||
public int getSeverity() { return severity; }
|
||||
public String getActor() { return actor; }
|
||||
public String getSource() { return source; }
|
||||
public String getDetails() { return details; }
|
||||
public long getTimestamp() { return timestamp; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,317 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.*;
|
||||
|
||||
@Service
|
||||
public class IPReputationService {
|
||||
|
||||
private final Map<String, IPProfile> ipProfiles = new ConcurrentHashMap<>();
|
||||
private final Map<String, List<ReputationEvent>> reputationHistory = new ConcurrentHashMap<>();
|
||||
private final Set<String> blacklistedIPs = ConcurrentHashMap.newKeySet();
|
||||
private final Set<String> whitelistedIPs = ConcurrentHashMap.newKeySet();
|
||||
private final Queue<ReputationAlert> alerts = new ConcurrentLinkedQueue<>();
|
||||
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
|
||||
|
||||
private static final double DEFAULT_REPUTATION_SCORE = 50.0;
|
||||
private static final double MAX_SCORE = 100.0;
|
||||
private static final double MIN_SCORE = 0.0;
|
||||
private static final long SCORE_DECAY_TIME = 86400000;
|
||||
|
||||
private final AtomicLong totalEvaluations = new AtomicLong(0);
|
||||
|
||||
public IPReputationService() {
|
||||
startReputationTask();
|
||||
}
|
||||
|
||||
public double evaluateIP(String ip) {
|
||||
totalEvaluations.incrementAndGet();
|
||||
|
||||
if (whitelistedIPs.contains(ip)) {
|
||||
return MAX_SCORE;
|
||||
}
|
||||
|
||||
if (blacklistedIPs.contains(ip)) {
|
||||
return MIN_SCORE;
|
||||
}
|
||||
|
||||
IPProfile profile = ipProfiles.computeIfAbsent(ip, k -> new IPProfile(ip));
|
||||
double score = profile.calculateScore();
|
||||
|
||||
if (score < 20) {
|
||||
addAlert(ip, "LOW_REPUTATION", "IP reputation score below threshold: " + score);
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
public void recordSuccessfulConnection(String ip) {
|
||||
IPProfile profile = ipProfiles.computeIfAbsent(ip, k -> new IPProfile(ip));
|
||||
profile.recordSuccess();
|
||||
|
||||
ReputationEvent event = new ReputationEvent(ip, "SUCCESS", "Connection successful", System.currentTimeMillis());
|
||||
addToHistory(ip, event);
|
||||
}
|
||||
|
||||
public void recordFailedConnection(String ip, String reason) {
|
||||
IPProfile profile = ipProfiles.computeIfAbsent(ip, k -> new IPProfile(ip));
|
||||
profile.recordFailure();
|
||||
|
||||
ReputationEvent event = new ReputationEvent(ip, "FAILURE", reason, System.currentTimeMillis());
|
||||
addToHistory(ip, event);
|
||||
|
||||
if (profile.failures.get() > 10) {
|
||||
addAlert(ip, "REPEAT_FAILURES", "Multiple failed connections: " + profile.failures.get());
|
||||
}
|
||||
}
|
||||
|
||||
public void recordSuspiciousActivity(String ip, String activityType) {
|
||||
IPProfile profile = ipProfiles.computeIfAbsent(ip, k -> new IPProfile(ip));
|
||||
profile.recordSuspicious(activityType);
|
||||
|
||||
ReputationEvent event = new ReputationEvent(ip, "SUSPICIOUS", activityType, System.currentTimeMillis());
|
||||
addToHistory(ip, event);
|
||||
|
||||
if (profile.suspiciousCount.get() > 5) {
|
||||
addAlert(ip, "SUSPICIOUS_ACTIVITY", "Multiple suspicious activities: " + activityType);
|
||||
}
|
||||
}
|
||||
|
||||
public void recordAttack(String ip, String attackType) {
|
||||
IPProfile profile = ipProfiles.computeIfAbsent(ip, k -> new IPProfile(ip));
|
||||
profile.recordAttack(attackType);
|
||||
|
||||
ReputationEvent event = new ReputationEvent(ip, "ATTACK", attackType, System.currentTimeMillis());
|
||||
addToHistory(ip, event);
|
||||
|
||||
addAlert(ip, "ATTACK_DETECTED", "Attack detected: " + attackType);
|
||||
}
|
||||
|
||||
public void addToBlacklist(String ip) {
|
||||
blacklistedIPs.add(ip);
|
||||
whitelistedIPs.remove(ip);
|
||||
|
||||
IPProfile profile = ipProfiles.get(ip);
|
||||
if (profile != null) {
|
||||
profile.blacklisted = true;
|
||||
}
|
||||
|
||||
ReputationEvent event = new ReputationEvent(ip, "BLACKLIST", "Added to blacklist", System.currentTimeMillis());
|
||||
addToHistory(ip, event);
|
||||
}
|
||||
|
||||
public void removeFromBlacklist(String ip) {
|
||||
blacklistedIPs.remove(ip);
|
||||
|
||||
IPProfile profile = ipProfiles.get(ip);
|
||||
if (profile != null) {
|
||||
profile.blacklisted = false;
|
||||
}
|
||||
|
||||
ReputationEvent event = new ReputationEvent(ip, "UNBLACKLIST", "Removed from blacklist", System.currentTimeMillis());
|
||||
addToHistory(ip, event);
|
||||
}
|
||||
|
||||
public void addToWhitelist(String ip) {
|
||||
whitelistedIPs.add(ip);
|
||||
blacklistedIPs.remove(ip);
|
||||
|
||||
IPProfile profile = ipProfiles.computeIfAbsent(ip, k -> new IPProfile(ip));
|
||||
profile.whitelisted = true;
|
||||
profile.score = MAX_SCORE;
|
||||
|
||||
ReputationEvent event = new ReputationEvent(ip, "WHITELIST", "Added to whitelist", System.currentTimeMillis());
|
||||
addToHistory(ip, event);
|
||||
}
|
||||
|
||||
public void removeFromWhitelist(String ip) {
|
||||
whitelistedIPs.remove(ip);
|
||||
|
||||
IPProfile profile = ipProfiles.get(ip);
|
||||
if (profile != null) {
|
||||
profile.whitelisted = false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isBlacklisted(String ip) {
|
||||
return blacklistedIPs.contains(ip);
|
||||
}
|
||||
|
||||
public boolean isWhitelisted(String ip) {
|
||||
return whitelistedIPs.contains(ip);
|
||||
}
|
||||
|
||||
public IPProfile getProfile(String ip) {
|
||||
return ipProfiles.get(ip);
|
||||
}
|
||||
|
||||
public Collection<IPProfile> getAllProfiles() {
|
||||
return ipProfiles.values();
|
||||
}
|
||||
|
||||
public List<ReputationEvent> getHistory(String ip, int limit) {
|
||||
List<ReputationEvent> history = reputationHistory.get(ip);
|
||||
if (history == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
return history.stream().limit(limit).toList();
|
||||
}
|
||||
|
||||
private void addToHistory(String ip, ReputationEvent event) {
|
||||
List<ReputationEvent> history = reputationHistory.computeIfAbsent(ip, k -> new CopyOnWriteArrayList<>());
|
||||
history.add(event);
|
||||
|
||||
while (history.size() > 100) {
|
||||
history.remove(0);
|
||||
}
|
||||
}
|
||||
|
||||
private void addAlert(String ip, String type, String details) {
|
||||
ReputationAlert alert = new ReputationAlert(ip, type, details, System.currentTimeMillis());
|
||||
alerts.offer(alert);
|
||||
|
||||
while (alerts.size() > 500) {
|
||||
alerts.poll();
|
||||
}
|
||||
}
|
||||
|
||||
public List<ReputationAlert> getAlerts(int limit) {
|
||||
List<ReputationAlert> result = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (ReputationAlert alert : alerts) {
|
||||
if (count++ >= limit) break;
|
||||
result.add(alert);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void startReputationTask() {
|
||||
scheduler.scheduleAtFixedRate(() -> {
|
||||
for (IPProfile profile : ipProfiles.values()) {
|
||||
if (profile.whitelisted || profile.blacklisted) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (System.currentTimeMillis() - profile.lastUpdate > SCORE_DECAY_TIME) {
|
||||
profile.applyDecay();
|
||||
}
|
||||
}
|
||||
|
||||
reputationHistory.entrySet().removeIf(entry -> {
|
||||
List<ReputationEvent> history = entry.getValue();
|
||||
history.removeIf(e -> System.currentTimeMillis() - e.timestamp > SCORE_DECAY_TIME);
|
||||
return history.isEmpty();
|
||||
});
|
||||
|
||||
}, 3600, 3600, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public Map<String, Object> getStats() {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("totalProfiles", ipProfiles.size());
|
||||
stats.put("blacklisted", blacklistedIPs.size());
|
||||
stats.put("whitelisted", whitelistedIPs.size());
|
||||
stats.put("totalEvaluations", totalEvaluations.get());
|
||||
return stats;
|
||||
}
|
||||
|
||||
public static class IPProfile {
|
||||
public final String ip;
|
||||
public final AtomicLong successes = new AtomicLong(0);
|
||||
public final AtomicLong failures = new AtomicLong(0);
|
||||
public final AtomicInteger suspiciousCount = new AtomicInteger(0);
|
||||
public final Map<String, AtomicInteger> attackCounts = new ConcurrentHashMap<>();
|
||||
public volatile double score = DEFAULT_REPUTATION_SCORE;
|
||||
public volatile boolean whitelisted = false;
|
||||
public volatile boolean blacklisted = false;
|
||||
public volatile long lastUpdate;
|
||||
|
||||
public IPProfile(String ip) {
|
||||
this.ip = ip;
|
||||
this.lastUpdate = System.currentTimeMillis();
|
||||
this.score = DEFAULT_REPUTATION_SCORE;
|
||||
}
|
||||
|
||||
public void recordSuccess() {
|
||||
successes.incrementAndGet();
|
||||
score = Math.min(MAX_SCORE, score + 2);
|
||||
lastUpdate = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void recordFailure() {
|
||||
failures.incrementAndGet();
|
||||
score = Math.max(MIN_SCORE, score - 5);
|
||||
lastUpdate = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void recordSuspicious(String activityType) {
|
||||
suspiciousCount.incrementAndGet();
|
||||
score = Math.max(MIN_SCORE, score - 10);
|
||||
lastUpdate = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void recordAttack(String attackType) {
|
||||
attackCounts.computeIfAbsent(attackType, k -> new AtomicInteger(0)).incrementAndGet();
|
||||
score = Math.max(MIN_SCORE, score - 20);
|
||||
lastUpdate = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void applyDecay() {
|
||||
score = Math.max(MIN_SCORE, score - 1);
|
||||
lastUpdate = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public double calculateScore() {
|
||||
if (whitelisted) return MAX_SCORE;
|
||||
if (blacklisted) return MIN_SCORE;
|
||||
|
||||
double baseScore = 50.0;
|
||||
|
||||
long totalAttempts = successes.get() + failures.get();
|
||||
if (totalAttempts > 0) {
|
||||
double successRate = (double) successes.get() / totalAttempts;
|
||||
baseScore += successRate * 30;
|
||||
}
|
||||
|
||||
baseScore -= failures.get() * 2;
|
||||
baseScore -= suspiciousCount.get() * 5;
|
||||
|
||||
for (AtomicInteger count : attackCounts.values()) {
|
||||
baseScore -= count.get() * 10;
|
||||
}
|
||||
|
||||
return Math.max(MIN_SCORE, Math.min(MAX_SCORE, baseScore));
|
||||
}
|
||||
}
|
||||
|
||||
public static class ReputationEvent {
|
||||
public final String ip;
|
||||
public final String type;
|
||||
public final String details;
|
||||
public final long timestamp;
|
||||
|
||||
public ReputationEvent(String ip, String type, String details, long timestamp) {
|
||||
this.ip = ip;
|
||||
this.type = type;
|
||||
this.details = details;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ReputationAlert {
|
||||
public final String ip;
|
||||
public final String type;
|
||||
public final String details;
|
||||
public final long timestamp;
|
||||
|
||||
public ReputationAlert(String ip, String type, String details, long timestamp) {
|
||||
this.ip = ip;
|
||||
this.type = type;
|
||||
this.details = details;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,467 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.*;
|
||||
import java.security.MessageDigest;
|
||||
import java.nio.file.*;
|
||||
import java.io.*;
|
||||
|
||||
@Service
|
||||
public class IntrusionDetectionService {
|
||||
|
||||
private final Map<String, IntrusionAlert> activeAlerts = new ConcurrentHashMap<>();
|
||||
private final Map<String, SuspiciousActivity> activityLog = new ConcurrentHashMap<>();
|
||||
private final Map<String, List<LoginAttempt>> loginAttempts = new ConcurrentHashMap<>();
|
||||
private final Map<String, UserBehavior> userBehaviors = new ConcurrentHashMap<>();
|
||||
private final Queue<IntrusionEvent> events = new ConcurrentLinkedQueue<>();
|
||||
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(3);
|
||||
|
||||
private static final int MAX_LOGIN_ATTEMPTS = 5;
|
||||
private static final long LOGIN_TIMEOUT = 300000;
|
||||
private static final int MAX_ALERTS = 1000;
|
||||
private static final double ANOMALY_THRESHOLD = 0.8;
|
||||
|
||||
private final AtomicInteger alertLevel = new AtomicInteger(0);
|
||||
private final AtomicLong totalIntrusions = new AtomicLong(0);
|
||||
|
||||
public IntrusionDetectionService() {
|
||||
startDetectionTask();
|
||||
}
|
||||
|
||||
public boolean checkLoginAttempt(String username, String ip, boolean success) {
|
||||
List<LoginAttempt> attempts = loginAttempts.computeIfAbsent(username.toLowerCase(), k -> new CopyOnWriteArrayList<>());
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
attempts.removeIf(a -> now - a.timestamp > LOGIN_TIMEOUT);
|
||||
|
||||
if (!success) {
|
||||
LoginAttempt attempt = new LoginAttempt(username, ip, false, now);
|
||||
attempts.add(attempt);
|
||||
|
||||
if (attempts.size() >= MAX_LOGIN_ATTEMPTS) {
|
||||
triggerAlert("BRUTE_FORCE", username, "Failed login attempts: " + attempts.size(), ip);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
LoginAttempt attempt = new LoginAttempt(username, ip, true, now);
|
||||
attempts.clear();
|
||||
attempts.add(attempt);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void recordFailedLogin(String username, String ip, String reason) {
|
||||
List<LoginAttempt> attempts = loginAttempts.computeIfAbsent(username.toLowerCase(), k -> new CopyOnWriteArrayList<>());
|
||||
attempts.add(new LoginAttempt(username, ip, false, System.currentTimeMillis()));
|
||||
|
||||
recordActivity(username, "LOGIN_FAILED", reason, ip);
|
||||
|
||||
if (attempts.size() >= MAX_LOGIN_ATTEMPTS) {
|
||||
triggerAlert("BRUTE_FORCE", username, "Account: " + username, ip);
|
||||
}
|
||||
}
|
||||
|
||||
public void recordSuccessfulLogin(String username, String ip) {
|
||||
List<LoginAttempt> attempts = loginAttempts.get(username.toLowerCase());
|
||||
if (attempts != null) {
|
||||
attempts.clear();
|
||||
}
|
||||
|
||||
recordActivity(username, "LOGIN_SUCCESS", "Login successful", ip);
|
||||
}
|
||||
|
||||
public void recordActivity(String username, String activityType, String details, String ip) {
|
||||
String key = username + ":" + activityType;
|
||||
SuspiciousActivity activity = activityLog.computeIfAbsent(key, k -> new SuspiciousActivity(username, activityType));
|
||||
|
||||
activity.addOccurrence(details, ip);
|
||||
|
||||
if (activity.isSuspicious()) {
|
||||
triggerAlert("ANOMALY_DETECTED", username, activityType + ": " + details, ip);
|
||||
}
|
||||
}
|
||||
|
||||
public void analyzeBehavior(String username, Map<String, Object> metrics) {
|
||||
UserBehavior behavior = userBehaviors.computeIfAbsent(username, k -> new UserBehavior(username));
|
||||
|
||||
double cpuUsage = getMetricValue(metrics, "cpu");
|
||||
double memoryUsage = getMetricValue(metrics, "memory");
|
||||
double networkIn = getMetricValue(metrics, "network_in");
|
||||
double networkOut = getMetricValue(metrics, "network_out");
|
||||
int fileAccess = getMetricInt(metrics, "file_access");
|
||||
int processCount = getMetricInt(metrics, "processes");
|
||||
|
||||
behavior.addSample(cpuUsage, memoryUsage, networkIn, networkOut, fileAccess, processCount);
|
||||
|
||||
if (behavior.isAnomalous()) {
|
||||
triggerAlert("BEHAVIOR_ANOMALY", username, "Unusual behavior pattern detected", "system");
|
||||
}
|
||||
}
|
||||
|
||||
private double getMetricValue(Map<String, Object> metrics, String key) {
|
||||
Object value = metrics.get(key);
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).doubleValue();
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
private int getMetricInt(Map<String, Object> metrics, String key) {
|
||||
Object value = metrics.get(key);
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).intValue();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public void checkFileAccess(String username, String filePath, String operation) {
|
||||
if (isSensitivePath(filePath)) {
|
||||
recordActivity(username, "SENSITIVE_FILE_ACCESS", operation + ": " + filePath, "system");
|
||||
triggerAlert("FILE_ACCESS_VIOLATION", username, "Access to sensitive file: " + filePath, "system");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSensitivePath(String path) {
|
||||
String[] sensitive = {"/etc/passwd", "/etc/shadow", "/root", "/var/log",
|
||||
"C:\\Windows\\System32", "C:\\Windows\\System", "config"};
|
||||
for (String s : sensitive) {
|
||||
if (path.contains(s)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void checkProcessActivity(String username, String processName, boolean isNew) {
|
||||
if (isSuspiciousProcess(processName)) {
|
||||
recordActivity(username, "SUSPICIOUS_PROCESS", processName, "system");
|
||||
triggerAlert("MALICIOUS_PROCESS", username, "Suspicious process: " + processName, "system");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSuspiciousProcess(String processName) {
|
||||
String[] suspicious = {"nc ", "netcat", "ncat", "socat", "meterpreter",
|
||||
"mimikatz", "pwdump", "lsass", "procmon"};
|
||||
for (String s : suspicious) {
|
||||
if (processName.toLowerCase().contains(s.toLowerCase())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void checkNetworkConnection(String username, String remoteIP, int port) {
|
||||
if (isSuspiciousPort(port)) {
|
||||
recordActivity(username, "SUSPICIOUS_CONNECTION", remoteIP + ":" + port, remoteIP);
|
||||
triggerAlert("SUSPICIOUS_CONNECTION", username, "Connection to suspicious port: " + port, remoteIP);
|
||||
}
|
||||
|
||||
if (isKnownMaliciousIP(remoteIP)) {
|
||||
triggerAlert("MALICIOUS_IP", username, "Known malicious IP: " + remoteIP, remoteIP);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSuspiciousPort(int port) {
|
||||
int[] suspicious = {4444, 5555, 6666, 7777, 8888, 31337, 12345, 54321};
|
||||
for (int p : suspicious) {
|
||||
if (port == p) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isKnownMaliciousIP(String ip) {
|
||||
return false;
|
||||
}
|
||||
|
||||
public void checkCommandExecution(String username, String command) {
|
||||
if (isDangerousCommand(command)) {
|
||||
recordActivity(username, "DANGEROUS_COMMAND", command, "system");
|
||||
triggerAlert("COMMAND_INJECTION", username, "Dangerous command: " + command, "system");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isDangerousCommand(String command) {
|
||||
String[] dangerous = {"rm -rf", "dd if", "mkfs", "shutdown", "reboot",
|
||||
"wget |", "curl |", "nc -e", "bash -i"};
|
||||
for (String d : dangerous) {
|
||||
if (command.toLowerCase().contains(d.toLowerCase())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void checkPrivilegeEscalation(String username, String attemptedAction) {
|
||||
recordActivity(username, "PRIVILEGE_ESCALATION", attemptedAction, "system");
|
||||
triggerAlert("PRIVILEGE_ESCALATION", username, "Privilege escalation attempt: " + attemptedAction, "system");
|
||||
}
|
||||
|
||||
public void triggerAlert(String type, String username, String details, String sourceIP) {
|
||||
String alertKey = type + ":" + username + ":" + System.currentTimeMillis();
|
||||
|
||||
IntrusionAlert alert = new IntrusionAlert(type, username, details, sourceIP);
|
||||
activeAlerts.put(alertKey, alert);
|
||||
|
||||
totalIntrusions.incrementAndGet();
|
||||
|
||||
logEvent(type, username, details, sourceIP);
|
||||
|
||||
updateAlertLevel(type);
|
||||
|
||||
while (activeAlerts.size() > MAX_ALERTS) {
|
||||
String firstKey = activeAlerts.keySet().iterator().next();
|
||||
activeAlerts.remove(firstKey);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateAlertLevel(String alertType) {
|
||||
int current = alertLevel.get();
|
||||
int newLevel = switch (alertType) {
|
||||
case "BRUTE_FORCE", "FILE_ACCESS_VIOLATION" -> Math.min(current + 1, 3);
|
||||
case "MALICIOUS_PROCESS", "MALICIOUS_IP" -> Math.min(current + 2, 3);
|
||||
case "PRIVILEGE_ESCALATION", "COMMAND_INJECTION" -> 3;
|
||||
default -> current;
|
||||
};
|
||||
alertLevel.set(newLevel);
|
||||
}
|
||||
|
||||
public IntrusionAlert getAlert(String alertKey) {
|
||||
return activeAlerts.get(alertKey);
|
||||
}
|
||||
|
||||
public Collection<IntrusionAlert> getActiveAlerts() {
|
||||
return activeAlerts.values();
|
||||
}
|
||||
|
||||
public boolean acknowledgeAlert(String alertKey) {
|
||||
return activeAlerts.remove(alertKey) != null;
|
||||
}
|
||||
|
||||
public void clearAlerts() {
|
||||
activeAlerts.clear();
|
||||
}
|
||||
|
||||
public void logEvent(String type, String username, String details, String sourceIP) {
|
||||
IntrusionEvent event = new IntrusionEvent(type, username, details, sourceIP, System.currentTimeMillis());
|
||||
events.offer(event);
|
||||
|
||||
while (events.size() > 10000) {
|
||||
events.poll();
|
||||
}
|
||||
}
|
||||
|
||||
public List<IntrusionEvent> getEvents(int limit) {
|
||||
List<IntrusionEvent> result = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (IntrusionEvent event : events) {
|
||||
if (count++ >= limit) break;
|
||||
result.add(event);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public int getAlertLevel() {
|
||||
return alertLevel.get();
|
||||
}
|
||||
|
||||
public void setAlertLevel(int level) {
|
||||
alertLevel.set(Math.max(0, Math.min(3, level)));
|
||||
}
|
||||
|
||||
private void startDetectionTask() {
|
||||
scheduler.scheduleAtFixedRate(() -> {
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
activityLog.entrySet().removeIf(entry ->
|
||||
now - entry.getValue().lastActivity > 3600000);
|
||||
|
||||
loginAttempts.entrySet().removeIf(entry -> {
|
||||
entry.getValue().removeIf(a -> now - a.timestamp > LOGIN_TIMEOUT);
|
||||
return entry.getValue().isEmpty();
|
||||
});
|
||||
|
||||
if (alertLevel.get() > 0) {
|
||||
alertLevel.decrementAndGet();
|
||||
}
|
||||
}, 60, 60, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public Map<String, Object> getStats() {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("activeAlerts", activeAlerts.size());
|
||||
stats.put("totalIntrusions", totalIntrusions.get());
|
||||
stats.put("alertLevel", alertLevel.get());
|
||||
stats.put("trackedUsers", userBehaviors.size());
|
||||
return stats;
|
||||
}
|
||||
|
||||
public static class IntrusionAlert {
|
||||
public final String type;
|
||||
public final String username;
|
||||
public final String details;
|
||||
public final String sourceIP;
|
||||
public final long timestamp;
|
||||
public boolean acknowledged = false;
|
||||
|
||||
public IntrusionAlert(String type, String username, String details, String sourceIP) {
|
||||
this.type = type;
|
||||
this.username = username;
|
||||
this.details = details;
|
||||
this.sourceIP = sourceIP;
|
||||
this.timestamp = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
|
||||
public static class LoginAttempt {
|
||||
public final String username;
|
||||
public final String ip;
|
||||
public final boolean success;
|
||||
public final long timestamp;
|
||||
|
||||
public LoginAttempt(String username, String ip, boolean success, long timestamp) {
|
||||
this.username = username;
|
||||
this.ip = ip;
|
||||
this.success = success;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SuspiciousActivity {
|
||||
public final String username;
|
||||
public final String activityType;
|
||||
public final List<ActivityOccurrence> occurrences = new ArrayList<>();
|
||||
public volatile long lastActivity;
|
||||
private int suspiciousCount = 0;
|
||||
|
||||
public SuspiciousActivity(String username, String activityType) {
|
||||
this.username = username;
|
||||
this.activityType = activityType;
|
||||
this.lastActivity = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void addOccurrence(String details, String ip) {
|
||||
occurrences.add(new ActivityOccurrence(details, ip, System.currentTimeMillis()));
|
||||
lastActivity = System.currentTimeMillis();
|
||||
|
||||
if (occurrences.size() > 10) {
|
||||
occurrences.remove(0);
|
||||
}
|
||||
|
||||
if (isRapidFire()) {
|
||||
suspiciousCount++;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isRapidFire() {
|
||||
if (occurrences.size() < 3) return false;
|
||||
long timeSpan = occurrences.get(occurrences.size()-1).timestamp - occurrences.get(0).timestamp;
|
||||
return timeSpan < 1000;
|
||||
}
|
||||
|
||||
public boolean isSuspicious() {
|
||||
return suspiciousCount > 3 || occurrences.size() > 10;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ActivityOccurrence {
|
||||
public final String details;
|
||||
public final String ip;
|
||||
public final long timestamp;
|
||||
|
||||
public ActivityOccurrence(String details, String ip, long timestamp) {
|
||||
this.details = details;
|
||||
this.ip = ip;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
public static class UserBehavior {
|
||||
public final String username;
|
||||
public final List<BehaviorSample> samples = new ArrayList<>();
|
||||
private double avgCpu = 0, avgMemory = 0, avgNetworkIn = 0, avgNetworkOut = 0;
|
||||
private double stdCpu = 0, stdMemory = 0;
|
||||
|
||||
public UserBehavior(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public void addSample(double cpu, double memory, double networkIn, double networkOut, int fileAccess, int processes) {
|
||||
samples.add(new BehaviorSample(cpu, memory, networkIn, networkOut, fileAccess, processes));
|
||||
|
||||
if (samples.size() > 100) {
|
||||
samples.remove(0);
|
||||
}
|
||||
|
||||
recalculateBaselines();
|
||||
}
|
||||
|
||||
private void recalculateBaselines() {
|
||||
if (samples.isEmpty()) return;
|
||||
|
||||
avgCpu = samples.stream().mapToDouble(s -> s.cpu).average().orElse(0);
|
||||
avgMemory = samples.stream().mapToDouble(s -> s.memory).average().orElse(0);
|
||||
avgNetworkIn = samples.stream().mapToDouble(s -> s.networkIn).average().orElse(0);
|
||||
avgNetworkOut = samples.stream().mapToDouble(s -> s.networkOut).average().orElse(0);
|
||||
|
||||
double varianceCpu = samples.stream()
|
||||
.mapToDouble(s -> Math.pow(s.cpu - avgCpu, 2))
|
||||
.average().orElse(0);
|
||||
stdCpu = Math.sqrt(varianceCpu);
|
||||
|
||||
double varianceMemory = samples.stream()
|
||||
.mapToDouble(s -> Math.pow(s.memory - avgMemory, 2))
|
||||
.average().orElse(0);
|
||||
stdMemory = Math.sqrt(varianceMemory);
|
||||
}
|
||||
|
||||
public boolean isAnomalous() {
|
||||
if (samples.isEmpty()) return false;
|
||||
|
||||
BehaviorSample latest = samples.get(samples.size() - 1);
|
||||
|
||||
double cpuDeviation = stdCpu > 0 ? Math.abs(latest.cpu - avgCpu) / stdCpu : 0;
|
||||
double memDeviation = stdMemory > 0 ? Math.abs(latest.memory - avgMemory) / stdMemory : 0;
|
||||
|
||||
return cpuDeviation > ANOMALY_THRESHOLD || memDeviation > ANOMALY_THRESHOLD;
|
||||
}
|
||||
}
|
||||
|
||||
public static class BehaviorSample {
|
||||
public final double cpu;
|
||||
public final double memory;
|
||||
public final double networkIn;
|
||||
public final double networkOut;
|
||||
public final int fileAccess;
|
||||
public final int processes;
|
||||
|
||||
public BehaviorSample(double cpu, double memory, double networkIn, double networkOut, int fileAccess, int processes) {
|
||||
this.cpu = cpu;
|
||||
this.memory = memory;
|
||||
this.networkIn = networkIn;
|
||||
this.networkOut = networkOut;
|
||||
this.fileAccess = fileAccess;
|
||||
this.processes = processes;
|
||||
}
|
||||
}
|
||||
|
||||
public static class IntrusionEvent {
|
||||
public final String type;
|
||||
public final String username;
|
||||
public final String details;
|
||||
public final String sourceIP;
|
||||
public final long timestamp;
|
||||
|
||||
public IntrusionEvent(String type, String username, String details, String sourceIP, long timestamp) {
|
||||
this.type = type;
|
||||
this.username = username;
|
||||
this.details = details;
|
||||
this.sourceIP = sourceIP;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,455 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.Matcher;
|
||||
import java.security.MessageDigest;
|
||||
|
||||
@Component
|
||||
public class IntrusionPreventionSystem {
|
||||
private static final Logger logger = LoggerFactory.getLogger(IntrusionPreventionSystem.class);
|
||||
|
||||
private final HostEnforcementService hostEnforcementService;
|
||||
private final CloudflareIntegrationService cloudflareIntegrationService;
|
||||
private final Map<String, IPSRule> rules = new ConcurrentHashMap<>();
|
||||
private final Map<String, BlockedEntry> blockedIPs = new ConcurrentHashMap<>();
|
||||
private final Queue<IPSAlert> alertQueue = new ConcurrentLinkedQueue<>();
|
||||
private final Map<String, ConnectionTracker> connectionTrackers = new ConcurrentHashMap<>();
|
||||
private final AtomicLong totalPacketsProcessed = new AtomicLong(0);
|
||||
private final AtomicLong totalPacketsBlocked = new AtomicLong(0);
|
||||
private final AtomicLong totalAttacksPrevented = new AtomicLong(0);
|
||||
|
||||
private static final int BLOCK_DURATION_MINUTES = 60;
|
||||
private static final int MAX_CONNECTIONS_PER_MINUTE = 100;
|
||||
private static final int PACKET_RATE_LIMIT = 1000;
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
public IntrusionPreventionSystem() {
|
||||
this(new HostEnforcementService(), new CloudflareIntegrationService());
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public IntrusionPreventionSystem(HostEnforcementService hostEnforcementService,
|
||||
CloudflareIntegrationService cloudflareIntegrationService) {
|
||||
this.hostEnforcementService = hostEnforcementService;
|
||||
this.cloudflareIntegrationService = cloudflareIntegrationService;
|
||||
initializeDefaultRules();
|
||||
logger.info("Intrusion Prevention System initialized");
|
||||
}
|
||||
|
||||
private void initializeDefaultRules() {
|
||||
addRule("BLOCK_SSH_BRUTE_FORCE", "HIGH", "TCP", 22,
|
||||
"(\\w+:){1,100}Failed password", "SSH brute force attack");
|
||||
|
||||
addRule("BLOCK_TELNET_BRUTE_FORCE", "HIGH", "TCP", 23,
|
||||
"(failed|invalid).*login", "Telnet brute force attack");
|
||||
|
||||
addRule("BLOCK_FTP_BRUTE_FORCE", "HIGH", "TCP", 21,
|
||||
"530 Login authentication failed", "FTP brute force attack");
|
||||
|
||||
addRule("BLOCK_MYSQL_BRUTE_FORCE", "HIGH", "TCP", 3306,
|
||||
"Access denied for user", "MySQL brute force attack");
|
||||
|
||||
addRule("BLOCK_PORT_SCAN", "MEDIUM", "TCP", 0,
|
||||
"port.*scan", "Port scan detected");
|
||||
|
||||
addRule("BLOCK_SYN_FLOOD", "HIGH", "TCP", 0,
|
||||
"SYN.*SYN.*SYN", "SYN flood attack");
|
||||
|
||||
addRule("BLOCK_UDP_FLOOD", "HIGH", "UDP", 0,
|
||||
"udp.*flood", "UDP flood attack");
|
||||
|
||||
addRule("BLOCK_ICMP_FLOOD", "MEDIUM", "ICMP", 0,
|
||||
"icmp.*flood", "ICMP flood attack");
|
||||
|
||||
addRule("BLOCK_DNS_AMPLIFICATION", "HIGH", "UDP", 53,
|
||||
"dns.*amplification", "DNS amplification attack");
|
||||
|
||||
addRule("BLOCK_SQL_INJECTION", "HIGH", "TCP", 0,
|
||||
"(union|select|insert|update|delete).*(from|where)", "SQL injection attempt");
|
||||
|
||||
addRule("BLOCK_XSS_ATTACK", "HIGH", "TCP", 0,
|
||||
"<script|javascript:|onerror=", "Cross-site scripting attempt");
|
||||
|
||||
addRule("BLOCK_DIRECTORY_TRAVERSAL", "MEDIUM", "TCP", 0,
|
||||
"(\\.\\./|\\.\\.\\\\)", "Directory traversal attempt");
|
||||
|
||||
logger.info("Initialized {} IPS rules", rules.size());
|
||||
}
|
||||
|
||||
public void addRule(String name, String severity, String protocol, int port, String pattern, String description) {
|
||||
IPSRule rule = new IPSRule(name, severity, protocol, port, pattern, description);
|
||||
rules.put(name, rule);
|
||||
logger.info("Added IPS rule: {}", name);
|
||||
}
|
||||
|
||||
public IPSResult processPacket(String sourceIP, String destIP, int sourcePort, int destPort,
|
||||
String protocol, byte[] payload) {
|
||||
IPSResult result = new IPSResult();
|
||||
result.setSourceIP(sourceIP);
|
||||
result.setDestIP(destIP);
|
||||
result.setSourcePort(sourcePort);
|
||||
result.setDestPort(destPort);
|
||||
result.setProtocol(protocol);
|
||||
result.setTimestamp(LocalDateTime.now());
|
||||
|
||||
totalPacketsProcessed.incrementAndGet();
|
||||
|
||||
if (isBlocked(sourceIP)) {
|
||||
result.setBlocked(true);
|
||||
result.setReason("IP is already blocked");
|
||||
totalPacketsBlocked.incrementAndGet();
|
||||
return result;
|
||||
}
|
||||
|
||||
if (checkRateLimit(sourceIP)) {
|
||||
blockIP(sourceIP, "Rate limit exceeded", 30);
|
||||
result.setBlocked(true);
|
||||
result.setReason("Rate limit exceeded");
|
||||
totalPacketsBlocked.incrementAndGet();
|
||||
totalAttacksPrevented.incrementAndGet();
|
||||
return result;
|
||||
}
|
||||
|
||||
updateConnectionTracker(sourceIP, destIP, destPort, protocol);
|
||||
|
||||
for (IPSRule rule : rules.values()) {
|
||||
if (!rule.isEnabled()) continue;
|
||||
|
||||
if (!rule.matchesProtocol(protocol)) continue;
|
||||
if (rule.getPort() != 0 && rule.getPort() != destPort) continue;
|
||||
|
||||
if (payload != null && rule.matchesPayload(new String(payload))) {
|
||||
result.addMatchedRule(rule.getName());
|
||||
result.setThreatDetected(true);
|
||||
|
||||
if ("HIGH".equals(rule.getSeverity())) {
|
||||
blockIP(sourceIP, rule.getDescription(), BLOCK_DURATION_MINUTES);
|
||||
result.setBlocked(true);
|
||||
result.setReason("Blocked: " + rule.getDescription());
|
||||
totalPacketsBlocked.incrementAndGet();
|
||||
totalAttacksPrevented.incrementAndGet();
|
||||
triggerAlert(sourceIP, destIP, rule);
|
||||
} else if ("MEDIUM".equals(rule.getSeverity())) {
|
||||
result.setWarning("Medium severity threat detected: " + rule.getDescription());
|
||||
triggerAlert(sourceIP, destIP, rule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean isBlocked(String ip) {
|
||||
BlockedEntry entry = blockedIPs.get(ip);
|
||||
if (entry == null) return false;
|
||||
|
||||
if (entry.isExpired()) {
|
||||
blockedIPs.remove(ip);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void blockIP(String ip, String reason, int durationMinutes) {
|
||||
BlockedEntry entry = new BlockedEntry(ip, reason, durationMinutes);
|
||||
blockedIPs.put(ip, entry);
|
||||
hostEnforcementService.blockIp(ip, reason, durationMinutes);
|
||||
logger.warn("Blocked IP: {} for {} minutes - Reason: {}", ip, durationMinutes, reason);
|
||||
}
|
||||
|
||||
public void unblockIP(String ip) {
|
||||
blockedIPs.remove(ip);
|
||||
hostEnforcementService.releaseIp(ip, "IPS unblock");
|
||||
logger.info("Unblocked IP: {}", ip);
|
||||
}
|
||||
|
||||
public HostEnforcementService.EnforcementActionResult enforceDecision(String ip, int severity, String reason, boolean challengeEdge) {
|
||||
if (severity >= 90) {
|
||||
HostEnforcementService.EnforcementActionResult local = hostEnforcementService.blockIp(ip, reason, BLOCK_DURATION_MINUTES);
|
||||
if (challengeEdge) {
|
||||
cloudflareIntegrationService.applyChallenge(ip, reason);
|
||||
} else {
|
||||
cloudflareIntegrationService.applyBlock(ip, reason);
|
||||
}
|
||||
return local;
|
||||
}
|
||||
return hostEnforcementService.rateLimitIp(ip,
|
||||
hostEnforcementService.getStats().get("managedRules") instanceof Number ? 60 : 60,
|
||||
reason);
|
||||
}
|
||||
|
||||
private boolean checkRateLimit(String ip) {
|
||||
ConnectionTracker tracker = connectionTrackers.get(ip);
|
||||
if (tracker == null) return false;
|
||||
|
||||
return tracker.getPacketsPerMinute() > PACKET_RATE_LIMIT;
|
||||
}
|
||||
|
||||
private void updateConnectionTracker(String sourceIP, String destIP, int destPort, String protocol) {
|
||||
ConnectionTracker tracker = connectionTrackers.computeIfAbsent(sourceIP, k -> new ConnectionTracker(sourceIP));
|
||||
tracker.recordConnection(destIP, destPort, protocol);
|
||||
}
|
||||
|
||||
private void triggerAlert(String sourceIP, String destIP, IPSRule rule) {
|
||||
IPSAlert alert = new IPSAlert(
|
||||
sourceIP,
|
||||
destIP,
|
||||
rule.getName(),
|
||||
rule.getSeverity(),
|
||||
rule.getDescription(),
|
||||
LocalDateTime.now()
|
||||
);
|
||||
alertQueue.offer(alert);
|
||||
|
||||
if (alertQueue.size() > 1000) {
|
||||
alertQueue.poll();
|
||||
}
|
||||
}
|
||||
|
||||
public void enableRule(String ruleName) {
|
||||
IPSRule rule = rules.get(ruleName);
|
||||
if (rule != null) {
|
||||
rule.setEnabled(true);
|
||||
logger.info("Enabled IPS rule: {}", ruleName);
|
||||
}
|
||||
}
|
||||
|
||||
public void disableRule(String ruleName) {
|
||||
IPSRule rule = rules.get(ruleName);
|
||||
if (rule != null) {
|
||||
rule.setEnabled(false);
|
||||
logger.info("Disabled IPS rule: {}", ruleName);
|
||||
}
|
||||
}
|
||||
|
||||
public void addBlockedIP(String ip, String reason, int durationMinutes) {
|
||||
blockIP(ip, reason, durationMinutes);
|
||||
}
|
||||
|
||||
public Map<String, Object> getStats() {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("totalPacketsProcessed", totalPacketsProcessed.get());
|
||||
stats.put("totalPacketsBlocked", totalPacketsBlocked.get());
|
||||
stats.put("totalAttacksPrevented", totalAttacksPrevented.get());
|
||||
stats.put("activeBlocks", blockedIPs.size());
|
||||
stats.put("rulesLoaded", rules.size());
|
||||
stats.put("activeTrackers", connectionTrackers.size());
|
||||
|
||||
long highSeverity = rules.values().stream()
|
||||
.filter(r -> "HIGH".equals(r.getSeverity()) && r.isEnabled())
|
||||
.count();
|
||||
stats.put("highSeverityRules", highSeverity);
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
public List<BlockedEntry> getBlockedIPs() {
|
||||
return new ArrayList<>(blockedIPs.values());
|
||||
}
|
||||
|
||||
public List<IPSAlert> getRecentAlerts(int limit) {
|
||||
List<IPSAlert> alerts = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (IPSAlert alert : alertQueue) {
|
||||
if (count++ >= limit) break;
|
||||
alerts.add(alert);
|
||||
}
|
||||
return alerts;
|
||||
}
|
||||
|
||||
public Map<String, ConnectionTracker> getConnectionTrackers() {
|
||||
return new HashMap<>(connectionTrackers);
|
||||
}
|
||||
|
||||
public void cleanupExpiredBlocks() {
|
||||
List<String> expired = new ArrayList<>();
|
||||
for (Map.Entry<String, BlockedEntry> entry : blockedIPs.entrySet()) {
|
||||
if (entry.getValue().isExpired()) {
|
||||
expired.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
for (String ip : expired) {
|
||||
blockedIPs.remove(ip);
|
||||
}
|
||||
|
||||
if (!expired.isEmpty()) {
|
||||
logger.info("Cleaned up {} expired blocks", expired.size());
|
||||
}
|
||||
}
|
||||
|
||||
public static class IPSRule {
|
||||
private final String name;
|
||||
private final String severity;
|
||||
private final String protocol;
|
||||
private final int port;
|
||||
private final Pattern pattern;
|
||||
private final String description;
|
||||
private volatile boolean enabled = true;
|
||||
|
||||
public IPSRule(String name, String severity, String protocol, int port, String pattern, String description) {
|
||||
this.name = name;
|
||||
this.severity = severity;
|
||||
this.protocol = protocol;
|
||||
this.port = port;
|
||||
this.pattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE);
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public boolean matchesProtocol(String protocol) {
|
||||
return this.protocol.equalsIgnoreCase(protocol) || this.protocol.equalsIgnoreCase("ALL");
|
||||
}
|
||||
|
||||
public boolean matchesPayload(String payload) {
|
||||
if (payload == null || payload.isEmpty()) return false;
|
||||
return pattern.matcher(payload).find();
|
||||
}
|
||||
|
||||
public String getName() { return name; }
|
||||
public String getSeverity() { return severity; }
|
||||
public String getProtocol() { return protocol; }
|
||||
public int getPort() { return port; }
|
||||
public Pattern getPattern() { return pattern; }
|
||||
public String getDescription() { return description; }
|
||||
public boolean isEnabled() { return enabled; }
|
||||
public void setEnabled(boolean enabled) { this.enabled = enabled; }
|
||||
}
|
||||
|
||||
public static class BlockedEntry {
|
||||
private final String ip;
|
||||
private final String reason;
|
||||
private final long blockedAt;
|
||||
private final long expiresAt;
|
||||
|
||||
public BlockedEntry(String ip, String reason, int durationMinutes) {
|
||||
this.ip = ip;
|
||||
this.reason = reason;
|
||||
this.blockedAt = System.currentTimeMillis();
|
||||
this.expiresAt = blockedAt + (durationMinutes * 60 * 1000L);
|
||||
}
|
||||
|
||||
public boolean isExpired() {
|
||||
return System.currentTimeMillis() > expiresAt;
|
||||
}
|
||||
|
||||
public String getIp() { return ip; }
|
||||
public String getReason() { return reason; }
|
||||
public long getBlockedAt() { return blockedAt; }
|
||||
public long getExpiresAt() { return expiresAt; }
|
||||
}
|
||||
|
||||
public static class ConnectionTracker {
|
||||
private final String ip;
|
||||
private final Map<String, Integer> destConnections;
|
||||
private final AtomicLong totalPackets;
|
||||
private volatile long windowStart;
|
||||
private volatile long packetsInWindow;
|
||||
|
||||
public ConnectionTracker(String ip) {
|
||||
this.ip = ip;
|
||||
this.destConnections = new ConcurrentHashMap<>();
|
||||
this.totalPackets = new AtomicLong(0);
|
||||
this.windowStart = System.currentTimeMillis();
|
||||
this.packetsInWindow = 0;
|
||||
}
|
||||
|
||||
public synchronized void recordConnection(String destIP, int port, String protocol) {
|
||||
totalPackets.incrementAndGet();
|
||||
packetsInWindow++;
|
||||
|
||||
String key = destIP + ":" + port;
|
||||
destConnections.merge(key, 1, Integer::sum);
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
if (now - windowStart > 60000) {
|
||||
packetsInWindow = 0;
|
||||
windowStart = now;
|
||||
}
|
||||
}
|
||||
|
||||
public long getPacketsPerMinute() {
|
||||
return packetsInWindow;
|
||||
}
|
||||
|
||||
public String getIp() { return ip; }
|
||||
public long getTotalPackets() { return totalPackets.get(); }
|
||||
public Map<String, Integer> getDestConnections() { return destConnections; }
|
||||
}
|
||||
|
||||
public static class IPSResult {
|
||||
private String sourceIP;
|
||||
private String destIP;
|
||||
private int sourcePort;
|
||||
private int destPort;
|
||||
private String protocol;
|
||||
private boolean blocked;
|
||||
private boolean threatDetected;
|
||||
private String reason;
|
||||
private String warning;
|
||||
private List<String> matchedRules;
|
||||
private LocalDateTime timestamp;
|
||||
|
||||
public IPSResult() {
|
||||
this.matchedRules = new ArrayList<>();
|
||||
}
|
||||
|
||||
public String getSourceIP() { return sourceIP; }
|
||||
public void setSourceIP(String sourceIP) { this.sourceIP = sourceIP; }
|
||||
public String getDestIP() { return destIP; }
|
||||
public void setDestIP(String destIP) { this.destIP = destIP; }
|
||||
public int getSourcePort() { return sourcePort; }
|
||||
public void setSourcePort(int sourcePort) { this.sourcePort = sourcePort; }
|
||||
public int getDestPort() { return destPort; }
|
||||
public void setDestPort(int destPort) { this.destPort = destPort; }
|
||||
public String getProtocol() { return protocol; }
|
||||
public void setProtocol(String protocol) { this.protocol = protocol; }
|
||||
public boolean isBlocked() { return blocked; }
|
||||
public void setBlocked(boolean blocked) { this.blocked = blocked; }
|
||||
public boolean isThreatDetected() { return threatDetected; }
|
||||
public void setThreatDetected(boolean threatDetected) { this.threatDetected = threatDetected; }
|
||||
public String getReason() { return reason; }
|
||||
public void setReason(String reason) { this.reason = reason; }
|
||||
public String getWarning() { return warning; }
|
||||
public void setWarning(String warning) { this.warning = warning; }
|
||||
public List<String> getMatchedRules() { return matchedRules; }
|
||||
public void addMatchedRule(String rule) { this.matchedRules.add(rule); }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
public void setTimestamp(LocalDateTime timestamp) { this.timestamp = timestamp; }
|
||||
}
|
||||
|
||||
public static class IPSAlert {
|
||||
private final String sourceIP;
|
||||
private final String destIP;
|
||||
private final String ruleName;
|
||||
private final String severity;
|
||||
private final String description;
|
||||
private final LocalDateTime timestamp;
|
||||
|
||||
public IPSAlert(String sourceIP, String destIP, String ruleName, String severity, String description, LocalDateTime timestamp) {
|
||||
this.sourceIP = sourceIP;
|
||||
this.destIP = destIP;
|
||||
this.ruleName = ruleName;
|
||||
this.severity = severity;
|
||||
this.description = description;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getSourceIP() { return sourceIP; }
|
||||
public String getDestIP() { return destIP; }
|
||||
public String getRuleName() { return ruleName; }
|
||||
public String getSeverity() { return severity; }
|
||||
public String getDescription() { return description; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,404 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Component
|
||||
public class LoadBalancerService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(LoadBalancerService.class);
|
||||
|
||||
private final Map<String, BackendServer> backendServers = new ConcurrentHashMap<>();
|
||||
private final Map<String, LoadBalancerRule> rules = new ConcurrentHashMap<>();
|
||||
private final Map<String, Queue<Request>> requestQueue = new ConcurrentHashMap<>();
|
||||
private final Map<String, LoadBalancerStats> stats = new ConcurrentHashMap<>();
|
||||
private final Queue<LoadBalancerEvent> eventLog = new ConcurrentLinkedQueue<>();
|
||||
private final AtomicLong totalRequests = new AtomicLong(0);
|
||||
private final AtomicLong totalFailedRequests = new AtomicLong(0);
|
||||
private final AtomicLong totalRedirects = new AtomicLong(0);
|
||||
|
||||
private String currentAlgorithm = "ROUND_ROBIN";
|
||||
private volatile boolean enabled = true;
|
||||
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
|
||||
|
||||
private static final int MAX_QUEUE_SIZE = 1000;
|
||||
private static final int HEALTH_CHECK_INTERVAL = 30;
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
public LoadBalancerService() {
|
||||
initializeDefaultRules();
|
||||
startHealthCheck();
|
||||
logger.info("Load Balancer Service initialized");
|
||||
}
|
||||
|
||||
private void initializeDefaultRules() {
|
||||
addRule("round-robin", "ROUND_ROBIN", "Simple round robin distribution");
|
||||
addRule("least-connections", "LEAST_CONNECTIONS", "Route to server with fewest connections");
|
||||
addRule("weighted", "WEIGHTED", "Weighted distribution based on server capacity");
|
||||
addRule("ip-hash", "IP_HASH", "Sticky sessions based on client IP");
|
||||
addRule("response-time", "RESPONSE_TIME", "Route to fastest responding server");
|
||||
}
|
||||
|
||||
private void addRule(String name, String algorithm, String description) {
|
||||
LoadBalancerRule rule = new LoadBalancerRule(name, algorithm, description);
|
||||
rules.put(name, rule);
|
||||
}
|
||||
|
||||
public void addBackendServer(String serverId, String host, int port, int weight) {
|
||||
BackendServer server = new BackendServer(serverId, host, port, weight);
|
||||
backendServers.put(serverId, server);
|
||||
stats.put(serverId, new LoadBalancerStats(serverId));
|
||||
logger.info("Added backend server: {} -> {}:{}", serverId, host, port);
|
||||
}
|
||||
|
||||
public void removeBackendServer(String serverId) {
|
||||
backendServers.remove(serverId);
|
||||
stats.remove(serverId);
|
||||
logger.info("Removed backend server: {}", serverId);
|
||||
}
|
||||
|
||||
public BackendServer selectBackendServer(String clientIP) {
|
||||
if (!enabled) return null;
|
||||
|
||||
List<BackendServer> healthyServers = getHealthyServers();
|
||||
if (healthyServers.isEmpty()) {
|
||||
logger.warn("No healthy backend servers available");
|
||||
return null;
|
||||
}
|
||||
|
||||
totalRequests.incrementAndGet();
|
||||
|
||||
BackendServer selected;
|
||||
switch (currentAlgorithm) {
|
||||
case "LEAST_CONNECTIONS":
|
||||
selected = selectLeastConnections(healthyServers);
|
||||
break;
|
||||
case "WEIGHTED":
|
||||
selected = selectWeighted(healthyServers);
|
||||
break;
|
||||
case "IP_HASH":
|
||||
selected = selectByIPHash(clientIP, healthyServers);
|
||||
break;
|
||||
case "RESPONSE_TIME":
|
||||
selected = selectByResponseTime(healthyServers);
|
||||
break;
|
||||
default:
|
||||
selected = selectRoundRobin(healthyServers);
|
||||
}
|
||||
|
||||
if (selected != null) {
|
||||
LoadBalancerStats serverStats = stats.get(selected.getServerId());
|
||||
if (serverStats != null) {
|
||||
serverStats.incrementRequestCount();
|
||||
}
|
||||
logEvent("ROUTED", clientIP, selected.getHost() + ":" + selected.getPort());
|
||||
}
|
||||
|
||||
return selected;
|
||||
}
|
||||
|
||||
private List<BackendServer> getHealthyServers() {
|
||||
List<BackendServer> healthy = new ArrayList<>();
|
||||
for (BackendServer server : backendServers.values()) {
|
||||
if (server.isHealthy()) {
|
||||
healthy.add(server);
|
||||
}
|
||||
}
|
||||
return healthy;
|
||||
}
|
||||
|
||||
private BackendServer selectRoundRobin(List<BackendServer> servers) {
|
||||
int index = (int) (totalRequests.get() % servers.size());
|
||||
return servers.get(index);
|
||||
}
|
||||
|
||||
private BackendServer selectLeastConnections(List<BackendServer> servers) {
|
||||
BackendServer selected = null;
|
||||
long minConnections = Long.MAX_VALUE;
|
||||
|
||||
for (BackendServer server : servers) {
|
||||
LoadBalancerStats serverStats = stats.get(server.getServerId());
|
||||
long connections = serverStats != null ? serverStats.getActiveConnections() : 0;
|
||||
|
||||
if (connections < minConnections) {
|
||||
minConnections = connections;
|
||||
selected = server;
|
||||
}
|
||||
}
|
||||
|
||||
return selected;
|
||||
}
|
||||
|
||||
private BackendServer selectWeighted(List<BackendServer> servers) {
|
||||
int totalWeight = servers.stream().mapToInt(BackendServer::getWeight).sum();
|
||||
int random = new Random().nextInt(totalWeight);
|
||||
|
||||
int cumulative = 0;
|
||||
for (BackendServer server : servers) {
|
||||
cumulative += server.getWeight();
|
||||
if (random < cumulative) {
|
||||
return server;
|
||||
}
|
||||
}
|
||||
|
||||
return servers.get(0);
|
||||
}
|
||||
|
||||
private BackendServer selectByIPHash(String clientIP, List<BackendServer> servers) {
|
||||
int hash = clientIP.hashCode();
|
||||
int index = Math.abs(hash) % servers.size();
|
||||
return servers.get(index);
|
||||
}
|
||||
|
||||
private BackendServer selectByResponseTime(List<BackendServer> servers) {
|
||||
BackendServer selected = null;
|
||||
long fastestResponse = Long.MAX_VALUE;
|
||||
|
||||
for (BackendServer server : servers) {
|
||||
LoadBalancerStats serverStats = stats.get(server.getServerId());
|
||||
long avgResponse = serverStats != null ? serverStats.getAverageResponseTime() : 0;
|
||||
|
||||
if (avgResponse < fastestResponse) {
|
||||
fastestResponse = avgResponse;
|
||||
selected = server;
|
||||
}
|
||||
}
|
||||
|
||||
return selected;
|
||||
}
|
||||
|
||||
public void recordRequestSuccess(String serverId, long responseTime) {
|
||||
LoadBalancerStats serverStats = stats.get(serverId);
|
||||
if (serverStats != null) {
|
||||
serverStats.recordSuccess(responseTime);
|
||||
}
|
||||
}
|
||||
|
||||
public void recordRequestFailure(String serverId) {
|
||||
LoadBalancerStats serverStats = stats.get(serverId);
|
||||
if (serverStats != null) {
|
||||
serverStats.recordFailure();
|
||||
}
|
||||
totalFailedRequests.incrementAndGet();
|
||||
|
||||
BackendServer server = backendServers.get(serverId);
|
||||
if (server != null) {
|
||||
server.incrementFailureCount();
|
||||
if (server.getFailureCount() >= 5) {
|
||||
server.setHealthy(false);
|
||||
logger.warn("Backend server {} marked as unhealthy after {} failures", serverId, server.getFailureCount());
|
||||
logEvent("SERVER_UNHEALTHY", serverId, "Marked as unhealthy");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setAlgorithm(String algorithm) {
|
||||
this.currentAlgorithm = algorithm;
|
||||
logger.info("Load balancer algorithm changed to: {}", algorithm);
|
||||
}
|
||||
|
||||
public void enable() {
|
||||
this.enabled = true;
|
||||
logger.info("Load balancer enabled");
|
||||
}
|
||||
|
||||
public void disable() {
|
||||
this.enabled = false;
|
||||
logger.info("Load balancer disabled");
|
||||
}
|
||||
|
||||
private void startHealthCheck() {
|
||||
scheduler.scheduleAtFixedRate(() -> {
|
||||
checkBackendHealth();
|
||||
}, HEALTH_CHECK_INTERVAL, HEALTH_CHECK_INTERVAL, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private void checkBackendHealth() {
|
||||
for (BackendServer server : backendServers.values()) {
|
||||
boolean healthy = performHealthCheck(server);
|
||||
|
||||
if (healthy && !server.isHealthy()) {
|
||||
server.setHealthy(true);
|
||||
server.resetFailureCount();
|
||||
logger.info("Backend server {} is healthy again", server.getServerId());
|
||||
logEvent("SERVER_HEALTHY", server.getServerId(), "Health check passed");
|
||||
} else if (!healthy && server.isHealthy()) {
|
||||
server.setHealthy(false);
|
||||
logger.warn("Backend server {} failed health check", server.getServerId());
|
||||
logEvent("SERVER_UNHEALTHY", server.getServerId(), "Health check failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean performHealthCheck(BackendServer server) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public Map<String, Object> getStats() {
|
||||
Map<String, Object> statsMap = new HashMap<>();
|
||||
statsMap.put("enabled", enabled);
|
||||
statsMap.put("algorithm", currentAlgorithm);
|
||||
statsMap.put("totalRequests", totalRequests.get());
|
||||
statsMap.put("totalFailedRequests", totalFailedRequests.get());
|
||||
statsMap.put("totalRedirects", totalRedirects.get());
|
||||
statsMap.put("backendServers", backendServers.size());
|
||||
|
||||
long healthyServers = backendServers.values().stream().filter(BackendServer::isHealthy).count();
|
||||
statsMap.put("healthyServers", healthyServers);
|
||||
|
||||
double successRate = totalRequests.get() > 0 ?
|
||||
(totalRequests.get() - totalFailedRequests.get()) * 100.0 / totalRequests.get() : 0;
|
||||
statsMap.put("successRate", String.format("%.2f%%", successRate));
|
||||
|
||||
return statsMap;
|
||||
}
|
||||
|
||||
public Map<String, BackendServer> getBackendServers() {
|
||||
return new HashMap<>(backendServers);
|
||||
}
|
||||
|
||||
public Map<String, LoadBalancerStats> getServerStats() {
|
||||
return new HashMap<>(stats);
|
||||
}
|
||||
|
||||
public List<LoadBalancerEvent> getRecentEvents(int limit) {
|
||||
List<LoadBalancerEvent> events = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (LoadBalancerEvent event : eventLog) {
|
||||
if (count++ >= limit) break;
|
||||
events.add(event);
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
private void logEvent(String type, String source, String details) {
|
||||
LoadBalancerEvent event = new LoadBalancerEvent(type, source, details, LocalDateTime.now());
|
||||
eventLog.offer(event);
|
||||
if (eventLog.size() > 1000) {
|
||||
eventLog.poll();
|
||||
}
|
||||
}
|
||||
|
||||
public static class BackendServer {
|
||||
private final String serverId;
|
||||
private final String host;
|
||||
private final int port;
|
||||
private final int weight;
|
||||
private volatile boolean healthy = true;
|
||||
private volatile int failureCount = 0;
|
||||
|
||||
public BackendServer(String serverId, String host, int port, int weight) {
|
||||
this.serverId = serverId;
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.weight = weight;
|
||||
}
|
||||
|
||||
public void incrementFailureCount() { failureCount++; }
|
||||
public void resetFailureCount() { failureCount = 0; }
|
||||
|
||||
public String getServerId() { return serverId; }
|
||||
public String getHost() { return host; }
|
||||
public int getPort() { return port; }
|
||||
public int getWeight() { return weight; }
|
||||
public boolean isHealthy() { return healthy; }
|
||||
public void setHealthy(boolean healthy) { this.healthy = healthy; }
|
||||
public int getFailureCount() { return failureCount; }
|
||||
}
|
||||
|
||||
public static class LoadBalancerRule {
|
||||
private final String name;
|
||||
private final String algorithm;
|
||||
private final String description;
|
||||
|
||||
public LoadBalancerRule(String name, String algorithm, String description) {
|
||||
this.name = name;
|
||||
this.algorithm = algorithm;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getName() { return name; }
|
||||
public String getAlgorithm() { return algorithm; }
|
||||
public String getDescription() { return description; }
|
||||
}
|
||||
|
||||
public static class LoadBalancerStats {
|
||||
private final String serverId;
|
||||
private final AtomicLong requestCount = new AtomicLong(0);
|
||||
private final AtomicLong failureCount = new AtomicLong(0);
|
||||
private final AtomicLong totalResponseTime = new AtomicLong(0);
|
||||
private volatile long activeConnections = 0;
|
||||
|
||||
public LoadBalancerStats(String serverId) {
|
||||
this.serverId = serverId;
|
||||
}
|
||||
|
||||
public void incrementRequestCount() { requestCount.incrementAndGet(); activeConnections++; }
|
||||
public void recordSuccess(long responseTime) {
|
||||
totalResponseTime.addAndGet(responseTime);
|
||||
activeConnections = Math.max(0, activeConnections - 1);
|
||||
}
|
||||
public void recordFailure() {
|
||||
failureCount.incrementAndGet();
|
||||
activeConnections = Math.max(0, activeConnections - 1);
|
||||
}
|
||||
|
||||
public long getAverageResponseTime() {
|
||||
long count = requestCount.get();
|
||||
return count > 0 ? totalResponseTime.get() / count : 0;
|
||||
}
|
||||
|
||||
public String getServerId() { return serverId; }
|
||||
public long getRequestCount() { return requestCount.get(); }
|
||||
public long getFailureCount() { return failureCount.get(); }
|
||||
public long getActiveConnections() { return activeConnections; }
|
||||
}
|
||||
|
||||
public static class Request {
|
||||
private final String requestId;
|
||||
private final String clientIP;
|
||||
private final String path;
|
||||
private final long timestamp;
|
||||
|
||||
public Request(String requestId, String clientIP, String path) {
|
||||
this.requestId = requestId;
|
||||
this.clientIP = clientIP;
|
||||
this.path = path;
|
||||
this.timestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public String getRequestId() { return requestId; }
|
||||
public String getClientIP() { return clientIP; }
|
||||
public String getPath() { return path; }
|
||||
public long getTimestamp() { return timestamp; }
|
||||
}
|
||||
|
||||
public static class LoadBalancerEvent {
|
||||
private final String type;
|
||||
private final String source;
|
||||
private final String details;
|
||||
private final LocalDateTime timestamp;
|
||||
|
||||
public LoadBalancerEvent(String type, String source, String details, LocalDateTime timestamp) {
|
||||
this.type = type;
|
||||
this.source = source;
|
||||
this.details = details;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getType() { return type; }
|
||||
public String getSource() { return source; }
|
||||
public String getDetails() { return details; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.*;
|
||||
import java.security.MessageDigest;
|
||||
|
||||
@Service
|
||||
public class LogAnalysisService {
|
||||
|
||||
private final Map<String, List<LogEntry>> logBuffer = new ConcurrentHashMap<>();
|
||||
private final Map<String, PatternRule> patternRules = new ConcurrentHashMap<>();
|
||||
private final Queue<SecurityEvent> securityEvents = new ConcurrentLinkedQueue<>();
|
||||
private final Map<String, AtomicInteger> threatCounts = new ConcurrentHashMap<>();
|
||||
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
|
||||
|
||||
private static final int MAX_BUFFER_SIZE = 10000;
|
||||
private static final long ANALYSIS_INTERVAL = 30000;
|
||||
|
||||
private final AtomicLong totalLogsAnalyzed = new AtomicLong(0);
|
||||
private final AtomicLong threatsDetected = new AtomicLong(0);
|
||||
|
||||
public LogAnalysisService() {
|
||||
initializePatternRules();
|
||||
startAnalysisTask();
|
||||
}
|
||||
|
||||
private void initializePatternRules() {
|
||||
addPatternRule("SQL_INJECTION", ".*(union|select|insert|update|delete|drop).*", 80, true);
|
||||
addPatternRule("XSS_ATTACK", ".*(<script|javascript:|onerror=|onload=).*", 80, true);
|
||||
addPatternRule("PATH_TRAVERSAL", ".*(\\.\\./|\\.\\.\\\\).*", 70, true);
|
||||
addPatternRule("COMMAND_INJECTION", ".*(;|\\||\\$\\(|`).*", 75, true);
|
||||
addPatternRule("BRUTE_FORCE", ".*(failed login|authentication failure|invalid password).*", 60, true);
|
||||
addPatternRule("FILE_ACCESS", ".*(\\.\\./etc/passwd|\\.\\./etc/shadow).*", 90, true);
|
||||
addPatternRule("PORT_SCAN", ".*(port scan|connection attempt).*", 50, true);
|
||||
addPatternRule("DDOS_PATTERN", ".*(flood| amplification|botnet).*", 70, true);
|
||||
}
|
||||
|
||||
public void addPatternRule(String name, String pattern, int severity, boolean enabled) {
|
||||
try {
|
||||
PatternRule rule = new PatternRule(name, pattern, severity, enabled);
|
||||
patternRules.put(name, rule);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
|
||||
public void analyzeLog(String source, String logMessage) {
|
||||
totalLogsAnalyzed.incrementAndGet();
|
||||
|
||||
LogEntry entry = new LogEntry(source, logMessage, System.currentTimeMillis());
|
||||
|
||||
List<LogEntry> buffer = logBuffer.computeIfAbsent(source, k -> new CopyOnWriteArrayList<>());
|
||||
buffer.add(entry);
|
||||
|
||||
while (buffer.size() > MAX_BUFFER_SIZE) {
|
||||
buffer.remove(0);
|
||||
}
|
||||
|
||||
for (PatternRule rule : patternRules.values()) {
|
||||
if (!rule.enabled) continue;
|
||||
|
||||
if (rule.matches(logMessage)) {
|
||||
handleThreatDetected(source, rule.name, rule.severity, logMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void handleThreatDetected(String source, String threatType, int severity, String details) {
|
||||
threatsDetected.incrementAndGet();
|
||||
|
||||
AtomicInteger count = threatCounts.computeIfAbsent(threatType, k -> new AtomicInteger(0));
|
||||
count.incrementAndGet();
|
||||
|
||||
SecurityEvent event = new SecurityEvent(threatType, source, severity, details, System.currentTimeMillis());
|
||||
securityEvents.offer(event);
|
||||
|
||||
while (securityEvents.size() > 5000) {
|
||||
securityEvents.poll();
|
||||
}
|
||||
}
|
||||
|
||||
public void addCustomLog(String source, String message, Map<String, Object> metadata) {
|
||||
LogEntry entry = new LogEntry(source, message, System.currentTimeMillis());
|
||||
entry.metadata = metadata;
|
||||
|
||||
List<LogEntry> buffer = logBuffer.computeIfAbsent(source, k -> new CopyOnWriteArrayList<>());
|
||||
buffer.add(entry);
|
||||
}
|
||||
|
||||
public List<LogEntry> getLogs(String source, int limit) {
|
||||
List<LogEntry> buffer = logBuffer.get(source);
|
||||
if (buffer == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
List<LogEntry> result = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (LogEntry entry : buffer) {
|
||||
if (count++ >= limit) break;
|
||||
result.add(entry);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<LogEntry> getRecentLogs(int limit) {
|
||||
List<LogEntry> allLogs = new ArrayList<>();
|
||||
for (List<LogEntry> buffer : logBuffer.values()) {
|
||||
allLogs.addAll(buffer);
|
||||
}
|
||||
|
||||
allLogs.sort((a, b) -> Long.compare(b.timestamp, a.timestamp));
|
||||
|
||||
return allLogs.stream().limit(limit).toList();
|
||||
}
|
||||
|
||||
public List<SecurityEvent> getSecurityEvents(int limit) {
|
||||
List<SecurityEvent> result = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (SecurityEvent event : securityEvents) {
|
||||
if (count++ >= limit) break;
|
||||
result.add(event);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public Map<String, Integer> getThreatCounts() {
|
||||
Map<String, Integer> result = new HashMap<>();
|
||||
threatCounts.forEach((k, v) -> result.put(k, v.get()));
|
||||
return result;
|
||||
}
|
||||
|
||||
public Map<String, Object> getStatistics() {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("totalLogsAnalyzed", totalLogsAnalyzed.get());
|
||||
stats.put("threatsDetected", threatsDetected.get());
|
||||
stats.put("bufferedSources", logBuffer.size());
|
||||
stats.put("activeRules", patternRules.size());
|
||||
return stats;
|
||||
}
|
||||
|
||||
public boolean enableRule(String ruleName) {
|
||||
PatternRule rule = patternRules.get(ruleName);
|
||||
if (rule == null) return false;
|
||||
rule.enabled = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean disableRule(String ruleName) {
|
||||
PatternRule rule = patternRules.get(ruleName);
|
||||
if (rule == null) return false;
|
||||
rule.enabled = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public PatternRule getRule(String ruleName) {
|
||||
return patternRules.get(ruleName);
|
||||
}
|
||||
|
||||
public Collection<PatternRule> getAllRules() {
|
||||
return patternRules.values();
|
||||
}
|
||||
|
||||
private void startAnalysisTask() {
|
||||
scheduler.scheduleAtFixedRate(() -> {
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
for (Map.Entry<String, List<LogEntry>> entry : logBuffer.entrySet()) {
|
||||
List<LogEntry> buffer = entry.getValue();
|
||||
buffer.removeIf(e -> now - e.timestamp > 3600000);
|
||||
if (buffer.isEmpty()) {
|
||||
logBuffer.remove(entry.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
threatCounts.entrySet().removeIf(e -> e.getValue().get() == 0);
|
||||
|
||||
}, ANALYSIS_INTERVAL, ANALYSIS_INTERVAL, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public static class PatternRule {
|
||||
public final String name;
|
||||
public final String pattern;
|
||||
public final int severity;
|
||||
public volatile boolean enabled;
|
||||
|
||||
private final java.util.regex.Pattern compiledPattern;
|
||||
|
||||
public PatternRule(String name, String pattern, int severity, boolean enabled) {
|
||||
this.name = name;
|
||||
this.pattern = pattern;
|
||||
this.severity = severity;
|
||||
this.enabled = enabled;
|
||||
this.compiledPattern = java.util.regex.Pattern.compile(pattern, java.util.regex.Pattern.CASE_INSENSITIVE);
|
||||
}
|
||||
|
||||
public boolean matches(String input) {
|
||||
return compiledPattern.matcher(input).find();
|
||||
}
|
||||
}
|
||||
|
||||
public static class LogEntry {
|
||||
public final String source;
|
||||
public final String message;
|
||||
public final long timestamp;
|
||||
public Map<String, Object> metadata;
|
||||
|
||||
public LogEntry(String source, String message, long timestamp) {
|
||||
this.source = source;
|
||||
this.message = message;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SecurityEvent {
|
||||
public final String threatType;
|
||||
public final String source;
|
||||
public final int severity;
|
||||
public final String details;
|
||||
public final long timestamp;
|
||||
|
||||
public SecurityEvent(String threatType, String source, int severity, String details, long timestamp) {
|
||||
this.threatType = threatType;
|
||||
this.source = source;
|
||||
this.severity = severity;
|
||||
this.details = details;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,327 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
@Component
|
||||
public class LogCorrelationService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(LogCorrelationService.class);
|
||||
|
||||
private final Map<String, LogSource> logSources = new ConcurrentHashMap<>();
|
||||
private final Map<String, LogEntry> logBuffer = new ConcurrentHashMap<>();
|
||||
private final Queue<CorrelatedEvent> correlatedEvents = new ConcurrentLinkedQueue<>();
|
||||
private final Map<String, CorrelationPattern> patterns = new ConcurrentHashMap<>();
|
||||
private final Map<String, List<LogEntry>> logTimeline = new ConcurrentHashMap<>();
|
||||
private final AtomicLong totalLogsProcessed = new AtomicLong(0);
|
||||
private final AtomicLong correlatedEventsFound = new AtomicLong(0);
|
||||
|
||||
private static final int MAX_BUFFER_SIZE = 50000;
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
public LogCorrelationService() {
|
||||
initializeDefaultPatterns();
|
||||
logger.info("Log Correlation Service initialized");
|
||||
}
|
||||
|
||||
private void initializeDefaultPatterns() {
|
||||
addPattern("SQL_INJECTION", "SQL.*(union|select|insert|update|delete).*", Arrays.asList("SQL_INJECTION_START", "SQL_INJECTION_END"), 300);
|
||||
addPattern("BRUTE_FORCE", "AUTH.*(failed|invalid)", Arrays.asList("LOGIN_FAILED", "LOGIN_FAILED", "LOGIN_FAILED"), 60);
|
||||
addPattern("XSS_ATTACK", "HTTP.*(<script|javascript:)", Arrays.asList("XSS_START", "XSS_PAYLOAD"), 120);
|
||||
addPattern("DATA_EXFILTRATION", "(upload|download).*large", Arrays.asList("LARGE_UPLOAD", "DATA_TRANSFER"), 600);
|
||||
addPattern("PRIVILEGE_ESCALATION", "(sudo|admin|root).*(success|granted)", Arrays.asList("ELEVATED_ACCESS", "PERMISSION_CHANGE"), 300);
|
||||
addPattern("CREDENTIALS_COMPROMISED", "AUTH.*success.*(unusual|unknown)", Arrays.asList("ANOMALY_LOGIN", "CREDENTIAL_CHANGE"), 180);
|
||||
addPattern("DDoS_ATTACK", "NET.*(flood|storm|amplification)", Arrays.asList("HIGH_TRAFFIC", "CONNECTION_LIMIT"), 60);
|
||||
addPattern("MALWARE_INFECTION", "PROC.*(suspicious|malicious|infected)", Arrays.asList("SUSPICIOUS_PROCESS", "MALWARE_DETECTED"), 600);
|
||||
|
||||
logger.info("Initialized {} correlation patterns", patterns.size());
|
||||
}
|
||||
|
||||
public void addPattern(String name, String matchRegex, List<String> sequence, int timeWindow) {
|
||||
CorrelationPattern pattern = new CorrelationPattern(name, matchRegex, sequence, timeWindow);
|
||||
patterns.put(name, pattern);
|
||||
}
|
||||
|
||||
public void addLogSource(String sourceId, String sourceType, String host, int port) {
|
||||
LogSource source = new LogSource(sourceId, sourceType, host, port);
|
||||
logSources.put(sourceId, source);
|
||||
logger.info("Added log source: {} ({})", sourceId, sourceType);
|
||||
}
|
||||
|
||||
public void processLogEntry(String sourceId, String level, String category, String message, Map<String, Object> metadata) {
|
||||
String entryId = UUID.randomUUID().toString();
|
||||
LogEntry entry = new LogEntry(entryId, sourceId, level, category, message, metadata, LocalDateTime.now());
|
||||
|
||||
logBuffer.put(entryId, entry);
|
||||
if (logBuffer.size() > MAX_BUFFER_SIZE) {
|
||||
String firstKey = logBuffer.keySet().iterator().next();
|
||||
logBuffer.remove(firstKey);
|
||||
}
|
||||
|
||||
totalLogsProcessed.incrementAndGet();
|
||||
|
||||
List<LogEntry> timeline = logTimeline.computeIfAbsent(sourceId, k -> new ArrayList<>());
|
||||
timeline.add(entry);
|
||||
if (timeline.size() > 1000) {
|
||||
timeline.remove(0);
|
||||
}
|
||||
|
||||
checkCorrelationPatterns(entry);
|
||||
|
||||
logger.debug("Processed log entry: {} [{}]", category, level);
|
||||
}
|
||||
|
||||
private void checkCorrelationPatterns(LogEntry entry) {
|
||||
for (CorrelationPattern pattern : patterns.values()) {
|
||||
if (matchesPattern(entry, pattern)) {
|
||||
List<LogEntry> sourceTimeline = logTimeline.get(entry.getSourceId());
|
||||
if (sourceTimeline == null) continue;
|
||||
|
||||
List<LogEntry> recentEntries = new ArrayList<>();
|
||||
long cutoffTime = System.currentTimeMillis() - (pattern.getTimeWindow() * 1000);
|
||||
|
||||
for (LogEntry e : sourceTimeline) {
|
||||
if (e.getTimestamp().isAfter(LocalDateTime.now().minusSeconds(pattern.getTimeWindow()))) {
|
||||
recentEntries.add(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (checkSequenceMatch(recentEntries, pattern)) {
|
||||
CorrelatedEvent event = new CorrelatedEvent(
|
||||
pattern.getName(),
|
||||
entry.getSourceId(),
|
||||
recentEntries,
|
||||
"CORRELATION_DETECTED",
|
||||
LocalDateTime.now()
|
||||
);
|
||||
correlatedEvents.offer(event);
|
||||
correlatedEventsFound.incrementAndGet();
|
||||
|
||||
if (correlatedEvents.size() > 1000) {
|
||||
correlatedEvents.poll();
|
||||
}
|
||||
|
||||
logger.warn("Correlation detected: {} from source: {}", pattern.getName(), entry.getSourceId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean matchesPattern(LogEntry entry, CorrelationPattern pattern) {
|
||||
try {
|
||||
return entry.getMessage().matches(pattern.getMatchRegex()) ||
|
||||
entry.getCategory().matches(pattern.getMatchRegex());
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkSequenceMatch(List<LogEntry> entries, CorrelationPattern pattern) {
|
||||
List<String> sequence = pattern.getSequence();
|
||||
if (entries.size() < sequence.size()) return false;
|
||||
|
||||
int matchCount = 0;
|
||||
for (int i = 0; i <= entries.size() - sequence.size(); i++) {
|
||||
boolean allMatch = true;
|
||||
for (int j = 0; j < sequence.size(); j++) {
|
||||
String entryCategory = entries.get(i + j).getCategory();
|
||||
if (!entryCategory.matches(sequence.get(j)) && !entryCategory.contains(sequence.get(j))) {
|
||||
allMatch = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (allMatch) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public List<CorrelatedEvent> queryCorrelatedEvents(String patternName, int limit) {
|
||||
List<CorrelatedEvent> results = new ArrayList<>();
|
||||
for (CorrelatedEvent event : correlatedEvents) {
|
||||
if (patternName == null || event.getPatternName().equals(patternName)) {
|
||||
results.add(event);
|
||||
}
|
||||
if (results.size() >= limit) break;
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
public List<LogEntry> queryLogs(String sourceId, String level, String category, int limit) {
|
||||
List<LogEntry> results = new ArrayList<>();
|
||||
|
||||
for (LogEntry entry : logBuffer.values()) {
|
||||
boolean matches = true;
|
||||
|
||||
if (sourceId != null && !entry.getSourceId().equals(sourceId)) matches = false;
|
||||
if (level != null && !entry.getLevel().equals(level)) matches = false;
|
||||
if (category != null && !entry.getCategory().equals(category)) matches = false;
|
||||
|
||||
if (matches) {
|
||||
results.add(entry);
|
||||
}
|
||||
|
||||
if (results.size() >= limit) break;
|
||||
}
|
||||
|
||||
results.sort((a, b) -> b.getTimestamp().compareTo(a.getTimestamp()));
|
||||
return results;
|
||||
}
|
||||
|
||||
public Map<String, Object> getStats() {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("totalLogsProcessed", totalLogsProcessed.get());
|
||||
stats.put("correlatedEventsFound", correlatedEventsFound.get());
|
||||
stats.put("logSources", logSources.size());
|
||||
stats.put("correlationPatterns", patterns.size());
|
||||
stats.put("bufferSize", logBuffer.size());
|
||||
|
||||
Map<String, Long> categoryCounts = new HashMap<>();
|
||||
for (LogEntry entry : logBuffer.values()) {
|
||||
categoryCounts.merge(entry.getCategory(), 1L, Long::sum);
|
||||
}
|
||||
stats.put("categoryCounts", categoryCounts);
|
||||
|
||||
Map<String, Long> levelCounts = new HashMap<>();
|
||||
for (LogEntry entry : logBuffer.values()) {
|
||||
levelCounts.merge(entry.getLevel(), 1L, Long::sum);
|
||||
}
|
||||
stats.put("levelCounts", levelCounts);
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
public Map<String, LogSource> getLogSources() {
|
||||
return new HashMap<>(logSources);
|
||||
}
|
||||
|
||||
public Map<String, CorrelationPattern> getPatterns() {
|
||||
return new HashMap<>(patterns);
|
||||
}
|
||||
|
||||
public void enablePattern(String patternName) {
|
||||
CorrelationPattern pattern = patterns.get(patternName);
|
||||
if (pattern != null) {
|
||||
pattern.setEnabled(true);
|
||||
logger.info("Enabled correlation pattern: {}", patternName);
|
||||
}
|
||||
}
|
||||
|
||||
public void disablePattern(String patternName) {
|
||||
CorrelationPattern pattern = patterns.get(patternName);
|
||||
if (pattern != null) {
|
||||
pattern.setEnabled(false);
|
||||
logger.info("Disabled correlation pattern: {}", patternName);
|
||||
}
|
||||
}
|
||||
|
||||
public void clearBuffer() {
|
||||
logBuffer.clear();
|
||||
logger.info("Log buffer cleared");
|
||||
}
|
||||
|
||||
public static class LogSource {
|
||||
private final String sourceId;
|
||||
private final String sourceType;
|
||||
private final String host;
|
||||
private final int port;
|
||||
private volatile boolean active = true;
|
||||
private volatile long lastLogTime;
|
||||
|
||||
public LogSource(String sourceId, String sourceType, String host, int port) {
|
||||
this.sourceId = sourceId;
|
||||
this.sourceType = sourceType;
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public String getSourceId() { return sourceId; }
|
||||
public String getSourceType() { return sourceType; }
|
||||
public String getHost() { return host; }
|
||||
public int getPort() { return port; }
|
||||
public boolean isActive() { return active; }
|
||||
public void setActive(boolean active) { this.active = active; }
|
||||
public long getLastLogTime() { return lastLogTime; }
|
||||
public void setLastLogTime(long lastLogTime) { this.lastLogTime = lastLogTime; }
|
||||
}
|
||||
|
||||
public static class LogEntry {
|
||||
private final String entryId;
|
||||
private final String sourceId;
|
||||
private final String level;
|
||||
private final String category;
|
||||
private final String message;
|
||||
private final Map<String, Object> metadata;
|
||||
private final LocalDateTime timestamp;
|
||||
|
||||
public LogEntry(String entryId, String sourceId, String level, String category,
|
||||
String message, Map<String, Object> metadata, LocalDateTime timestamp) {
|
||||
this.entryId = entryId;
|
||||
this.sourceId = sourceId;
|
||||
this.level = level;
|
||||
this.category = category;
|
||||
this.message = message;
|
||||
this.metadata = metadata;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getEntryId() { return entryId; }
|
||||
public String getSourceId() { return sourceId; }
|
||||
public String getLevel() { return level; }
|
||||
public String getCategory() { return category; }
|
||||
public String getMessage() { return message; }
|
||||
public Map<String, Object> getMetadata() { return metadata; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
}
|
||||
|
||||
public static class CorrelationPattern {
|
||||
private final String name;
|
||||
private final String matchRegex;
|
||||
private final List<String> sequence;
|
||||
private final int timeWindow;
|
||||
private volatile boolean enabled = true;
|
||||
|
||||
public CorrelationPattern(String name, String matchRegex, List<String> sequence, int timeWindow) {
|
||||
this.name = name;
|
||||
this.matchRegex = matchRegex;
|
||||
this.sequence = sequence;
|
||||
this.timeWindow = timeWindow;
|
||||
}
|
||||
|
||||
public String getName() { return name; }
|
||||
public String getMatchRegex() { return matchRegex; }
|
||||
public List<String> getSequence() { return sequence; }
|
||||
public int getTimeWindow() { return timeWindow; }
|
||||
public boolean isEnabled() { return enabled; }
|
||||
public void setEnabled(boolean enabled) { this.enabled = enabled; }
|
||||
}
|
||||
|
||||
public static class CorrelatedEvent {
|
||||
private final String patternName;
|
||||
private final String sourceId;
|
||||
private final List<LogEntry> relatedEntries;
|
||||
private final String status;
|
||||
private final LocalDateTime timestamp;
|
||||
|
||||
public CorrelatedEvent(String patternName, String sourceId, List<LogEntry> relatedEntries,
|
||||
String status, LocalDateTime timestamp) {
|
||||
this.patternName = patternName;
|
||||
this.sourceId = sourceId;
|
||||
this.relatedEntries = relatedEntries;
|
||||
this.status = status;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getPatternName() { return patternName; }
|
||||
public String getSourceId() { return sourceId; }
|
||||
public List<LogEntry> getRelatedEntries() { return relatedEntries; }
|
||||
public String getStatus() { return status; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,342 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import com.aluer.config.ServerGuardConfig;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
@Service
|
||||
public class MinecraftProtocolSecurityService {
|
||||
private static final long WINDOW_MS = 60_000L;
|
||||
|
||||
private final ServerGuardConfig config;
|
||||
private final Map<String, MinecraftSessionProfile> profiles = new ConcurrentHashMap<>();
|
||||
private final ConcurrentLinkedQueue<MinecraftThreatSignal> signals = new ConcurrentLinkedQueue<>();
|
||||
|
||||
public MinecraftProtocolSecurityService() {
|
||||
this(new ServerGuardConfig());
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public MinecraftProtocolSecurityService(ServerGuardConfig config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public List<MinecraftThreatSignal> analyzePacket(String sourceIP, String destIP, int sourcePort, int destPort, byte[] payload) {
|
||||
if (!config.getSecurity().getMinecraftDefense().isEnabled()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
if (!isMinecraftRelevantPort(destPort)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
MinecraftSessionProfile profile = profiles.computeIfAbsent(sourceIP, MinecraftSessionProfile::new);
|
||||
profile.destinations.add(destIP);
|
||||
profile.totalPackets++;
|
||||
profile.lastSeen = System.currentTimeMillis();
|
||||
|
||||
String stage = classifyStage(destPort, payload);
|
||||
recordStage(profile, stage);
|
||||
|
||||
List<MinecraftThreatSignal> result = new ArrayList<>();
|
||||
|
||||
if ("STATUS".equals(stage) && profile.statusRequests.size() >= config.getSecurity().getMinecraftDefense().getStatusPingThreshold()) {
|
||||
result.add(createSignal(sourceIP, "STATUS_PING_FLOOD", 88, "Frequent status probes on Minecraft port"));
|
||||
}
|
||||
if ("LOGIN".equals(stage) && profile.loginAttempts.size() >= config.getSecurity().getMinecraftDefense().getLoginBurstThreshold()) {
|
||||
result.add(createSignal(sourceIP, "LOGIN_BURST", 84, "Burst of login attempts detected"));
|
||||
}
|
||||
if ("QUERY".equals(stage) && profile.queryRequests.size() >= config.getSecurity().getMinecraftDefense().getQueryFloodThreshold()) {
|
||||
result.add(createSignal(sourceIP, "QUERY_ABUSE", 80, "Frequent query traffic detected"));
|
||||
}
|
||||
if ("RCON".equals(stage) && profile.rconAttempts.size() >= config.getSecurity().getMinecraftDefense().getRconBruteForceThreshold()) {
|
||||
result.add(createSignal(sourceIP, "RCON_BRUTE_FORCE", 92, "Repeated RCON authentication attempts"));
|
||||
}
|
||||
if (detectInvalidVarint(payload)) {
|
||||
result.add(createSignal(sourceIP, "INVALID_VARINT", 90, "Malformed VarInt header detected"));
|
||||
}
|
||||
if (detectMalformedHandshake(destPort, payload)) {
|
||||
result.add(createSignal(sourceIP, "MALFORMED_HANDSHAKE", 82, "Unexpected or truncated handshake"));
|
||||
}
|
||||
if (detectCompressionAbuse(payload)) {
|
||||
result.add(createSignal(sourceIP, "COMPRESSION_ABUSE", 72, "Large or repetitive payload suggests compression abuse"));
|
||||
}
|
||||
if (detectProtocolStateBypass(profile, stage)) {
|
||||
result.add(createSignal(sourceIP, "PROTOCOL_STATE_BYPASS", 86, "Unexpected protocol stage transition"));
|
||||
}
|
||||
|
||||
if (stage.equals("LOGIN") || stage.equals("STATUS")) {
|
||||
int uniqueSources = countUniqueSourcesForStage(stage);
|
||||
if (uniqueSources >= config.getSecurity().getMinecraftDefense().getBotSwarmThreshold()) {
|
||||
result.add(createSignal(sourceIP, "BOT_SWARM", 78, "Multiple IPs are synchronously probing Minecraft"));
|
||||
}
|
||||
}
|
||||
|
||||
for (MinecraftThreatSignal signal : result) {
|
||||
profile.signalsTriggered.merge(signal.type, 1, Integer::sum);
|
||||
signals.offer(signal);
|
||||
}
|
||||
|
||||
while (signals.size() > 1000) {
|
||||
signals.poll();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<MinecraftThreatSignal> getRecentSignals(int limit) {
|
||||
List<MinecraftThreatSignal> result = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (MinecraftThreatSignal signal : signals) {
|
||||
if (count++ >= limit) {
|
||||
break;
|
||||
}
|
||||
result.add(signal);
|
||||
}
|
||||
result.sort(Comparator.comparingLong(MinecraftThreatSignal::getTimestamp).reversed());
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<MinecraftThreatSignal> getSignalsForIp(String ip, int limit) {
|
||||
List<MinecraftThreatSignal> result = new ArrayList<>();
|
||||
for (MinecraftThreatSignal signal : getRecentSignals(200)) {
|
||||
if (ip.equals(signal.sourceIP)) {
|
||||
result.add(signal);
|
||||
if (result.size() >= limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public Map<String, Object> getPosture() {
|
||||
Map<String, Object> posture = new LinkedHashMap<>();
|
||||
posture.put("activeSessions", profiles.size());
|
||||
posture.put("recentSignals", signals.size());
|
||||
posture.put("botSwarmSources", countSignals("BOT_SWARM"));
|
||||
posture.put("rconRisk", countSignals("RCON_BRUTE_FORCE"));
|
||||
posture.put("malformedPackets", countSignals("INVALID_VARINT") + countSignals("MALFORMED_HANDSHAKE"));
|
||||
posture.put("topSources", getTopProfiles(5));
|
||||
return posture;
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> getSessionProfiles(int limit) {
|
||||
return getTopProfiles(limit);
|
||||
}
|
||||
|
||||
private List<Map<String, Object>> getTopProfiles(int limit) {
|
||||
List<MinecraftSessionProfile> sessionProfiles = new ArrayList<>(profiles.values());
|
||||
sessionProfiles.sort(Comparator.comparingLong((MinecraftSessionProfile profile) -> profile.totalPackets).reversed());
|
||||
|
||||
List<Map<String, Object>> result = new ArrayList<>();
|
||||
for (MinecraftSessionProfile profile : sessionProfiles) {
|
||||
if (result.size() >= limit) {
|
||||
break;
|
||||
}
|
||||
result.add(profile.toMap());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private int countSignals(String type) {
|
||||
int count = 0;
|
||||
for (MinecraftThreatSignal signal : signals) {
|
||||
if (type.equals(signal.type)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private int countUniqueSourcesForStage(String stage) {
|
||||
Set<String> sources = new HashSet<>();
|
||||
long cutoff = System.currentTimeMillis() - WINDOW_MS;
|
||||
for (MinecraftSessionProfile profile : profiles.values()) {
|
||||
List<Long> timestamps = switch (stage) {
|
||||
case "STATUS" -> profile.statusRequests;
|
||||
case "LOGIN" -> profile.loginAttempts;
|
||||
default -> List.of();
|
||||
};
|
||||
timestamps.removeIf(timestamp -> timestamp < cutoff);
|
||||
if (!timestamps.isEmpty()) {
|
||||
sources.add(profile.sourceIP);
|
||||
}
|
||||
}
|
||||
return sources.size();
|
||||
}
|
||||
|
||||
private boolean detectInvalidVarint(byte[] payload) {
|
||||
if (payload == null || payload.length == 0) {
|
||||
return false;
|
||||
}
|
||||
int continuation = 0;
|
||||
for (int i = 0; i < Math.min(6, payload.length); i++) {
|
||||
if ((payload[i] & 0x80) != 0) {
|
||||
continuation++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return continuation > 4;
|
||||
}
|
||||
|
||||
private boolean detectMalformedHandshake(int destPort, byte[] payload) {
|
||||
if (payload == null) {
|
||||
return true;
|
||||
}
|
||||
if (destPort == config.getSecurity().getMinecraftDefense().getGameTcpPort() && payload.length < 2) {
|
||||
return true;
|
||||
}
|
||||
String content = payload.length == 0 ? "" : new String(payload, StandardCharsets.UTF_8).toLowerCase(Locale.ROOT);
|
||||
return content.contains("handshake") && payload.length < 8;
|
||||
}
|
||||
|
||||
private boolean detectCompressionAbuse(byte[] payload) {
|
||||
if (payload == null || payload.length < config.getSecurity().getMinecraftDefense().getCompressionPayloadThreshold()) {
|
||||
return false;
|
||||
}
|
||||
int repeated = 0;
|
||||
for (int i = 1; i < payload.length; i++) {
|
||||
if (payload[i] == payload[i - 1]) {
|
||||
repeated++;
|
||||
}
|
||||
}
|
||||
return repeated > payload.length / 3;
|
||||
}
|
||||
|
||||
private boolean detectProtocolStateBypass(MinecraftSessionProfile profile, String stage) {
|
||||
if (profile.lastStage == null || stage.equals(profile.lastStage)) {
|
||||
return false;
|
||||
}
|
||||
if ("LOGIN".equals(stage) && !"HANDSHAKE".equals(profile.lastStage)) {
|
||||
return true;
|
||||
}
|
||||
if ("QUERY".equals(stage) && "RCON".equals(profile.lastStage)) {
|
||||
return true;
|
||||
}
|
||||
return "RCON".equals(stage) && "STATUS".equals(profile.lastStage);
|
||||
}
|
||||
|
||||
private String classifyStage(int destPort, byte[] payload) {
|
||||
if (destPort == config.getSecurity().getMinecraftDefense().getRconTcpPort()) {
|
||||
return "RCON";
|
||||
}
|
||||
if (destPort == config.getSecurity().getMinecraftDefense().getQueryUdpPort() && payload != null && payload.length > 0 && payload[0] == (byte) 0xFE) {
|
||||
return "QUERY";
|
||||
}
|
||||
|
||||
String content = payload == null ? "" : new String(payload, StandardCharsets.UTF_8).toLowerCase(Locale.ROOT);
|
||||
if (content.contains("status") || content.contains("ping")) {
|
||||
return "STATUS";
|
||||
}
|
||||
if (content.contains("login") || content.contains("join")) {
|
||||
return "LOGIN";
|
||||
}
|
||||
if (content.contains("handshake")) {
|
||||
return "HANDSHAKE";
|
||||
}
|
||||
if (content.contains("query")) {
|
||||
return "QUERY";
|
||||
}
|
||||
return destPort == config.getSecurity().getMinecraftDefense().getGameTcpPort() ? "HANDSHAKE" : "UNKNOWN";
|
||||
}
|
||||
|
||||
private void recordStage(MinecraftSessionProfile profile, String stage) {
|
||||
long now = System.currentTimeMillis();
|
||||
profile.lastStage = stage;
|
||||
switch (stage) {
|
||||
case "STATUS" -> pruneAndAdd(profile.statusRequests, now);
|
||||
case "LOGIN" -> pruneAndAdd(profile.loginAttempts, now);
|
||||
case "QUERY" -> pruneAndAdd(profile.queryRequests, now);
|
||||
case "RCON" -> pruneAndAdd(profile.rconAttempts, now);
|
||||
default -> pruneAndAdd(profile.handshakes, now);
|
||||
}
|
||||
}
|
||||
|
||||
private void pruneAndAdd(List<Long> timestamps, long timestamp) {
|
||||
timestamps.removeIf(existing -> existing < timestamp - WINDOW_MS);
|
||||
timestamps.add(timestamp);
|
||||
}
|
||||
|
||||
private MinecraftThreatSignal createSignal(String sourceIP, String type, int confidence, String details) {
|
||||
return new MinecraftThreatSignal(sourceIP, type, confidence, details, Instant.now().toEpochMilli());
|
||||
}
|
||||
|
||||
private boolean isMinecraftRelevantPort(int port) {
|
||||
ServerGuardConfig.MinecraftDefenseConfig defense = config.getSecurity().getMinecraftDefense();
|
||||
return port == defense.getGameTcpPort()
|
||||
|| port == defense.getQueryUdpPort()
|
||||
|| port == defense.getRconTcpPort();
|
||||
}
|
||||
|
||||
public static class MinecraftThreatSignal {
|
||||
private final String sourceIP;
|
||||
private final String type;
|
||||
private final int confidence;
|
||||
private final String details;
|
||||
private final long timestamp;
|
||||
|
||||
public MinecraftThreatSignal(String sourceIP, String type, int confidence, String details, long timestamp) {
|
||||
this.sourceIP = sourceIP;
|
||||
this.type = type;
|
||||
this.confidence = confidence;
|
||||
this.details = details;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getSourceIP() { return sourceIP; }
|
||||
public String getType() { return type; }
|
||||
public int getConfidence() { return confidence; }
|
||||
public String getDetails() { return details; }
|
||||
public long getTimestamp() { return timestamp; }
|
||||
}
|
||||
|
||||
public static class MinecraftSessionProfile {
|
||||
private final String sourceIP;
|
||||
private final Set<String> destinations = ConcurrentHashMap.newKeySet();
|
||||
private final List<Long> statusRequests = Collections.synchronizedList(new ArrayList<>());
|
||||
private final List<Long> loginAttempts = Collections.synchronizedList(new ArrayList<>());
|
||||
private final List<Long> queryRequests = Collections.synchronizedList(new ArrayList<>());
|
||||
private final List<Long> rconAttempts = Collections.synchronizedList(new ArrayList<>());
|
||||
private final List<Long> handshakes = Collections.synchronizedList(new ArrayList<>());
|
||||
private final Map<String, Integer> signalsTriggered = new ConcurrentHashMap<>();
|
||||
private volatile long totalPackets = 0;
|
||||
private volatile long lastSeen = System.currentTimeMillis();
|
||||
private volatile String lastStage = null;
|
||||
|
||||
public MinecraftSessionProfile(String sourceIP) {
|
||||
this.sourceIP = sourceIP;
|
||||
}
|
||||
|
||||
public Map<String, Object> toMap() {
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
data.put("sourceIP", sourceIP);
|
||||
data.put("totalPackets", totalPackets);
|
||||
data.put("destinations", destinations.size());
|
||||
data.put("statusRequests", statusRequests.size());
|
||||
data.put("loginAttempts", loginAttempts.size());
|
||||
data.put("queryRequests", queryRequests.size());
|
||||
data.put("rconAttempts", rconAttempts.size());
|
||||
data.put("signalsTriggered", signalsTriggered);
|
||||
data.put("lastStage", lastStage);
|
||||
data.put("lastSeen", lastSeen);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.*;
|
||||
|
||||
@Service
|
||||
public class NetworkMonitorService {
|
||||
|
||||
private final Map<String, NetworkSession> sessions = new ConcurrentHashMap<>();
|
||||
private final Map<String, BandwidthTracker> bandwidthTrackers = new ConcurrentHashMap<>();
|
||||
private final Queue<NetworkAlert> alerts = new ConcurrentLinkedQueue<>();
|
||||
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
|
||||
|
||||
private static final long SESSION_TIMEOUT = 300000;
|
||||
private static final long BANDWIDTH_UPDATE_INTERVAL = 1000;
|
||||
|
||||
private final AtomicLong totalBytesIn = new AtomicLong(0);
|
||||
private final AtomicLong totalBytesOut = new AtomicLong(0);
|
||||
private final AtomicLong totalPacketsIn = new AtomicLong(0);
|
||||
private final AtomicLong totalPacketsOut = new AtomicLong(0);
|
||||
|
||||
public NetworkMonitorService() {
|
||||
startMonitoringTask();
|
||||
}
|
||||
|
||||
public void recordInboundTraffic(String ip, long bytes, int packets) {
|
||||
totalBytesIn.addAndGet(bytes);
|
||||
totalPacketsIn.addAndGet(packets);
|
||||
|
||||
BandwidthTracker tracker = bandwidthTrackers.computeIfAbsent(ip, k -> new BandwidthTracker(ip));
|
||||
tracker.recordInbound(bytes, packets);
|
||||
|
||||
NetworkSession session = sessions.computeIfAbsent(ip, k -> new NetworkSession(ip));
|
||||
session.recordInbound(bytes, packets);
|
||||
}
|
||||
|
||||
public void recordOutboundTraffic(String ip, long bytes, int packets) {
|
||||
totalBytesOut.addAndGet(bytes);
|
||||
totalPacketsOut.addAndGet(packets);
|
||||
|
||||
BandwidthTracker tracker = bandwidthTrackers.computeIfAbsent(ip, k -> new BandwidthTracker(ip));
|
||||
tracker.recordOutbound(bytes, packets);
|
||||
|
||||
NetworkSession session = sessions.computeIfAbsent(ip, k -> new NetworkSession(ip));
|
||||
session.recordOutbound(bytes, packets);
|
||||
}
|
||||
|
||||
public void recordConnection(String ip, int port, String protocol) {
|
||||
NetworkSession session = sessions.computeIfAbsent(ip, k -> new NetworkSession(ip));
|
||||
session.addConnection(port, protocol);
|
||||
}
|
||||
|
||||
public void closeConnection(String ip, int port) {
|
||||
NetworkSession session = sessions.get(ip);
|
||||
if (session != null) {
|
||||
session.removeConnection(port);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isHighBandwidth(String ip) {
|
||||
BandwidthTracker tracker = bandwidthTrackers.get(ip);
|
||||
if (tracker == null) {
|
||||
return false;
|
||||
}
|
||||
return tracker.isHighBandwidth();
|
||||
}
|
||||
|
||||
public boolean isExcessiveConnections(String ip) {
|
||||
NetworkSession session = sessions.get(ip);
|
||||
if (session == null) {
|
||||
return false;
|
||||
}
|
||||
return session.getConnectionCount() > 100;
|
||||
}
|
||||
|
||||
public NetworkSession getSession(String ip) {
|
||||
return sessions.get(ip);
|
||||
}
|
||||
|
||||
public Collection<NetworkSession> getAllSessions() {
|
||||
return sessions.values();
|
||||
}
|
||||
|
||||
public BandwidthTracker getBandwidthTracker(String ip) {
|
||||
return bandwidthTrackers.get(ip);
|
||||
}
|
||||
|
||||
public void addAlert(String ip, String type, String details) {
|
||||
NetworkAlert alert = new NetworkAlert(ip, type, details, System.currentTimeMillis());
|
||||
alerts.offer(alert);
|
||||
|
||||
while (alerts.size() > 500) {
|
||||
alerts.poll();
|
||||
}
|
||||
}
|
||||
|
||||
public List<NetworkAlert> getAlerts(int limit) {
|
||||
List<NetworkAlert> result = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (NetworkAlert alert : alerts) {
|
||||
if (count++ >= limit) break;
|
||||
result.add(alert);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public Map<String, Object> getGlobalStats() {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("activeSessions", sessions.size());
|
||||
stats.put("totalBytesIn", totalBytesIn.get());
|
||||
stats.put("totalBytesOut", totalBytesOut.get());
|
||||
stats.put("totalPacketsIn", totalPacketsIn.get());
|
||||
stats.put("totalPacketsOut", totalPacketsOut.get());
|
||||
return stats;
|
||||
}
|
||||
|
||||
private void startMonitoringTask() {
|
||||
scheduler.scheduleAtFixedRate(() -> {
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
sessions.entrySet().removeIf(entry ->
|
||||
now - entry.getValue().lastActivity > SESSION_TIMEOUT);
|
||||
|
||||
for (BandwidthTracker tracker : bandwidthTrackers.values()) {
|
||||
tracker.resetIfNeeded();
|
||||
}
|
||||
|
||||
}, 30, 30, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public static class NetworkSession {
|
||||
private final String ip;
|
||||
private final Set<Integer> connections = ConcurrentHashMap.newKeySet();
|
||||
private volatile long totalBytesIn = 0;
|
||||
private volatile long totalBytesOut = 0;
|
||||
private volatile long totalPacketsIn = 0;
|
||||
private volatile long totalPacketsOut = 0;
|
||||
private volatile long lastActivity;
|
||||
|
||||
public NetworkSession(String ip) {
|
||||
this.ip = ip;
|
||||
this.lastActivity = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void recordInbound(long bytes, int packets) {
|
||||
totalBytesIn += bytes;
|
||||
totalPacketsIn += packets;
|
||||
lastActivity = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void recordOutbound(long bytes, int packets) {
|
||||
totalBytesOut += bytes;
|
||||
totalPacketsOut += packets;
|
||||
lastActivity = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void addConnection(int port, String protocol) {
|
||||
connections.add(port);
|
||||
lastActivity = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void removeConnection(int port) {
|
||||
connections.remove(port);
|
||||
}
|
||||
|
||||
public int getConnectionCount() {
|
||||
return connections.size();
|
||||
}
|
||||
|
||||
public String getIp() { return ip; }
|
||||
public long getTotalBytesIn() { return totalBytesIn; }
|
||||
public long getTotalBytesOut() { return totalBytesOut; }
|
||||
}
|
||||
|
||||
public static class BandwidthTracker {
|
||||
private final String ip;
|
||||
private volatile long windowBytesIn = 0;
|
||||
private volatile long windowBytesOut = 0;
|
||||
private volatile long windowPacketsIn = 0;
|
||||
private volatile long windowPacketsOut = 0;
|
||||
private volatile long lastUpdate;
|
||||
|
||||
public BandwidthTracker(String ip) {
|
||||
this.ip = ip;
|
||||
this.lastUpdate = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void recordInbound(long bytes, int packets) {
|
||||
windowBytesIn += bytes;
|
||||
windowPacketsIn += packets;
|
||||
}
|
||||
|
||||
public void recordOutbound(long bytes, int packets) {
|
||||
windowBytesOut += bytes;
|
||||
windowPacketsOut += packets;
|
||||
}
|
||||
|
||||
public void resetIfNeeded() {
|
||||
long now = System.currentTimeMillis();
|
||||
if (now - lastUpdate > BANDWIDTH_UPDATE_INTERVAL) {
|
||||
windowBytesIn = 0;
|
||||
windowBytesOut = 0;
|
||||
windowPacketsIn = 0;
|
||||
windowPacketsOut = 0;
|
||||
lastUpdate = now;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isHighBandwidth() {
|
||||
return windowBytesIn > 10_000_000 || windowBytesOut > 10_000_000;
|
||||
}
|
||||
|
||||
public String getIp() { return ip; }
|
||||
public long getWindowBytesIn() { return windowBytesIn; }
|
||||
public long getWindowBytesOut() { return windowBytesOut; }
|
||||
}
|
||||
|
||||
public static class NetworkAlert {
|
||||
private final String ip;
|
||||
private final String type;
|
||||
private final String details;
|
||||
private final long timestamp;
|
||||
|
||||
public NetworkAlert(String ip, String type, String details, long timestamp) {
|
||||
this.ip = ip;
|
||||
this.type = type;
|
||||
this.details = details;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getIp() { return ip; }
|
||||
public String getType() { return type; }
|
||||
public String getDetails() { return details; }
|
||||
public long getTimestamp() { return timestamp; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
@Component
|
||||
public class NetworkSegmentationService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(NetworkSegmentationService.class);
|
||||
|
||||
private final Map<String, NetworkSegment> segments = new ConcurrentHashMap<>();
|
||||
private final Map<String, SegmentPolicy> policies = new ConcurrentHashMap<>();
|
||||
private final Map<String, List<String>> segmentConnections = new ConcurrentHashMap<>();
|
||||
private final Queue<SegmentationEvent> eventLog = new ConcurrentLinkedQueue<>();
|
||||
private final AtomicLong totalSegments = new AtomicLong(0);
|
||||
private final AtomicLong violationsDetected = new AtomicLong(0);
|
||||
|
||||
private volatile boolean enabled = true;
|
||||
private static final int MAX_EVENTS = 5000;
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
public NetworkSegmentationService() {
|
||||
initializeDefaultSegments();
|
||||
initializeDefaultPolicies();
|
||||
logger.info("Network Segmentation Service initialized");
|
||||
}
|
||||
|
||||
private void initializeDefaultSegments() {
|
||||
addSegment("DMZ", "Demilitarized zone for public-facing services", "10.0.1.0/24");
|
||||
addSegment("APPLICATION", "Application servers and services", "10.0.2.0/24");
|
||||
addSegment("DATABASE", "Database servers and storage", "10.0.3.0/24");
|
||||
addSegment("MANAGEMENT", "Management and administrative systems", "10.0.4.0/24");
|
||||
addSegment("GUEST", "Guest and visitor networks", "10.0.5.0/24");
|
||||
addSegment("IOT", "IoT devices and sensors", "10.0.6.0/24");
|
||||
addSegment("SECURE", "Highly sensitive data and systems", "10.0.7.0/24");
|
||||
|
||||
addConnection("DMZ", "APPLICATION");
|
||||
addConnection("APPLICATION", "DATABASE");
|
||||
addConnection("APPLICATION", "MANAGEMENT");
|
||||
addConnection("MANAGEMENT", "DATABASE");
|
||||
addConnection("DMZ", "GUEST", false);
|
||||
addConnection("IOT", "APPLICATION");
|
||||
|
||||
logger.info("Initialized {} network segments", segments.size());
|
||||
}
|
||||
|
||||
private void initializeDefaultPolicies() {
|
||||
addPolicy("DENY_ALL", "DENY", "Default deny all traffic between segments", 0);
|
||||
addPolicy("ALLOW_DMZ_TO_APP", "ALLOW", "Allow DMZ to Application traffic", 100);
|
||||
addPolicy("ALLOW_APP_TO_DB", "ALLOW", "Allow Application to Database", 100);
|
||||
addPolicy("ALLOW_MGMT_TO_ALL", "ALLOW", "Allow Management to all segments", 90);
|
||||
addPolicy("RESTRICT_GUEST", "RESTRICT", "Restrict guest network", 50);
|
||||
addPolicy("RESTRICT_IOT", "RESTRICT", "Restrict IoT devices", 60);
|
||||
addPolicy("AUDIT_SECURE", "AUDIT", "Audit all traffic to Secure segment", 80);
|
||||
|
||||
logger.info("Initialized {} segmentation policies", policies.size());
|
||||
}
|
||||
|
||||
public void addSegment(String name, String description, String cidr) {
|
||||
NetworkSegment segment = new NetworkSegment(name, description, cidr);
|
||||
segments.put(name, segment);
|
||||
segmentConnections.put(name, new ArrayList<>());
|
||||
totalSegments.incrementAndGet();
|
||||
logger.info("Added network segment: {} ({})", name, cidr);
|
||||
}
|
||||
|
||||
public void addConnection(String from, String to) {
|
||||
addConnection(from, to, true);
|
||||
}
|
||||
|
||||
public void addConnection(String from, String to, boolean allowed) {
|
||||
List<String> fromConnections = segmentConnections.computeIfAbsent(from, k -> new ArrayList<>());
|
||||
if (!fromConnections.contains(to)) {
|
||||
fromConnections.add(to);
|
||||
}
|
||||
|
||||
if (allowed) {
|
||||
logEvent(from, to, "CONNECTION_ADDED", "Allowed connection added");
|
||||
} else {
|
||||
logEvent(from, to, "RESTRICTED_CONNECTION", "Restricted connection added");
|
||||
}
|
||||
|
||||
logger.info("Added connection from {} to {}", from, to);
|
||||
}
|
||||
|
||||
public void removeConnection(String from, String to) {
|
||||
List<String> fromConnections = segmentConnections.get(from);
|
||||
if (fromConnections != null) {
|
||||
fromConnections.remove(to);
|
||||
}
|
||||
logEvent(from, to, "CONNECTION_REMOVED", "Connection removed");
|
||||
logger.info("Removed connection from {} to {}", from, to);
|
||||
}
|
||||
|
||||
public void addPolicy(String name, String type, String description, int priority) {
|
||||
SegmentPolicy policy = new SegmentPolicy(name, type, description, priority);
|
||||
policies.put(name, policy);
|
||||
}
|
||||
|
||||
public boolean checkConnection(String sourceSegment, String destSegment) {
|
||||
List<String> allowedDestinations = segmentConnections.get(sourceSegment);
|
||||
|
||||
if (allowedDestinations == null || allowedDestinations.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (allowedDestinations.contains(destSegment)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
SegmentPolicy policy = findMatchingPolicy(sourceSegment, destSegment);
|
||||
if (policy != null) {
|
||||
if ("DENY".equals(policy.getType())) {
|
||||
violationsDetected.incrementAndGet();
|
||||
logEvent(sourceSegment, destSegment, "VIOLATION", "Denied by policy: " + policy.getName());
|
||||
return false;
|
||||
}
|
||||
if ("ALLOW".equals(policy.getType())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private SegmentPolicy findMatchingPolicy(String source, String dest) {
|
||||
SegmentPolicy matched = null;
|
||||
int highestPriority = -1;
|
||||
|
||||
for (SegmentPolicy policy : policies.values()) {
|
||||
if (matchesPolicy(policy, source, dest) && policy.getPriority() > highestPriority) {
|
||||
matched = policy;
|
||||
highestPriority = policy.getPriority();
|
||||
}
|
||||
}
|
||||
|
||||
return matched;
|
||||
}
|
||||
|
||||
private boolean matchesPolicy(SegmentPolicy policy, String source, String dest) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void enforceMicrosegmentation(String segmentName) {
|
||||
NetworkSegment segment = segments.get(segmentName);
|
||||
if (segment == null) return;
|
||||
|
||||
segment.setMicrosegmentationEnabled(true);
|
||||
|
||||
List<String> connections = segmentConnections.get(segmentName);
|
||||
if (connections != null) {
|
||||
for (String dest : new ArrayList<>(connections)) {
|
||||
connections.clear();
|
||||
connections.add(dest);
|
||||
}
|
||||
}
|
||||
|
||||
logEvent(segmentName, "ALL", "MICROSEGMENTATION", "Microsegmentation enforced");
|
||||
logger.info("Microsegmentation enforced for segment: {}", segmentName);
|
||||
}
|
||||
|
||||
public void monitorTraffic(String sourceIP, String destIP) {
|
||||
String sourceSegment = identifySegment(sourceIP);
|
||||
String destSegment = identifySegment(destIP);
|
||||
|
||||
if (sourceSegment == null || destSegment == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!checkConnection(sourceSegment, destSegment)) {
|
||||
violationsDetected.incrementAndGet();
|
||||
logEvent(sourceSegment, destSegment, "UNAUTHORIZED", "Unauthorized traffic detected: " + sourceIP + " -> " + destIP);
|
||||
logger.warn("Unauthorized traffic: {} ({}) -> {} ({})", sourceIP, sourceSegment, destIP, destSegment);
|
||||
}
|
||||
}
|
||||
|
||||
private String identifySegment(String ip) {
|
||||
for (NetworkSegment segment : segments.values()) {
|
||||
if (ip.startsWith(segment.getCidr().replace("/24", "").replace("10.0.", "10.0.").split("\\.")[0] + ".")) {
|
||||
return segment.getName();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Map<String, Object> getStats() {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("enabled", enabled);
|
||||
stats.put("totalSegments", totalSegments.get());
|
||||
stats.put("violationsDetected", violationsDetected.get());
|
||||
stats.put("segmentsConfigured", segments.size());
|
||||
stats.put("policiesConfigured", policies.size());
|
||||
|
||||
Map<String, Integer> connectionCounts = new HashMap<>();
|
||||
for (Map.Entry<String, List<String>> entry : segmentConnections.entrySet()) {
|
||||
connectionCounts.put(entry.getKey(), entry.getValue().size());
|
||||
}
|
||||
stats.put("connectionCounts", connectionCounts);
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
public Map<String, NetworkSegment> getSegments() {
|
||||
return new HashMap<>(segments);
|
||||
}
|
||||
|
||||
public Map<String, SegmentPolicy> getPolicies() {
|
||||
return new HashMap<>(policies);
|
||||
}
|
||||
|
||||
public Map<String, List<String>> getConnections() {
|
||||
return new HashMap<>(segmentConnections);
|
||||
}
|
||||
|
||||
public List<SegmentationEvent> getRecentEvents(int limit) {
|
||||
List<SegmentationEvent> events = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (SegmentationEvent event : eventLog) {
|
||||
if (count++ >= limit) break;
|
||||
events.add(event);
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
private void logEvent(String source, String dest, String action, String details) {
|
||||
SegmentationEvent event = new SegmentationEvent(source, dest, action, details, LocalDateTime.now());
|
||||
eventLog.offer(event);
|
||||
if (eventLog.size() > MAX_EVENTS) {
|
||||
eventLog.poll();
|
||||
}
|
||||
}
|
||||
|
||||
public void enable() {
|
||||
enabled = true;
|
||||
logger.info("Network segmentation enabled");
|
||||
}
|
||||
|
||||
public void disable() {
|
||||
enabled = false;
|
||||
logger.info("Network segmentation disabled");
|
||||
}
|
||||
|
||||
public static class NetworkSegment {
|
||||
private final String name;
|
||||
private final String description;
|
||||
private final String cidr;
|
||||
private volatile boolean microsegmentationEnabled = false;
|
||||
|
||||
public NetworkSegment(String name, String description, String cidr) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.cidr = cidr;
|
||||
}
|
||||
|
||||
public String getName() { return name; }
|
||||
public String getDescription() { return description; }
|
||||
public String getCidr() { return cidr; }
|
||||
public boolean isMicrosegmentationEnabled() { return microsegmentationEnabled; }
|
||||
public void setMicrosegmentationEnabled(boolean enabled) { this.microsegmentationEnabled = enabled; }
|
||||
}
|
||||
|
||||
public static class SegmentPolicy {
|
||||
private final String name;
|
||||
private final String type;
|
||||
private final String description;
|
||||
private final int priority;
|
||||
|
||||
public SegmentPolicy(String name, String type, String description, int priority) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.description = description;
|
||||
this.priority = priority;
|
||||
}
|
||||
|
||||
public String getName() { return name; }
|
||||
public String getType() { return type; }
|
||||
public String getDescription() { return description; }
|
||||
public int getPriority() { return priority; }
|
||||
}
|
||||
|
||||
public static class SegmentationEvent {
|
||||
private final String source;
|
||||
private final String destination;
|
||||
private final String action;
|
||||
private final String details;
|
||||
private final LocalDateTime timestamp;
|
||||
|
||||
public SegmentationEvent(String source, String destination, String action, String details, LocalDateTime timestamp) {
|
||||
this.source = source;
|
||||
this.destination = destination;
|
||||
this.action = action;
|
||||
this.details = details;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getSource() { return source; }
|
||||
public String getDestination() { return destination; }
|
||||
public String getAction() { return action; }
|
||||
public String getDetails() { return details; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,388 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
@Component
|
||||
public class NetworkSnifferService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(NetworkSnifferService.class);
|
||||
|
||||
private final Map<String, CapturedPacket> packetBuffer = new LinkedHashMap<>();
|
||||
private final Queue<SnifferAlert> alertQueue = new ConcurrentLinkedQueue<>();
|
||||
private final Map<String, PacketStatistics> protocolStats = new ConcurrentHashMap<>();
|
||||
private final Map<String, java.util.List<CapturedPacket>> packetHistory = new java.util.concurrent.ConcurrentHashMap<>();
|
||||
private final Map<String, SessionInfo> sessions = new ConcurrentHashMap<>();
|
||||
private final AtomicLong totalPacketsCaptured = new AtomicLong(0);
|
||||
private final AtomicLong totalBytesCaptured = new AtomicLong(0);
|
||||
private final AtomicLong suspiciousPackets = new AtomicLong(0);
|
||||
|
||||
private volatile boolean capturing = false;
|
||||
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
|
||||
|
||||
private static final int MAX_BUFFER_SIZE = 10000;
|
||||
private static final int PACKET_HISTORY_SIZE = 1000;
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
public NetworkSnifferService() {
|
||||
initializeProtocolStats();
|
||||
logger.info("Network Sniffer Service initialized");
|
||||
}
|
||||
|
||||
private void initializeProtocolStats() {
|
||||
protocolStats.put("TCP", new PacketStatistics("TCP"));
|
||||
protocolStats.put("UDP", new PacketStatistics("UDP"));
|
||||
protocolStats.put("ICMP", new PacketStatistics("ICMP"));
|
||||
protocolStats.put("HTTP", new PacketStatistics("HTTP"));
|
||||
protocolStats.put("HTTPS", new PacketStatistics("HTTPS"));
|
||||
protocolStats.put("DNS", new PacketStatistics("DNS"));
|
||||
protocolStats.put("SSH", new PacketStatistics("SSH"));
|
||||
protocolStats.put("FTP", new PacketStatistics("FTP"));
|
||||
protocolStats.put("OTHER", new PacketStatistics("OTHER"));
|
||||
}
|
||||
|
||||
public void startCapture() {
|
||||
capturing = true;
|
||||
logger.info("Packet capture started");
|
||||
|
||||
scheduler.scheduleAtFixedRate(() -> {
|
||||
analyzeCapturedTraffic();
|
||||
detectAnomalies();
|
||||
cleanupOldData();
|
||||
}, 5, 5, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public void stopCapture() {
|
||||
capturing = false;
|
||||
logger.info("Packet capture stopped");
|
||||
}
|
||||
|
||||
public void capturePacket(String sourceIP, String destIP, int sourcePort, int destPort,
|
||||
String protocol, byte[] payload, int length) {
|
||||
if (!capturing) return;
|
||||
|
||||
totalPacketsCaptured.incrementAndGet();
|
||||
totalBytesCaptured.addAndGet(length);
|
||||
|
||||
String packetId = UUID.randomUUID().toString();
|
||||
CapturedPacket packet = new CapturedPacket(
|
||||
packetId, sourceIP, destIP, sourcePort, destPort,
|
||||
protocol, payload, length, LocalDateTime.now()
|
||||
);
|
||||
|
||||
packetBuffer.put(packetId, packet);
|
||||
if (packetBuffer.size() > MAX_BUFFER_SIZE) {
|
||||
String firstKey = packetBuffer.keySet().iterator().next();
|
||||
packetBuffer.remove(firstKey);
|
||||
}
|
||||
|
||||
String sessionKey = sourceIP + "-" + destIP + "-" + destPort;
|
||||
SessionInfo session = sessions.computeIfAbsent(sessionKey, k -> new SessionInfo(sourceIP, destIP, destPort));
|
||||
session.addPacket(packet);
|
||||
|
||||
updateProtocolStats(protocol, length);
|
||||
updatePacketHistory(sourceIP, packet);
|
||||
|
||||
if (isSuspiciousPacket(packet)) {
|
||||
suspiciousPackets.incrementAndGet();
|
||||
triggerAlert(packet, "Suspicious packet detected");
|
||||
}
|
||||
|
||||
analyzePacketContent(packet);
|
||||
}
|
||||
|
||||
private void updateProtocolStats(String protocol, int length) {
|
||||
PacketStatistics stats = protocolStats.computeIfAbsent(protocol, k -> new PacketStatistics(protocol));
|
||||
stats.incrementPacketCount();
|
||||
stats.addBytes(length);
|
||||
}
|
||||
|
||||
private void updatePacketHistory(String ip, CapturedPacket packet) {
|
||||
List<CapturedPacket> history = packetHistory.computeIfAbsent(ip, k -> new ArrayList<>());
|
||||
history.add(packet);
|
||||
if (history.size() > PACKET_HISTORY_SIZE) {
|
||||
history.remove(0);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSuspiciousPacket(CapturedPacket packet) {
|
||||
if (packet.getPayload() != null && packet.getPayload().length > 10000) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (packet.getPayload() != null) {
|
||||
String payloadStr = new String(packet.getPayload());
|
||||
if (payloadStr.contains("NULL") || payloadStr.contains("\\x00")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void analyzePacketContent(CapturedPacket packet) {
|
||||
if (packet.getPayload() == null || packet.getPayload().length == 0) return;
|
||||
|
||||
String payloadStr = new String(packet.getPayload());
|
||||
|
||||
if (payloadStr.matches(".*(?i)(union|select|insert|update|delete).*")) {
|
||||
triggerAlert(packet, "SQL Injection attempt detected");
|
||||
}
|
||||
|
||||
if (payloadStr.matches(".*(?i)(<script|javascript:|onerror=).*")) {
|
||||
triggerAlert(packet, "XSS attempt detected");
|
||||
}
|
||||
|
||||
if (payloadStr.matches(".*(?i)(\\.\\./|\\.\\.\\\\).*")) {
|
||||
triggerAlert(packet, "Directory traversal attempt detected");
|
||||
}
|
||||
|
||||
if (payloadStr.matches(".*(?i)(cmd\\.exe|powershell|/bin/bash).*")) {
|
||||
triggerAlert(packet, "Command injection attempt detected");
|
||||
}
|
||||
}
|
||||
|
||||
private void analyzeCapturedTraffic() {
|
||||
if (packetBuffer.isEmpty()) return;
|
||||
|
||||
Map<String, Integer> ipCounts = new HashMap<>();
|
||||
for (CapturedPacket packet : packetBuffer.values()) {
|
||||
ipCounts.merge(packet.getSourceIP(), 1, Integer::sum);
|
||||
}
|
||||
|
||||
for (Map.Entry<String, Integer> entry : ipCounts.entrySet()) {
|
||||
if (entry.getValue() > 1000) {
|
||||
logger.warn("High packet count from IP: {} - {} packets", entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void detectAnomalies() {
|
||||
for (SessionInfo session : sessions.values()) {
|
||||
if (session.getPacketCount() > 5000 && session.getTotalBytes() < session.getPacketCount() * 10) {
|
||||
logger.warn("Potential slowloris attack from {} to {}", session.getSourceIP(), session.getDestIP());
|
||||
}
|
||||
|
||||
if (session.getPacketCount() > 10000) {
|
||||
triggerAlert(session.getLatestPacket(), "Potential DoS attack detected");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void cleanupOldData() {
|
||||
long now = System.currentTimeMillis();
|
||||
List<String> expiredSessions = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<String, SessionInfo> entry : sessions.entrySet()) {
|
||||
if (now - entry.getValue().getLastPacketTime() > 300000) {
|
||||
expiredSessions.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
for (String key : expiredSessions) {
|
||||
sessions.remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
private void triggerAlert(CapturedPacket packet, String reason) {
|
||||
SnifferAlert alert = new SnifferAlert(
|
||||
packet.getSourceIP(),
|
||||
packet.getDestIP(),
|
||||
packet.getProtocol(),
|
||||
reason,
|
||||
LocalDateTime.now()
|
||||
);
|
||||
alertQueue.offer(alert);
|
||||
|
||||
if (alertQueue.size() > 500) {
|
||||
alertQueue.poll();
|
||||
}
|
||||
|
||||
logger.warn("Sniffer Alert: {} -> {} : {}", packet.getSourceIP(), packet.getDestIP(), reason);
|
||||
}
|
||||
|
||||
public Map<String, Object> getStats() {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("capturing", capturing);
|
||||
stats.put("totalPacketsCaptured", totalPacketsCaptured.get());
|
||||
stats.put("totalBytesCaptured", totalBytesCaptured.get());
|
||||
stats.put("suspiciousPackets", suspiciousPackets.get());
|
||||
stats.put("bufferSize", packetBuffer.size());
|
||||
stats.put("activeSessions", sessions.size());
|
||||
stats.put("alertQueueSize", alertQueue.size());
|
||||
|
||||
Map<String, PacketStatistics> protoStats = new HashMap<>();
|
||||
for (Map.Entry<String, PacketStatistics> entry : protocolStats.entrySet()) {
|
||||
protoStats.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
stats.put("protocolStats", protoStats);
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
public List<CapturedPacket> getRecentPackets(int limit) {
|
||||
List<CapturedPacket> packets = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (CapturedPacket packet : packetBuffer.values()) {
|
||||
if (count++ >= limit) break;
|
||||
packets.add(packet);
|
||||
}
|
||||
return packets;
|
||||
}
|
||||
|
||||
public List<CapturedPacket> getPacketsByIP(String ip, int limit) {
|
||||
List<CapturedPacket> packets = new ArrayList<>();
|
||||
List<CapturedPacket> history = packetHistory.get(ip);
|
||||
if (history != null) {
|
||||
int count = 0;
|
||||
for (CapturedPacket packet : history) {
|
||||
if (count++ >= limit) break;
|
||||
packets.add(packet);
|
||||
}
|
||||
}
|
||||
return packets;
|
||||
}
|
||||
|
||||
public List<SnifferAlert> getRecentAlerts(int limit) {
|
||||
List<SnifferAlert> alerts = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (SnifferAlert alert : alertQueue) {
|
||||
if (count++ >= limit) break;
|
||||
alerts.add(alert);
|
||||
}
|
||||
return alerts;
|
||||
}
|
||||
|
||||
public Map<String, SessionInfo> getActiveSessions() {
|
||||
return new HashMap<>(sessions);
|
||||
}
|
||||
|
||||
public void clearBuffer() {
|
||||
packetBuffer.clear();
|
||||
logger.info("Packet buffer cleared");
|
||||
}
|
||||
|
||||
public void exportPackets(String format) {
|
||||
logger.info("Exporting packets in {} format", format);
|
||||
}
|
||||
|
||||
public static class CapturedPacket {
|
||||
private final String packetId;
|
||||
private final String sourceIP;
|
||||
private final String destIP;
|
||||
private final int sourcePort;
|
||||
private final int destPort;
|
||||
private final String protocol;
|
||||
private final byte[] payload;
|
||||
private final int length;
|
||||
private final LocalDateTime timestamp;
|
||||
|
||||
public CapturedPacket(String packetId, String sourceIP, String destIP, int sourcePort,
|
||||
int destPort, String protocol, byte[] payload, int length, LocalDateTime timestamp) {
|
||||
this.packetId = packetId;
|
||||
this.sourceIP = sourceIP;
|
||||
this.destIP = destIP;
|
||||
this.sourcePort = sourcePort;
|
||||
this.destPort = destPort;
|
||||
this.protocol = protocol;
|
||||
this.payload = payload;
|
||||
this.length = length;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getPacketId() { return packetId; }
|
||||
public String getSourceIP() { return sourceIP; }
|
||||
public String getDestIP() { return destIP; }
|
||||
public int getSourcePort() { return sourcePort; }
|
||||
public int getDestPort() { return destPort; }
|
||||
public String getProtocol() { return protocol; }
|
||||
public byte[] getPayload() { return payload; }
|
||||
public int getLength() { return length; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
}
|
||||
|
||||
public static class PacketStatistics {
|
||||
private final String protocolName;
|
||||
private final AtomicLong packetCount = new AtomicLong(0);
|
||||
private final AtomicLong byteCount = new AtomicLong(0);
|
||||
|
||||
public PacketStatistics(String protocolName) {
|
||||
this.protocolName = protocolName;
|
||||
}
|
||||
|
||||
public void incrementPacketCount() { packetCount.incrementAndGet(); }
|
||||
public void addBytes(int bytes) { byteCount.addAndGet(bytes); }
|
||||
|
||||
public String getProtocolName() { return protocolName; }
|
||||
public long getPacketCount() { return packetCount.get(); }
|
||||
public long getByteCount() { return byteCount.get(); }
|
||||
}
|
||||
|
||||
public static class SessionInfo {
|
||||
private final String sourceIP;
|
||||
private final String destIP;
|
||||
private final int destPort;
|
||||
private volatile long packetCount;
|
||||
private volatile long totalBytes;
|
||||
private volatile long startTime;
|
||||
private volatile long lastPacketTime;
|
||||
private volatile CapturedPacket latestPacket;
|
||||
|
||||
public SessionInfo(String sourceIP, String destIP, int destPort) {
|
||||
this.sourceIP = sourceIP;
|
||||
this.destIP = destIP;
|
||||
this.destPort = destPort;
|
||||
this.startTime = System.currentTimeMillis();
|
||||
this.lastPacketTime = startTime;
|
||||
}
|
||||
|
||||
public void addPacket(CapturedPacket packet) {
|
||||
packetCount++;
|
||||
totalBytes += packet.getLength();
|
||||
lastPacketTime = System.currentTimeMillis();
|
||||
latestPacket = packet;
|
||||
}
|
||||
|
||||
public String getSourceIP() { return sourceIP; }
|
||||
public String getDestIP() { return destIP; }
|
||||
public int getDestPort() { return destPort; }
|
||||
public long getPacketCount() { return packetCount; }
|
||||
public long getTotalBytes() { return totalBytes; }
|
||||
public long getStartTime() { return startTime; }
|
||||
public long getLastPacketTime() { return lastPacketTime; }
|
||||
public CapturedPacket getLatestPacket() { return latestPacket; }
|
||||
}
|
||||
|
||||
public static class SnifferAlert {
|
||||
private final String sourceIP;
|
||||
private final String destIP;
|
||||
private final String protocol;
|
||||
private final String reason;
|
||||
private final LocalDateTime timestamp;
|
||||
|
||||
public SnifferAlert(String sourceIP, String destIP, String protocol, String reason, LocalDateTime timestamp) {
|
||||
this.sourceIP = sourceIP;
|
||||
this.destIP = destIP;
|
||||
this.protocol = protocol;
|
||||
this.reason = reason;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getSourceIP() { return sourceIP; }
|
||||
public String getDestIP() { return destIP; }
|
||||
public String getProtocol() { return protocol; }
|
||||
public String getReason() { return reason; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,661 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Deque;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
|
||||
@Service
|
||||
public class NetworkThreatFusionService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(NetworkThreatFusionService.class);
|
||||
|
||||
private static final int EVENT_SAMPLE_SIZE = 200;
|
||||
private static final int HIGH_RISK_THRESHOLD = 60;
|
||||
private static final int CRITICAL_RISK_THRESHOLD = 80;
|
||||
|
||||
private final DDoSProtectionService ddosProtectionService;
|
||||
private final FirewallService firewallService;
|
||||
private final IntrusionDetectionService intrusionDetectionService;
|
||||
private final NetworkMonitorService networkMonitorService;
|
||||
private final TrafficAnalysisService trafficAnalysisService;
|
||||
private final PortScanDetectionService portScanDetectionService;
|
||||
private final PacketInspectionService packetInspectionService;
|
||||
private final IPReputationService ipReputationService;
|
||||
private final GeoIPService geoIPService;
|
||||
private final RateLimitService rateLimitService;
|
||||
|
||||
private final Map<String, QuarantineRecord> quarantinedIps = new ConcurrentHashMap<>();
|
||||
private final Deque<FusionIncident> incidentLog = new ConcurrentLinkedDeque<>();
|
||||
|
||||
public NetworkThreatFusionService(DDoSProtectionService ddosProtectionService,
|
||||
FirewallService firewallService,
|
||||
IntrusionDetectionService intrusionDetectionService,
|
||||
NetworkMonitorService networkMonitorService,
|
||||
TrafficAnalysisService trafficAnalysisService,
|
||||
PortScanDetectionService portScanDetectionService,
|
||||
PacketInspectionService packetInspectionService,
|
||||
IPReputationService ipReputationService,
|
||||
GeoIPService geoIPService,
|
||||
RateLimitService rateLimitService) {
|
||||
this.ddosProtectionService = ddosProtectionService;
|
||||
this.firewallService = firewallService;
|
||||
this.intrusionDetectionService = intrusionDetectionService;
|
||||
this.networkMonitorService = networkMonitorService;
|
||||
this.trafficAnalysisService = trafficAnalysisService;
|
||||
this.portScanDetectionService = portScanDetectionService;
|
||||
this.packetInspectionService = packetInspectionService;
|
||||
this.ipReputationService = ipReputationService;
|
||||
this.geoIPService = geoIPService;
|
||||
this.rateLimitService = rateLimitService;
|
||||
}
|
||||
|
||||
public Map<String, Object> getPosture() {
|
||||
List<ThreatAssessment> assessments = collectThreatAssessments();
|
||||
long highRiskCount = assessments.stream()
|
||||
.filter(assessment -> assessment.riskScore >= HIGH_RISK_THRESHOLD)
|
||||
.count();
|
||||
long criticalRiskCount = assessments.stream()
|
||||
.filter(assessment -> assessment.riskScore >= CRITICAL_RISK_THRESHOLD)
|
||||
.count();
|
||||
|
||||
Map<String, Object> ddosStats = ddosProtectionService.getStats();
|
||||
Map<String, Object> intrusionStats = intrusionDetectionService.getStats();
|
||||
Map<String, Object> firewallStats = firewallService.getStats();
|
||||
Map<String, Object> portScanStats = portScanDetectionService.getStats();
|
||||
Map<String, Object> packetStats = packetInspectionService.getStats();
|
||||
Map<String, Object> networkStats = networkMonitorService.getGlobalStats();
|
||||
|
||||
int threatPressure = 0;
|
||||
threatPressure += toInt(ddosStats.get("blockedIPs")) * 6;
|
||||
threatPressure += toInt(intrusionStats.get("activeAlerts")) * 10;
|
||||
threatPressure += toInt(portScanStats.get("blockedScanners")) * 8;
|
||||
threatPressure += Math.min(20, toInt(packetStats.get("maliciousBlocked")));
|
||||
threatPressure += toInt(firewallStats.get("blacklistSize")) * 4;
|
||||
threatPressure += quarantinedIps.size() * 8;
|
||||
threatPressure += (int) highRiskCount * 5;
|
||||
threatPressure += (int) criticalRiskCount * 8;
|
||||
|
||||
int maxObservedRisk = assessments.stream()
|
||||
.mapToInt(assessment -> assessment.riskScore)
|
||||
.max()
|
||||
.orElse(0);
|
||||
|
||||
threatPressure += maxObservedRisk / 3;
|
||||
|
||||
int postureScore = Math.max(0, 100 - Math.min(100, threatPressure));
|
||||
String threatLevel = determineThreatLevel(postureScore, maxObservedRisk);
|
||||
|
||||
Map<String, Object> signals = new LinkedHashMap<>();
|
||||
signals.put("ddosBlockedIPs", ddosStats.getOrDefault("blockedIPs", 0));
|
||||
signals.put("firewallBlacklist", firewallStats.getOrDefault("blacklistSize", 0));
|
||||
signals.put("intrusionAlerts", intrusionStats.getOrDefault("activeAlerts", 0));
|
||||
signals.put("portScanBlocks", portScanStats.getOrDefault("blockedScanners", 0));
|
||||
signals.put("packetBlocks", packetStats.getOrDefault("maliciousBlocked", 0));
|
||||
signals.put("activeNetworkSessions", networkStats.getOrDefault("activeSessions", 0));
|
||||
|
||||
Map<String, Object> posture = new LinkedHashMap<>();
|
||||
posture.put("postureScore", postureScore);
|
||||
posture.put("threatLevel", threatLevel);
|
||||
posture.put("highRiskIPs", highRiskCount);
|
||||
posture.put("criticalRiskIPs", criticalRiskCount);
|
||||
posture.put("quarantinedIPs", quarantinedIps.size());
|
||||
posture.put("signals", signals);
|
||||
posture.put("topRiskIPs", toAssessmentMaps(assessments, 5));
|
||||
posture.put("recommendations", buildRecommendations(postureScore, assessments));
|
||||
return posture;
|
||||
}
|
||||
|
||||
public Map<String, Object> inspectIP(String ip) {
|
||||
ThreatAssessment assessment = assessIp(ip);
|
||||
return assessment.toMap();
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> getTopRiskIPs(int limit) {
|
||||
return toAssessmentMaps(collectThreatAssessments(), limit);
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> getRecentIncidents(int limit) {
|
||||
List<ThreatAssessment> assessments = collectThreatAssessments();
|
||||
Map<String, Integer> currentRisk = new ConcurrentHashMap<>();
|
||||
for (ThreatAssessment assessment : assessments) {
|
||||
currentRisk.put(assessment.ip, assessment.riskScore);
|
||||
}
|
||||
|
||||
List<FusionIncident> incidents = new ArrayList<>(incidentLog);
|
||||
|
||||
for (DDoSProtectionService.AttackEvent event : ddosProtectionService.getAttackLog(EVENT_SAMPLE_SIZE)) {
|
||||
incidents.add(new FusionIncident("ddos", event.ip, event.type, event.details, event.timestamp,
|
||||
currentRisk.getOrDefault(event.ip, 0)));
|
||||
}
|
||||
for (PortScanDetectionService.ScanEvent event : portScanDetectionService.getScanLog(EVENT_SAMPLE_SIZE)) {
|
||||
incidents.add(new FusionIncident("port-scan", event.ip, event.reason, event.details, event.timestamp,
|
||||
currentRisk.getOrDefault(event.ip, 0)));
|
||||
}
|
||||
for (PacketInspectionService.PacketAlert alert : packetInspectionService.getAlerts(EVENT_SAMPLE_SIZE)) {
|
||||
incidents.add(new FusionIncident("packet-inspection", alert.sourceIP, alert.ruleName, alert.details, alert.timestamp,
|
||||
currentRisk.getOrDefault(alert.sourceIP, 0)));
|
||||
}
|
||||
for (IntrusionDetectionService.IntrusionEvent event : intrusionDetectionService.getEvents(EVENT_SAMPLE_SIZE)) {
|
||||
if (isActionableSource(event.sourceIP)) {
|
||||
incidents.add(new FusionIncident("intrusion", event.sourceIP, event.type, event.details, event.timestamp,
|
||||
currentRisk.getOrDefault(event.sourceIP, 0)));
|
||||
}
|
||||
}
|
||||
for (NetworkMonitorService.NetworkAlert alert : networkMonitorService.getAlerts(EVENT_SAMPLE_SIZE)) {
|
||||
incidents.add(new FusionIncident("network-monitor", alert.getIp(), alert.getType(), alert.getDetails(), alert.getTimestamp(),
|
||||
currentRisk.getOrDefault(alert.getIp(), 0)));
|
||||
}
|
||||
|
||||
incidents.sort(Comparator.comparingLong((FusionIncident incident) -> incident.timestamp).reversed());
|
||||
|
||||
List<Map<String, Object>> result = new ArrayList<>();
|
||||
int maxItems = Math.max(1, limit);
|
||||
for (FusionIncident incident : incidents) {
|
||||
if (result.size() >= maxItems) {
|
||||
break;
|
||||
}
|
||||
result.add(incident.toMap());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public Map<String, Object> quarantineIP(String ip, String actor, String reason) {
|
||||
String targetIp = normalizeTarget(ip);
|
||||
if (targetIp == null) {
|
||||
return invalidTarget("invalid", "IP 不能为空");
|
||||
}
|
||||
|
||||
String operator = defaultText(actor, "system");
|
||||
String details = defaultText(reason, "Manual quarantine triggered");
|
||||
|
||||
ddosProtectionService.blockIP(targetIp, "THREAT_FUSION_QUARANTINE", details);
|
||||
firewallService.addToBlacklist(targetIp);
|
||||
ipReputationService.addToBlacklist(targetIp);
|
||||
networkMonitorService.addAlert(targetIp, "QUARANTINED", details);
|
||||
|
||||
QuarantineRecord record = quarantinedIps.compute(targetIp, (key, existing) -> {
|
||||
if (existing == null) {
|
||||
return new QuarantineRecord(targetIp, operator, details);
|
||||
}
|
||||
existing.actor = operator;
|
||||
existing.reason = details;
|
||||
existing.lastUpdated = System.currentTimeMillis();
|
||||
existing.count++;
|
||||
return existing;
|
||||
});
|
||||
|
||||
logger.warn("Threat fusion quarantined IP {} by {}: {}", targetIp, operator, details);
|
||||
logInternalIncident("quarantine", targetIp, "THREAT_FUSION_QUARANTINE", details, 100);
|
||||
|
||||
Map<String, Object> response = new LinkedHashMap<>(inspectIP(targetIp));
|
||||
response.put("status", "quarantined");
|
||||
response.put("quarantine", record.toMap());
|
||||
return response;
|
||||
}
|
||||
|
||||
public Map<String, Object> releaseIP(String ip, String actor, String reason) {
|
||||
String targetIp = normalizeTarget(ip);
|
||||
if (targetIp == null) {
|
||||
return invalidTarget("invalid", "IP 不能为空");
|
||||
}
|
||||
|
||||
String operator = defaultText(actor, "system");
|
||||
String details = defaultText(reason, "Manual release triggered");
|
||||
|
||||
ddosProtectionService.unblockIP(targetIp);
|
||||
firewallService.removeFromBlacklist(targetIp);
|
||||
ipReputationService.removeFromBlacklist(targetIp);
|
||||
networkMonitorService.addAlert(targetIp, "RELEASED", details);
|
||||
|
||||
QuarantineRecord removed = quarantinedIps.remove(targetIp);
|
||||
logger.info("Threat fusion released IP {} by {}: {}", targetIp, operator, details);
|
||||
logInternalIncident("release", targetIp, "THREAT_FUSION_RELEASE", details, 0);
|
||||
|
||||
Map<String, Object> response = new LinkedHashMap<>(inspectIP(targetIp));
|
||||
response.put("status", removed != null ? "released" : "not-quarantined");
|
||||
if (removed != null) {
|
||||
response.put("releasedQuarantine", removed.toMap());
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
private List<ThreatAssessment> collectThreatAssessments() {
|
||||
Set<String> candidates = new HashSet<>();
|
||||
|
||||
for (DDoSProtectionService.BlockedIP blockedIP : ddosProtectionService.getAllBlockedIPs()) {
|
||||
addCandidate(candidates, blockedIP.ip);
|
||||
}
|
||||
for (FirewallService.ConnectionState connection : firewallService.getActiveConnections()) {
|
||||
addCandidate(candidates, connection.sourceIP);
|
||||
}
|
||||
for (FirewallService.FirewallLog log : firewallService.getLogs(EVENT_SAMPLE_SIZE)) {
|
||||
addCandidate(candidates, log.sourceIP);
|
||||
}
|
||||
for (NetworkMonitorService.NetworkSession session : networkMonitorService.getAllSessions()) {
|
||||
addCandidate(candidates, session.getIp());
|
||||
}
|
||||
for (NetworkMonitorService.NetworkAlert alert : networkMonitorService.getAlerts(EVENT_SAMPLE_SIZE)) {
|
||||
addCandidate(candidates, alert.getIp());
|
||||
}
|
||||
for (PortScanDetectionService.BlockedScanner scanner : portScanDetectionService.getBlockedScanners()) {
|
||||
addCandidate(candidates, scanner.ip);
|
||||
}
|
||||
for (PortScanDetectionService.ScanEvent event : portScanDetectionService.getScanLog(EVENT_SAMPLE_SIZE)) {
|
||||
addCandidate(candidates, event.ip);
|
||||
}
|
||||
for (PacketInspectionService.PacketAlert alert : packetInspectionService.getAlerts(EVENT_SAMPLE_SIZE)) {
|
||||
addCandidate(candidates, alert.sourceIP);
|
||||
}
|
||||
for (TrafficAnalysisService.TrafficSession session : trafficAnalysisService.getAllSessions()) {
|
||||
addCandidate(candidates, session.getIp());
|
||||
}
|
||||
for (IPReputationService.IPProfile profile : ipReputationService.getAllProfiles()) {
|
||||
addCandidate(candidates, profile.ip);
|
||||
}
|
||||
for (GeoIPService.GeoEvent event : geoIPService.getRecentEvents(EVENT_SAMPLE_SIZE)) {
|
||||
addCandidate(candidates, event.ip);
|
||||
}
|
||||
for (IntrusionDetectionService.IntrusionEvent event : intrusionDetectionService.getEvents(EVENT_SAMPLE_SIZE)) {
|
||||
addCandidate(candidates, event.sourceIP);
|
||||
}
|
||||
candidates.addAll(quarantinedIps.keySet());
|
||||
|
||||
List<ThreatAssessment> assessments = new ArrayList<>();
|
||||
for (String candidate : candidates) {
|
||||
ThreatAssessment assessment = assessIp(candidate);
|
||||
if (assessment.riskScore > 0 || assessment.blocked || assessment.quarantined) {
|
||||
assessments.add(assessment);
|
||||
}
|
||||
}
|
||||
|
||||
assessments.sort(Comparator.comparingInt((ThreatAssessment assessment) -> assessment.riskScore)
|
||||
.reversed()
|
||||
.thenComparing(assessment -> assessment.ip));
|
||||
return assessments;
|
||||
}
|
||||
|
||||
private ThreatAssessment assessIp(String ip) {
|
||||
ThreatAssessment assessment = new ThreatAssessment(normalizeTarget(ip));
|
||||
if (assessment.ip == null) {
|
||||
return assessment;
|
||||
}
|
||||
|
||||
DDoSProtectionService.BlockedIP ddosBlock = ddosProtectionService.getBlockInfo(assessment.ip);
|
||||
if (ddosBlock != null) {
|
||||
assessment.addRisk(35, "DDoS 引擎已封禁,原因: " + ddosBlock.reason);
|
||||
assessment.blocked = true;
|
||||
}
|
||||
|
||||
QuarantineRecord quarantineRecord = quarantinedIps.get(assessment.ip);
|
||||
if (quarantineRecord != null) {
|
||||
assessment.addRisk(40, "融合防御已隔离该 IP");
|
||||
assessment.quarantined = true;
|
||||
assessment.blocked = true;
|
||||
}
|
||||
|
||||
if (firewallService.isBlacklisted(assessment.ip)) {
|
||||
assessment.addRisk(25, "防火墙黑名单命中");
|
||||
assessment.blocked = true;
|
||||
}
|
||||
|
||||
PortScanDetectionService.BlockedScanner blockedScanner = portScanDetectionService.getBlockInfo(assessment.ip);
|
||||
if (blockedScanner != null) {
|
||||
assessment.addRisk(30, "端口扫描检测已封禁,原因: " + blockedScanner.reason);
|
||||
}
|
||||
|
||||
NetworkMonitorService.NetworkSession session = networkMonitorService.getSession(assessment.ip);
|
||||
if (session != null) {
|
||||
if (session.getConnectionCount() >= 40) {
|
||||
assessment.addRisk(Math.min(20, session.getConnectionCount() / 2),
|
||||
"短时间连接端口过多: " + session.getConnectionCount());
|
||||
}
|
||||
|
||||
long totalBytes = session.getTotalBytesIn() + session.getTotalBytesOut();
|
||||
if (totalBytes >= 50_000_000L) {
|
||||
assessment.addRisk(10, "观测到高流量会话");
|
||||
}
|
||||
}
|
||||
|
||||
NetworkMonitorService.BandwidthTracker bandwidthTracker = networkMonitorService.getBandwidthTracker(assessment.ip);
|
||||
if (bandwidthTracker != null && bandwidthTracker.isHighBandwidth()) {
|
||||
assessment.addRisk(15, "带宽窗口突增");
|
||||
}
|
||||
|
||||
TrafficAnalysisService.AnomalyScore anomalyScore = trafficAnalysisService.getAnomalyScore(assessment.ip);
|
||||
if (anomalyScore != null && anomalyScore.getScore() > 0.2) {
|
||||
int contribution = (int) Math.round(anomalyScore.getScore() * 30);
|
||||
assessment.addRisk(contribution, String.format(Locale.ROOT,
|
||||
"流量异常评分偏高: %.2f", anomalyScore.getScore()));
|
||||
}
|
||||
|
||||
int packetAlerts = countPacketAlerts(assessment.ip);
|
||||
if (packetAlerts > 0) {
|
||||
assessment.addRisk(Math.min(20, packetAlerts * 5), "包检测已触发 " + packetAlerts + " 次告警");
|
||||
}
|
||||
|
||||
int intrusionEvents = countIntrusionEvents(assessment.ip);
|
||||
if (intrusionEvents > 0) {
|
||||
assessment.addRisk(Math.min(25, intrusionEvents * 5), "入侵检测记录到 " + intrusionEvents + " 个相关事件");
|
||||
}
|
||||
|
||||
int ddosEvents = countDdosEvents(assessment.ip);
|
||||
if (ddosEvents > 0) {
|
||||
assessment.addRisk(Math.min(15, ddosEvents * 3), "近期 DDoS/泛洪事件: " + ddosEvents);
|
||||
}
|
||||
|
||||
int firewallBlocks = countFirewallBlocks(assessment.ip);
|
||||
if (firewallBlocks > 0) {
|
||||
assessment.addRisk(Math.min(12, firewallBlocks * 3), "防火墙近期阻断次数: " + firewallBlocks);
|
||||
}
|
||||
|
||||
boolean reputationBlacklisted = ipReputationService.isBlacklisted(assessment.ip);
|
||||
if (reputationBlacklisted) {
|
||||
assessment.addRisk(20, "IP 信誉黑名单命中");
|
||||
assessment.blocked = true;
|
||||
}
|
||||
|
||||
IPReputationService.IPProfile profile = ipReputationService.getProfile(assessment.ip);
|
||||
if (profile != null) {
|
||||
double reputationScore = profile.calculateScore();
|
||||
assessment.metadata.put("reputationScore", roundToOneDecimal(reputationScore));
|
||||
|
||||
if (profile.blacklisted && !reputationBlacklisted) {
|
||||
assessment.addRisk(20, "IP 信誉黑名单命中");
|
||||
assessment.blocked = true;
|
||||
} else if (reputationScore < 30) {
|
||||
assessment.addRisk((int) Math.round(30 - reputationScore),
|
||||
"IP 信誉分过低: " + roundToOneDecimal(reputationScore));
|
||||
}
|
||||
}
|
||||
|
||||
GeoIPService.GeoLocation geoLocation = geoIPService.getGeoLocation(assessment.ip);
|
||||
if (geoLocation != null) {
|
||||
assessment.metadata.put("country", geoLocation.country);
|
||||
Set<String> blockedCountries = geoIPService.getBlockedCountries();
|
||||
Set<String> allowedCountries = geoIPService.getAllowedCountries();
|
||||
|
||||
if (blockedCountries.contains(geoLocation.country)) {
|
||||
assessment.addRisk(15, "来源国家已被封锁: " + geoLocation.country);
|
||||
} else if (!allowedCountries.isEmpty() && !allowedCountries.contains(geoLocation.country)) {
|
||||
assessment.addRisk(10, "来源国家不在白名单中: " + geoLocation.country);
|
||||
}
|
||||
}
|
||||
|
||||
RateLimitService.RateLimitStats rateLimitStats = rateLimitService.getStats(assessment.ip);
|
||||
if (rateLimitStats != null && rateLimitStats.rejections.get() > 0) {
|
||||
long rejections = rateLimitStats.rejections.get();
|
||||
assessment.addRisk((int) Math.min(10, rejections * 2), "限流拒绝次数: " + rejections);
|
||||
}
|
||||
|
||||
assessment.riskScore = Math.min(100, assessment.riskScore);
|
||||
assessment.riskLevel = riskLevelFor(assessment.riskScore);
|
||||
return assessment;
|
||||
}
|
||||
|
||||
private List<Map<String, Object>> toAssessmentMaps(List<ThreatAssessment> assessments, int limit) {
|
||||
List<Map<String, Object>> result = new ArrayList<>();
|
||||
int maxItems = Math.max(1, limit);
|
||||
for (ThreatAssessment assessment : assessments) {
|
||||
if (result.size() >= maxItems) {
|
||||
break;
|
||||
}
|
||||
result.add(assessment.toMap());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<String> buildRecommendations(int postureScore, List<ThreatAssessment> assessments) {
|
||||
List<String> recommendations = new ArrayList<>();
|
||||
|
||||
if (assessments.isEmpty()) {
|
||||
recommendations.add("当前网络态势平稳,继续观察连接基线即可。");
|
||||
return recommendations;
|
||||
}
|
||||
|
||||
if (assessments.stream().anyMatch(assessment -> assessment.riskScore >= CRITICAL_RISK_THRESHOLD)) {
|
||||
recommendations.add("优先隔离高风险 IP,并同步检查上游代理、防火墙或云清洗规则。");
|
||||
}
|
||||
|
||||
if (portScanDetectionService.getStats().getOrDefault("blockedScanners", 0) instanceof Number blockedScanners
|
||||
&& blockedScanners.longValue() > 0) {
|
||||
recommendations.add("当前存在端口扫描迹象,建议只暴露 Minecraft 与必要的管理入口。");
|
||||
}
|
||||
|
||||
Map<String, Object> packetStats = packetInspectionService.getStats();
|
||||
if (toInt(packetStats.get("maliciousBlocked")) > 0) {
|
||||
recommendations.add("已检测到异常报文,建议复核包检测规则和上游流量清洗策略。");
|
||||
}
|
||||
|
||||
if (postureScore < 60) {
|
||||
recommendations.add("建议把防火墙切到更严格的连接阈值,并临时提高关键接口限流。");
|
||||
}
|
||||
|
||||
if (recommendations.isEmpty()) {
|
||||
recommendations.add("风险可控,继续观察高风险 IP 的后续行为。");
|
||||
}
|
||||
|
||||
return recommendations.stream().limit(3).toList();
|
||||
}
|
||||
|
||||
private String determineThreatLevel(int postureScore, int maxObservedRisk) {
|
||||
int compositeRisk = Math.max(maxObservedRisk, 100 - postureScore);
|
||||
return riskLevelFor(compositeRisk);
|
||||
}
|
||||
|
||||
private String riskLevelFor(int riskScore) {
|
||||
if (riskScore >= 80) {
|
||||
return "critical";
|
||||
}
|
||||
if (riskScore >= 60) {
|
||||
return "high";
|
||||
}
|
||||
if (riskScore >= 40) {
|
||||
return "elevated";
|
||||
}
|
||||
if (riskScore >= 20) {
|
||||
return "guarded";
|
||||
}
|
||||
return "low";
|
||||
}
|
||||
|
||||
private int countPacketAlerts(String ip) {
|
||||
int count = 0;
|
||||
for (PacketInspectionService.PacketAlert alert : packetInspectionService.getAlerts(EVENT_SAMPLE_SIZE)) {
|
||||
if (ip.equals(alert.sourceIP)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private int countIntrusionEvents(String ip) {
|
||||
int count = 0;
|
||||
for (IntrusionDetectionService.IntrusionEvent event : intrusionDetectionService.getEvents(EVENT_SAMPLE_SIZE)) {
|
||||
if (ip.equals(event.sourceIP)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private int countDdosEvents(String ip) {
|
||||
int count = 0;
|
||||
for (DDoSProtectionService.AttackEvent event : ddosProtectionService.getAttackLog(EVENT_SAMPLE_SIZE)) {
|
||||
if (ip.equals(event.ip)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private int countFirewallBlocks(String ip) {
|
||||
int count = 0;
|
||||
for (FirewallService.FirewallLog log : firewallService.getLogs(EVENT_SAMPLE_SIZE)) {
|
||||
if (ip.equals(log.sourceIP) && log.type.startsWith("BLOCK")) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private void addCandidate(Collection<String> candidates, String source) {
|
||||
String normalized = normalizeTarget(source);
|
||||
if (normalized != null && isActionableSource(normalized)) {
|
||||
candidates.add(normalized);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isActionableSource(String source) {
|
||||
String lower = source.toLowerCase(Locale.ROOT);
|
||||
return !lower.equals("system") && !lower.equals("request") && !lower.equals("unknown");
|
||||
}
|
||||
|
||||
private String normalizeTarget(String ip) {
|
||||
if (ip == null) {
|
||||
return null;
|
||||
}
|
||||
String normalized = ip.trim();
|
||||
return normalized.isEmpty() ? null : normalized;
|
||||
}
|
||||
|
||||
private Map<String, Object> invalidTarget(String status, String message) {
|
||||
Map<String, Object> response = new LinkedHashMap<>();
|
||||
response.put("status", status);
|
||||
response.put("message", message);
|
||||
return response;
|
||||
}
|
||||
|
||||
private String defaultText(String value, String fallback) {
|
||||
String normalized = normalizeTarget(value);
|
||||
return normalized != null ? normalized : fallback;
|
||||
}
|
||||
|
||||
private int toInt(Object value) {
|
||||
if (value instanceof Number number) {
|
||||
return number.intValue();
|
||||
}
|
||||
if (value instanceof String text) {
|
||||
try {
|
||||
return Integer.parseInt(text);
|
||||
} catch (NumberFormatException ignored) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private double roundToOneDecimal(double value) {
|
||||
return Math.round(value * 10.0) / 10.0;
|
||||
}
|
||||
|
||||
private void logInternalIncident(String source, String ip, String type, String details, int riskScore) {
|
||||
FusionIncident incident = new FusionIncident(source, ip, type, details, System.currentTimeMillis(), riskScore);
|
||||
incidentLog.addFirst(incident);
|
||||
while (incidentLog.size() > 500) {
|
||||
incidentLog.removeLast();
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ThreatAssessment {
|
||||
private final String ip;
|
||||
private int riskScore;
|
||||
private String riskLevel = "low";
|
||||
private boolean blocked;
|
||||
private boolean quarantined;
|
||||
private final List<String> reasons = new ArrayList<>();
|
||||
private final Map<String, Object> metadata = new LinkedHashMap<>();
|
||||
|
||||
private ThreatAssessment(String ip) {
|
||||
this.ip = ip;
|
||||
}
|
||||
|
||||
private void addRisk(int value, String reason) {
|
||||
if (value <= 0) {
|
||||
return;
|
||||
}
|
||||
riskScore += value;
|
||||
reasons.add(reason);
|
||||
}
|
||||
|
||||
private Map<String, Object> toMap() {
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
data.put("ip", ip);
|
||||
data.put("riskScore", Math.min(100, riskScore));
|
||||
data.put("riskLevel", riskLevel);
|
||||
data.put("blocked", blocked);
|
||||
data.put("quarantined", quarantined);
|
||||
data.put("reasons", reasons);
|
||||
data.put("metadata", metadata);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class QuarantineRecord {
|
||||
private final String ip;
|
||||
private volatile String actor;
|
||||
private volatile String reason;
|
||||
private final long createdAt;
|
||||
private volatile long lastUpdated;
|
||||
private volatile int count = 1;
|
||||
|
||||
private QuarantineRecord(String ip, String actor, String reason) {
|
||||
this.ip = ip;
|
||||
this.actor = actor;
|
||||
this.reason = reason;
|
||||
this.createdAt = System.currentTimeMillis();
|
||||
this.lastUpdated = createdAt;
|
||||
}
|
||||
|
||||
private Map<String, Object> toMap() {
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
data.put("ip", ip);
|
||||
data.put("actor", actor);
|
||||
data.put("reason", reason);
|
||||
data.put("createdAt", createdAt);
|
||||
data.put("lastUpdated", lastUpdated);
|
||||
data.put("count", count);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class FusionIncident {
|
||||
private final String source;
|
||||
private final String ip;
|
||||
private final String type;
|
||||
private final String details;
|
||||
private final long timestamp;
|
||||
private final int riskScore;
|
||||
|
||||
private FusionIncident(String source, String ip, String type, String details, long timestamp, int riskScore) {
|
||||
this.source = source;
|
||||
this.ip = ip;
|
||||
this.type = type;
|
||||
this.details = details;
|
||||
this.timestamp = timestamp;
|
||||
this.riskScore = riskScore;
|
||||
}
|
||||
|
||||
private Map<String, Object> toMap() {
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
data.put("source", source);
|
||||
data.put("ip", ip);
|
||||
data.put("type", type);
|
||||
data.put("details", details);
|
||||
data.put("timestamp", timestamp);
|
||||
data.put("riskScore", riskScore);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.*;
|
||||
|
||||
@Service
|
||||
public class PacketInspectionService {
|
||||
|
||||
private final Map<String, PacketRule> rules = new ConcurrentHashMap<>();
|
||||
private final Map<String, List<PacketLog>> packetLogs = new ConcurrentHashMap<>();
|
||||
private final Queue<PacketAlert> alerts = new ConcurrentLinkedQueue<>();
|
||||
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
|
||||
|
||||
private static final int MAX_PACKET_SIZE = 65535;
|
||||
private static final int MAX_LOGS_PER_IP = 1000;
|
||||
|
||||
private final AtomicLong totalPacketsInspected = new AtomicLong(0);
|
||||
private final AtomicLong maliciousPacketsBlocked = new AtomicLong(0);
|
||||
|
||||
public PacketInspectionService() {
|
||||
initializeDefaultRules();
|
||||
startCleanupTask();
|
||||
}
|
||||
|
||||
private void initializeDefaultRules() {
|
||||
addRule("BLOCK_LARGE_PACKETS", "size", ">", 10000, true, "Block oversized packets");
|
||||
addRule("BLOCK_NULL_PACKETS", "size", "==", 0, true, "Block null size packets");
|
||||
addRule("BLOCK_FRAGMENTED", "fragmented", "==", true, true, "Block fragmented packets");
|
||||
addRule("ALERT_SYN_FLOOD", "tcp_flags", "==", "SYN", false, "Alert on SYN packets");
|
||||
addRule("BLOCK_MALFORMED", "malformed", "==", true, true, "Block malformed packets");
|
||||
}
|
||||
|
||||
public void addRule(String name, String field, String operator, Object value, boolean block, String description) {
|
||||
PacketRule rule = new PacketRule(name, field, operator, value, block, description);
|
||||
rules.put(name, rule);
|
||||
}
|
||||
|
||||
public boolean inspectPacket(String sourceIP, Map<String, Object> packetData) {
|
||||
totalPacketsInspected.incrementAndGet();
|
||||
|
||||
int packetSize = getIntValue(packetData, "size");
|
||||
if (packetSize > MAX_PACKET_SIZE || packetSize < 0) {
|
||||
logPacket(sourceIP, "INVALID_SIZE", packetData);
|
||||
maliciousPacketsBlocked.incrementAndGet();
|
||||
return false;
|
||||
}
|
||||
|
||||
for (PacketRule rule : rules.values()) {
|
||||
if (!rule.enabled) {
|
||||
continue;
|
||||
}
|
||||
if (matchesRule(packetData, rule)) {
|
||||
if (rule.block) {
|
||||
logPacket(sourceIP, "BLOCKED:" + rule.name, packetData);
|
||||
alerts.offer(new PacketAlert(sourceIP, rule.name, "Packet blocked by rule: " + rule.description, System.currentTimeMillis()));
|
||||
maliciousPacketsBlocked.incrementAndGet();
|
||||
return false;
|
||||
} else {
|
||||
logPacket(sourceIP, "ALERT:" + rule.name, packetData);
|
||||
alerts.offer(new PacketAlert(sourceIP, rule.name, "Alert: " + rule.description, System.currentTimeMillis()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logPacket(sourceIP, "ALLOWED", packetData);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean matchesRule(Map<String, Object> packetData, PacketRule rule) {
|
||||
Object fieldValue = packetData.get(rule.field);
|
||||
if (fieldValue == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return switch (rule.operator) {
|
||||
case "==" -> fieldValue.equals(rule.value);
|
||||
case "!=" -> !fieldValue.equals(rule.value);
|
||||
case ">" -> compareValues(fieldValue, rule.value) > 0;
|
||||
case "<" -> compareValues(fieldValue, rule.value) < 0;
|
||||
case ">=" -> compareValues(fieldValue, rule.value) >= 0;
|
||||
case "<=" -> compareValues(fieldValue, rule.value) <= 0;
|
||||
case "contains" -> fieldValue.toString().contains(rule.value.toString());
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
private int compareValues(Object a, Object b) {
|
||||
if (a instanceof Number && b instanceof Number) {
|
||||
return Double.compare(((Number) a).doubleValue(), ((Number) b).doubleValue());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int getIntValue(Map<String, Object> data, String key) {
|
||||
Object value = data.get(key);
|
||||
if (value instanceof Number) {
|
||||
return ((Number) value).intValue();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private void logPacket(String sourceIP, String status, Map<String, Object> packetData) {
|
||||
PacketLog log = new PacketLog(sourceIP, status, packetData, System.currentTimeMillis());
|
||||
List<PacketLog> logs = packetLogs.computeIfAbsent(sourceIP, k -> new CopyOnWriteArrayList<>());
|
||||
logs.add(log);
|
||||
|
||||
while (logs.size() > MAX_LOGS_PER_IP) {
|
||||
logs.remove(0);
|
||||
}
|
||||
}
|
||||
|
||||
public List<PacketLog> getPacketLogs(String sourceIP, int limit) {
|
||||
List<PacketLog> logs = packetLogs.get(sourceIP);
|
||||
if (logs == null) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return logs.stream().limit(limit).toList();
|
||||
}
|
||||
|
||||
public List<PacketAlert> getAlerts(int limit) {
|
||||
List<PacketAlert> result = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (PacketAlert alert : alerts) {
|
||||
if (count++ >= limit) break;
|
||||
result.add(alert);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void enableRule(String ruleName) {
|
||||
PacketRule rule = rules.get(ruleName);
|
||||
if (rule != null) {
|
||||
rule.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void disableRule(String ruleName) {
|
||||
PacketRule rule = rules.get(ruleName);
|
||||
if (rule != null) {
|
||||
rule.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
public Collection<PacketRule> getRules() {
|
||||
return rules.values();
|
||||
}
|
||||
|
||||
private void startCleanupTask() {
|
||||
scheduler.scheduleAtFixedRate(() -> {
|
||||
long cutoff = System.currentTimeMillis() - 3600000;
|
||||
|
||||
packetLogs.entrySet().removeIf(entry -> {
|
||||
List<PacketLog> logs = entry.getValue();
|
||||
logs.removeIf(log -> log.timestamp < cutoff);
|
||||
return logs.isEmpty();
|
||||
});
|
||||
|
||||
alerts.removeIf(alert -> alert.timestamp < cutoff);
|
||||
|
||||
}, 300, 300, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public Map<String, Object> getStats() {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("totalInspected", totalPacketsInspected.get());
|
||||
stats.put("maliciousBlocked", maliciousPacketsBlocked.get());
|
||||
stats.put("activeRules", rules.size());
|
||||
return stats;
|
||||
}
|
||||
|
||||
public static class PacketRule {
|
||||
public final String name;
|
||||
public final String field;
|
||||
public final String operator;
|
||||
public final Object value;
|
||||
public final boolean block;
|
||||
public final String description;
|
||||
public volatile boolean enabled = true;
|
||||
|
||||
public PacketRule(String name, String field, String operator, Object value, boolean block, String description) {
|
||||
this.name = name;
|
||||
this.field = field;
|
||||
this.operator = operator;
|
||||
this.value = value;
|
||||
this.block = block;
|
||||
this.description = description;
|
||||
}
|
||||
}
|
||||
|
||||
public static class PacketLog {
|
||||
public final String sourceIP;
|
||||
public final String status;
|
||||
public final Map<String, Object> packetData;
|
||||
public final long timestamp;
|
||||
|
||||
public PacketLog(String sourceIP, String status, Map<String, Object> packetData, long timestamp) {
|
||||
this.sourceIP = sourceIP;
|
||||
this.status = status;
|
||||
this.packetData = packetData;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
public static class PacketAlert {
|
||||
public final String sourceIP;
|
||||
public final String ruleName;
|
||||
public final String details;
|
||||
public final long timestamp;
|
||||
|
||||
public PacketAlert(String sourceIP, String ruleName, String details, long timestamp) {
|
||||
this.sourceIP = sourceIP;
|
||||
this.ruleName = ruleName;
|
||||
this.details = details;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.*;
|
||||
import java.nio.file.*;
|
||||
import java.io.*;
|
||||
|
||||
@Service
|
||||
public class PortScanDetectionService {
|
||||
|
||||
private final Map<String, ScanRecord> scanRecords = new ConcurrentHashMap<>();
|
||||
private final Map<String, BlockedScanner> blockedScanners = new ConcurrentHashMap<>();
|
||||
private final Queue<ScanEvent> scanLog = new ConcurrentLinkedQueue<>();
|
||||
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
|
||||
|
||||
private static final int MAX_PORTS_SCANNED = 20;
|
||||
private static final long SCAN_WINDOW = 60000;
|
||||
private static final long BLOCK_DURATION = 3600000;
|
||||
private static final int MAX_SCAN_EVENTS = 100;
|
||||
|
||||
private final AtomicLong totalScansDetected = new AtomicLong(0);
|
||||
|
||||
public PortScanDetectionService() {
|
||||
startMonitoring();
|
||||
}
|
||||
|
||||
public boolean checkPortAccess(String sourceIP, int port) {
|
||||
if (isBlocked(sourceIP)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ScanRecord record = scanRecords.computeIfAbsent(sourceIP, k -> new ScanRecord(sourceIP));
|
||||
boolean isScanning = record.recordPortAccess(port);
|
||||
|
||||
if (isScanning) {
|
||||
blockScanner(sourceIP, "PORT_SCAN", "Scanned " + record.getScannedPortCount() + " ports");
|
||||
totalScansDetected.incrementAndGet();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean checkMultipleConnections(String sourceIP, Set<Integer> ports) {
|
||||
if (isBlocked(sourceIP)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ScanRecord record = scanRecords.computeIfAbsent(sourceIP, k -> new ScanRecord(sourceIP));
|
||||
boolean isScanning = record.recordMultipleConnections(ports);
|
||||
|
||||
if (isScanning) {
|
||||
blockScanner(sourceIP, "MULTI_PORT_CONNECTION", "Connected to " + ports.size() + " ports");
|
||||
totalScansDetected.incrementAndGet();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean checkConnectionRate(String sourceIP) {
|
||||
if (isBlocked(sourceIP)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ScanRecord record = scanRecords.computeIfAbsent(sourceIP, k -> new ScanRecord(sourceIP));
|
||||
boolean isRateExcessive = record.checkConnectionRate();
|
||||
|
||||
if (isRateExcessive) {
|
||||
blockScanner(sourceIP, "HIGH_CONNECTION_RATE", "Connection rate exceeded");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void recordConnection(String sourceIP, int port) {
|
||||
ScanRecord record = scanRecords.get(sourceIP);
|
||||
if (record != null) {
|
||||
record.recordConnection(port);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isBlocked(String ip) {
|
||||
BlockedScanner blocked = blockedScanners.get(ip);
|
||||
if (blocked == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (System.currentTimeMillis() - blocked.blockedAt > BLOCK_DURATION) {
|
||||
blockedScanners.remove(ip);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void blockScanner(String ip, String reason, String details) {
|
||||
BlockedScanner blocked = blockedScanners.computeIfAbsent(ip, k -> new BlockedScanner(ip));
|
||||
blocked.reason = reason;
|
||||
blocked.details = details;
|
||||
blocked.blockedAt = System.currentTimeMillis();
|
||||
blocked.count.incrementAndGet();
|
||||
|
||||
logScan(ip, reason, details);
|
||||
}
|
||||
|
||||
public void unblockScanner(String ip) {
|
||||
blockedScanners.remove(ip);
|
||||
}
|
||||
|
||||
public BlockedScanner getBlockInfo(String ip) {
|
||||
return blockedScanners.get(ip);
|
||||
}
|
||||
|
||||
public Collection<BlockedScanner> getBlockedScanners() {
|
||||
return blockedScanners.values();
|
||||
}
|
||||
|
||||
public ScanRecord getScanRecord(String ip) {
|
||||
return scanRecords.get(ip);
|
||||
}
|
||||
|
||||
public Collection<ScanRecord> getAllScanRecords() {
|
||||
return scanRecords.values();
|
||||
}
|
||||
|
||||
private void logScan(String ip, String reason, String details) {
|
||||
ScanEvent event = new ScanEvent(ip, reason, details, System.currentTimeMillis());
|
||||
scanLog.offer(event);
|
||||
|
||||
while (scanLog.size() > 1000) {
|
||||
scanLog.poll();
|
||||
}
|
||||
}
|
||||
|
||||
public List<ScanEvent> getScanLog(int limit) {
|
||||
List<ScanEvent> result = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (ScanEvent event : scanLog) {
|
||||
if (count++ >= limit) break;
|
||||
result.add(event);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void startMonitoring() {
|
||||
scheduler.scheduleAtFixedRate(() -> {
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
scanRecords.entrySet().removeIf(entry ->
|
||||
now - entry.getValue().lastActivity > 300000);
|
||||
|
||||
blockedScanners.entrySet().removeIf(entry ->
|
||||
now - entry.getValue().blockedAt > BLOCK_DURATION);
|
||||
|
||||
}, 30, 30, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public Map<String, Object> getStats() {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("activeScanners", scanRecords.size());
|
||||
stats.put("blockedScanners", blockedScanners.size());
|
||||
stats.put("totalDetected", totalScansDetected.get());
|
||||
return stats;
|
||||
}
|
||||
|
||||
public static class ScanRecord {
|
||||
private final String ip;
|
||||
private final Set<Integer> scannedPorts = ConcurrentHashMap.newKeySet();
|
||||
private final List<Long> connectionTimestamps = new ArrayList<>();
|
||||
private volatile long lastActivity;
|
||||
|
||||
public ScanRecord(String ip) {
|
||||
this.ip = ip;
|
||||
this.lastActivity = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public boolean recordPortAccess(int port) {
|
||||
scannedPorts.add(port);
|
||||
lastActivity = System.currentTimeMillis();
|
||||
|
||||
cleanupOldRecords();
|
||||
|
||||
return scannedPorts.size() > MAX_PORTS_SCANNED;
|
||||
}
|
||||
|
||||
public boolean recordMultipleConnections(Set<Integer> ports) {
|
||||
for (int port : ports) {
|
||||
scannedPorts.add(port);
|
||||
}
|
||||
lastActivity = System.currentTimeMillis();
|
||||
|
||||
cleanupOldRecords();
|
||||
|
||||
return scannedPorts.size() > MAX_PORTS_SCANNED;
|
||||
}
|
||||
|
||||
public void recordConnection(int port) {
|
||||
scannedPorts.add(port);
|
||||
connectionTimestamps.add(System.currentTimeMillis());
|
||||
lastActivity = System.currentTimeMillis();
|
||||
|
||||
cleanupOldRecords();
|
||||
}
|
||||
|
||||
public boolean checkConnectionRate() {
|
||||
cleanupOldRecords();
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
connectionTimestamps.removeIf(t -> now - t > 10000);
|
||||
|
||||
return connectionTimestamps.size() > MAX_SCAN_EVENTS;
|
||||
}
|
||||
|
||||
private void cleanupOldRecords() {
|
||||
long cutoff = System.currentTimeMillis() - SCAN_WINDOW;
|
||||
scannedPorts.removeIf(p -> false);
|
||||
connectionTimestamps.removeIf(t -> t < cutoff);
|
||||
}
|
||||
|
||||
public int getScannedPortCount() {
|
||||
return scannedPorts.size();
|
||||
}
|
||||
|
||||
public Set<Integer> getScannedPorts() {
|
||||
return new HashSet<>(scannedPorts);
|
||||
}
|
||||
}
|
||||
|
||||
public static class BlockedScanner {
|
||||
public final String ip;
|
||||
public String reason;
|
||||
public String details;
|
||||
public volatile long blockedAt;
|
||||
public final AtomicInteger count = new AtomicInteger(1);
|
||||
|
||||
public BlockedScanner(String ip) {
|
||||
this.ip = ip;
|
||||
this.blockedAt = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
|
||||
public static class ScanEvent {
|
||||
public final String ip;
|
||||
public final String reason;
|
||||
public final String details;
|
||||
public final long timestamp;
|
||||
|
||||
public ScanEvent(String ip, String reason, String details, long timestamp) {
|
||||
this.ip = ip;
|
||||
this.reason = reason;
|
||||
this.details = details;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,441 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
@Component
|
||||
public class ProtocolAnalysisService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ProtocolAnalysisService.class);
|
||||
|
||||
private final MinecraftProtocolSecurityService minecraftProtocolSecurityService;
|
||||
private final DDoSDefenseCoordinator ddosDefenseCoordinator;
|
||||
private final Map<String, ProtocolStatistics> protocolStats = new ConcurrentHashMap<>();
|
||||
private final Map<String, ConnectionState> connectionStates = new ConcurrentHashMap<>();
|
||||
private final Queue<ProtocolEvent> eventLog = new ConcurrentLinkedQueue<>();
|
||||
private final Map<String, List<PacketFragment>> fragmentBuffers = new ConcurrentHashMap<>();
|
||||
private final AtomicLong totalPacketsAnalyzed = new AtomicLong(0);
|
||||
private final AtomicLong totalMalformedPackets = new AtomicLong(0);
|
||||
private final AtomicLong totalSuspiciousPackets = new AtomicLong(0);
|
||||
|
||||
private static final int MAX_PACKET_SIZE = 1024 * 1024;
|
||||
private static final int FRAGMENT_TIMEOUT_MS = 5000;
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
public ProtocolAnalysisService() {
|
||||
this(new MinecraftProtocolSecurityService(), new DDoSDefenseCoordinator());
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public ProtocolAnalysisService(MinecraftProtocolSecurityService minecraftProtocolSecurityService,
|
||||
DDoSDefenseCoordinator ddosDefenseCoordinator) {
|
||||
this.minecraftProtocolSecurityService = minecraftProtocolSecurityService;
|
||||
this.ddosDefenseCoordinator = ddosDefenseCoordinator;
|
||||
initializeProtocols();
|
||||
}
|
||||
|
||||
private void initializeProtocols() {
|
||||
protocolStats.put("TCP", new ProtocolStatistics("TCP"));
|
||||
protocolStats.put("UDP", new ProtocolStatistics("UDP"));
|
||||
protocolStats.put("ICMP", new ProtocolStatistics("ICMP"));
|
||||
protocolStats.put("HTTP", new ProtocolStatistics("HTTP"));
|
||||
protocolStats.put("HTTPS", new ProtocolStatistics("HTTPS"));
|
||||
protocolStats.put("RCON", new ProtocolStatistics("RCON"));
|
||||
}
|
||||
|
||||
public AnalysisResult analyzePacket(byte[] packetData, String sourceIP, String destIP, int sourcePort, int destPort) {
|
||||
AnalysisResult result = new AnalysisResult();
|
||||
result.setSourceIP(sourceIP);
|
||||
result.setDestIP(destIP);
|
||||
result.setSourcePort(sourcePort);
|
||||
result.setDestPort(destPort);
|
||||
result.setTimestamp(LocalDateTime.now());
|
||||
|
||||
if (packetData == null || packetData.length == 0) {
|
||||
result.setValid(false);
|
||||
result.setReason("Empty packet");
|
||||
totalMalformedPackets.incrementAndGet();
|
||||
return result;
|
||||
}
|
||||
|
||||
if (packetData.length > MAX_PACKET_SIZE) {
|
||||
result.setValid(false);
|
||||
result.setReason("Packet too large");
|
||||
totalMalformedPackets.incrementAndGet();
|
||||
return result;
|
||||
}
|
||||
|
||||
totalPacketsAnalyzed.incrementAndGet();
|
||||
|
||||
String protocol = detectProtocol(packetData, destPort);
|
||||
result.setProtocol(protocol);
|
||||
|
||||
ProtocolStatistics stats = protocolStats.get(protocol);
|
||||
if (stats != null) {
|
||||
stats.incrementPacketCount();
|
||||
stats.addBytes(packetData.length);
|
||||
}
|
||||
|
||||
if (isMalformedPacket(packetData, protocol)) {
|
||||
result.setValid(false);
|
||||
result.setReason("Malformed packet structure");
|
||||
result.setThreatLevel("HIGH");
|
||||
totalMalformedPackets.incrementAndGet();
|
||||
|
||||
if (stats != null) {
|
||||
stats.incrementMalformedCount();
|
||||
}
|
||||
|
||||
logEvent(sourceIP, destIP, protocol, "MALFORMED", "Malformed packet detected");
|
||||
return result;
|
||||
}
|
||||
|
||||
if (isSuspiciousPacket(packetData, protocol)) {
|
||||
result.setThreatLevel("MEDIUM");
|
||||
result.addWarning("Suspicious packet pattern detected");
|
||||
totalSuspiciousPackets.incrementAndGet();
|
||||
|
||||
if (stats != null) {
|
||||
stats.incrementSuspiciousCount();
|
||||
}
|
||||
|
||||
logEvent(sourceIP, destIP, protocol, "SUSPICIOUS", "Suspicious packet pattern");
|
||||
}
|
||||
|
||||
List<MinecraftProtocolSecurityService.MinecraftThreatSignal> minecraftSignals =
|
||||
minecraftProtocolSecurityService.analyzePacket(sourceIP, destIP, sourcePort, destPort, packetData);
|
||||
for (MinecraftProtocolSecurityService.MinecraftThreatSignal signal : minecraftSignals) {
|
||||
result.addWarning(signal.getType() + ": " + signal.getDetails());
|
||||
result.setThreatLevel("HIGH");
|
||||
logEvent(sourceIP, destIP, "MINECRAFT", signal.getType(), signal.getDetails());
|
||||
}
|
||||
|
||||
for (DDoSDefenseCoordinator.DDoSIncidentSummary incident : ddosDefenseCoordinator.analyzeTraffic(
|
||||
sourceIP, destIP, sourcePort, destPort, protocol, packetData)) {
|
||||
result.addWarning(incident.getType() + ": " + incident.getDetails());
|
||||
if (!"HIGH".equals(result.getThreatLevel())) {
|
||||
result.setThreatLevel(incident.getSeverity() >= 85 ? "HIGH" : "MEDIUM");
|
||||
}
|
||||
}
|
||||
|
||||
if (isFragmentedPacket(packetData)) {
|
||||
result.setFragmented(true);
|
||||
handleFragment(sourceIP, packetData, result);
|
||||
}
|
||||
|
||||
updateConnectionState(sourceIP, destIP, protocol, result);
|
||||
|
||||
result.setValid(true);
|
||||
return result;
|
||||
}
|
||||
|
||||
private String detectProtocol(byte[] packetData, int destPort) {
|
||||
if (destPort == 25565) return "MINECRAFT";
|
||||
if (destPort == 25575) return "RCON";
|
||||
if (destPort == 80) return "HTTP";
|
||||
if (destPort == 443) return "HTTPS";
|
||||
|
||||
if (packetData.length >= 20) {
|
||||
int protocol = packetData[9] & 0xFF;
|
||||
switch (protocol) {
|
||||
case 6: return "TCP";
|
||||
case 17: return "UDP";
|
||||
case 1: return "ICMP";
|
||||
}
|
||||
}
|
||||
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
private boolean isMalformedPacket(byte[] packetData, String protocol) {
|
||||
if ("TCP".equals(protocol) || "MINECRAFT".equals(protocol) || "RCON".equals(protocol)) {
|
||||
if (packetData.length < 20) return true;
|
||||
|
||||
int headerLength = (packetData[0] & 0x0F) * 4;
|
||||
if (headerLength < 20) return true;
|
||||
|
||||
int dataOffset = headerLength;
|
||||
if (packetData.length > dataOffset) {
|
||||
byte flags = packetData[dataOffset];
|
||||
if ((flags & 0x3F) == 0) return false;
|
||||
|
||||
int payloadLength = packetData.length - dataOffset;
|
||||
if (payloadLength < 0) return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ("UDP".equals(protocol)) {
|
||||
if (packetData.length < 8) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isSuspiciousPacket(byte[] packetData, String protocol) {
|
||||
if (packetData.length > 65535) return true;
|
||||
|
||||
if (packetData.length > 1400 && "UDP".equals(protocol)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int i = 0; i < Math.min(packetData.length, 100); i++) {
|
||||
if (packetData[i] == 0x00 && i % 10 == 0) {
|
||||
int zeroCount = 0;
|
||||
for (int j = i; j < Math.min(i + 10, packetData.length); j++) {
|
||||
if (packetData[j] == 0x00) zeroCount++;
|
||||
}
|
||||
if (zeroCount > 8) return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isFragmentedPacket(byte[] packetData) {
|
||||
if (packetData.length < 20) return false;
|
||||
|
||||
short flags = (short) ((packetData[6] << 8) | (packetData[7] & 0xFF));
|
||||
boolean moreFragments = (flags & 0x2000) != 0;
|
||||
boolean fragmentOffset = (flags & 0x1FFF) != 0;
|
||||
|
||||
return moreFragments || fragmentOffset;
|
||||
}
|
||||
|
||||
private void handleFragment(String sourceIP, byte[] packetData, AnalysisResult result) {
|
||||
String key = sourceIP + "-" + result.getDestIP();
|
||||
List<PacketFragment> fragments = fragmentBuffers.computeIfAbsent(key, k -> new ArrayList<>());
|
||||
|
||||
short identification = (short) ((packetData[4] << 8) | (packetData[5] & 0xFF));
|
||||
short flags = (short) ((packetData[6] << 8) | (packetData[7] & 0xFF));
|
||||
short offset = (short) (flags & 0x1FFF);
|
||||
|
||||
PacketFragment fragment = new PacketFragment(identification, offset, packetData, System.currentTimeMillis());
|
||||
fragments.add(fragment);
|
||||
|
||||
cleanupOldFragments(fragments);
|
||||
|
||||
int totalLength = fragments.stream().mapToInt(f -> f.getData().length).sum();
|
||||
if (totalLength > MAX_PACKET_SIZE) {
|
||||
result.addWarning("Fragment buffer overflow prevented");
|
||||
fragments.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void cleanupOldFragments(List<PacketFragment> fragments) {
|
||||
long now = System.currentTimeMillis();
|
||||
fragments.removeIf(f -> now - f.getTimestamp() > FRAGMENT_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
private void updateConnectionState(String sourceIP, String destIP, String protocol, AnalysisResult result) {
|
||||
String key = sourceIP + "-" + destIP;
|
||||
ConnectionState state = connectionStates.computeIfAbsent(key, k -> new ConnectionState(sourceIP, destIP, protocol));
|
||||
|
||||
state.update(result);
|
||||
|
||||
if (state.getPacketCount() > 10000) {
|
||||
result.addWarning("High packet count on connection");
|
||||
logEvent(sourceIP, destIP, protocol, "HIGH_VOLUME", "Connection high volume warning");
|
||||
}
|
||||
|
||||
long connectionAge = System.currentTimeMillis() - state.getStartTime();
|
||||
if (connectionAge > 3600000 && state.getPacketCount() < 10) {
|
||||
result.addWarning("Stale connection detected");
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Object> getStats() {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("totalPacketsAnalyzed", totalPacketsAnalyzed.get());
|
||||
stats.put("totalMalformedPackets", totalMalformedPackets.get());
|
||||
stats.put("totalSuspiciousPackets", totalSuspiciousPackets.get());
|
||||
stats.put("activeConnections", connectionStates.size());
|
||||
|
||||
Map<String, ProtocolStatistics> protoStats = new HashMap<>();
|
||||
for (Map.Entry<String, ProtocolStatistics> entry : protocolStats.entrySet()) {
|
||||
protoStats.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
stats.put("protocolStats", protoStats);
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
public Map<String, ConnectionState> getConnectionStates() {
|
||||
return new HashMap<>(connectionStates);
|
||||
}
|
||||
|
||||
public List<ProtocolEvent> getRecentEvents(int limit) {
|
||||
List<ProtocolEvent> events = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (ProtocolEvent event : eventLog) {
|
||||
if (count++ >= limit) break;
|
||||
events.add(event);
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
private void logEvent(String sourceIP, String destIP, String protocol, String eventType, String details) {
|
||||
ProtocolEvent event = new ProtocolEvent(
|
||||
sourceIP, destIP, protocol, eventType, details, LocalDateTime.now()
|
||||
);
|
||||
eventLog.offer(event);
|
||||
|
||||
if (eventLog.size() > 10000) {
|
||||
eventLog.poll();
|
||||
}
|
||||
}
|
||||
|
||||
public static class AnalysisResult {
|
||||
private String sourceIP;
|
||||
private String destIP;
|
||||
private int sourcePort;
|
||||
private int destPort;
|
||||
private String protocol;
|
||||
private boolean valid;
|
||||
private String reason;
|
||||
private String threatLevel;
|
||||
private boolean fragmented;
|
||||
private List<String> warnings = new ArrayList<>();
|
||||
private LocalDateTime timestamp;
|
||||
|
||||
public String getSourceIP() { return sourceIP; }
|
||||
public void setSourceIP(String sourceIP) { this.sourceIP = sourceIP; }
|
||||
public String getDestIP() { return destIP; }
|
||||
public void setDestIP(String destIP) { this.destIP = destIP; }
|
||||
public int getSourcePort() { return sourcePort; }
|
||||
public void setSourcePort(int sourcePort) { this.sourcePort = sourcePort; }
|
||||
public int getDestPort() { return destPort; }
|
||||
public void setDestPort(int destPort) { this.destPort = destPort; }
|
||||
public String getProtocol() { return protocol; }
|
||||
public void setProtocol(String protocol) { this.protocol = protocol; }
|
||||
public boolean isValid() { return valid; }
|
||||
public void setValid(boolean valid) { this.valid = valid; }
|
||||
public String getReason() { return reason; }
|
||||
public void setReason(String reason) { this.reason = reason; }
|
||||
public String getThreatLevel() { return threatLevel; }
|
||||
public void setThreatLevel(String threatLevel) { this.threatLevel = threatLevel; }
|
||||
public boolean isFragmented() { return fragmented; }
|
||||
public void setFragmented(boolean fragmented) { this.fragmented = fragmented; }
|
||||
public List<String> getWarnings() { return warnings; }
|
||||
public void addWarning(String warning) { this.warnings.add(warning); }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
public void setTimestamp(LocalDateTime timestamp) { this.timestamp = timestamp; }
|
||||
}
|
||||
|
||||
public static class ProtocolStatistics {
|
||||
private final String protocolName;
|
||||
private final AtomicLong packetCount = new AtomicLong(0);
|
||||
private final AtomicLong byteCount = new AtomicLong(0);
|
||||
private final AtomicLong malformedCount = new AtomicLong(0);
|
||||
private final AtomicLong suspiciousCount = new AtomicLong(0);
|
||||
|
||||
public ProtocolStatistics(String protocolName) {
|
||||
this.protocolName = protocolName;
|
||||
}
|
||||
|
||||
public void incrementPacketCount() { packetCount.incrementAndGet(); }
|
||||
public void addBytes(int bytes) { byteCount.addAndGet(bytes); }
|
||||
public void incrementMalformedCount() { malformedCount.incrementAndGet(); }
|
||||
public void incrementSuspiciousCount() { suspiciousCount.incrementAndGet(); }
|
||||
|
||||
public String getProtocolName() { return protocolName; }
|
||||
public long getPacketCount() { return packetCount.get(); }
|
||||
public long getByteCount() { return byteCount.get(); }
|
||||
public long getMalformedCount() { return malformedCount.get(); }
|
||||
public long getSuspiciousCount() { return suspiciousCount.get(); }
|
||||
}
|
||||
|
||||
public static class ConnectionState {
|
||||
private final String sourceIP;
|
||||
private final String destIP;
|
||||
private final String protocol;
|
||||
private final long startTime;
|
||||
private volatile long lastPacketTime;
|
||||
private volatile long packetCount;
|
||||
private volatile long totalBytes;
|
||||
private volatile String currentState;
|
||||
|
||||
public ConnectionState(String sourceIP, String destIP, String protocol) {
|
||||
this.sourceIP = sourceIP;
|
||||
this.destIP = destIP;
|
||||
this.protocol = protocol;
|
||||
this.startTime = System.currentTimeMillis();
|
||||
this.lastPacketTime = startTime;
|
||||
this.packetCount = 0;
|
||||
this.totalBytes = 0;
|
||||
this.currentState = "ESTABLISHED";
|
||||
}
|
||||
|
||||
public synchronized void update(AnalysisResult result) {
|
||||
lastPacketTime = System.currentTimeMillis();
|
||||
packetCount++;
|
||||
if (!result.isValid()) {
|
||||
currentState = "ERROR";
|
||||
} else if ("HIGH".equals(result.getThreatLevel())) {
|
||||
currentState = "THREAT";
|
||||
}
|
||||
}
|
||||
|
||||
public String getSourceIP() { return sourceIP; }
|
||||
public String getDestIP() { return destIP; }
|
||||
public String getProtocol() { return protocol; }
|
||||
public long getStartTime() { return startTime; }
|
||||
public long getLastPacketTime() { return lastPacketTime; }
|
||||
public long getPacketCount() { return packetCount; }
|
||||
public long getTotalBytes() { return totalBytes; }
|
||||
public String getCurrentState() { return currentState; }
|
||||
}
|
||||
|
||||
public static class PacketFragment {
|
||||
private final short identification;
|
||||
private final short offset;
|
||||
private final byte[] data;
|
||||
private final long timestamp;
|
||||
|
||||
public PacketFragment(short identification, short offset, byte[] data, long timestamp) {
|
||||
this.identification = identification;
|
||||
this.offset = offset;
|
||||
this.data = data;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public short getIdentification() { return identification; }
|
||||
public short getOffset() { return offset; }
|
||||
public byte[] getData() { return data; }
|
||||
public long getTimestamp() { return timestamp; }
|
||||
}
|
||||
|
||||
public static class ProtocolEvent {
|
||||
private final String sourceIP;
|
||||
private final String destIP;
|
||||
private final String protocol;
|
||||
private final String eventType;
|
||||
private final String details;
|
||||
private final LocalDateTime timestamp;
|
||||
|
||||
public ProtocolEvent(String sourceIP, String destIP, String protocol,
|
||||
String eventType, String details, LocalDateTime timestamp) {
|
||||
this.sourceIP = sourceIP;
|
||||
this.destIP = destIP;
|
||||
this.protocol = protocol;
|
||||
this.eventType = eventType;
|
||||
this.details = details;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getSourceIP() { return sourceIP; }
|
||||
public String getDestIP() { return destIP; }
|
||||
public String getProtocol() { return protocol; }
|
||||
public String getEventType() { return eventType; }
|
||||
public String getDetails() { return details; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.*;
|
||||
import java.security.MessageDigest;
|
||||
|
||||
@Service
|
||||
public class RateLimitService {
|
||||
|
||||
private final Map<String, RateLimitRule> rules = new ConcurrentHashMap<>();
|
||||
private final Map<String, List<Long>> requestTimestamps = new ConcurrentHashMap<>();
|
||||
private final Map<String, AtomicInteger> requestCounts = new ConcurrentHashMap<>();
|
||||
private final Map<String, RateLimitStats> stats = new ConcurrentHashMap<>();
|
||||
private final Queue<RateLimitEvent> events = new ConcurrentLinkedQueue<>();
|
||||
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
|
||||
|
||||
private static final int DEFAULT_MAX_REQUESTS = 100;
|
||||
private static final long DEFAULT_WINDOW_MS = 60000;
|
||||
private static final long BAN_DURATION = 300000;
|
||||
|
||||
public RateLimitService() {
|
||||
initializeDefaultRules();
|
||||
startCleanupTask();
|
||||
}
|
||||
|
||||
private void initializeDefaultRules() {
|
||||
addRule("api", DEFAULT_MAX_REQUESTS, DEFAULT_WINDOW_MS, "API requests");
|
||||
addRule("login", 5, 60000, "Login attempts");
|
||||
addRule("chat", 10, 5000, "Chat messages");
|
||||
addRule("command", 30, 60000, "Commands");
|
||||
addRule("file_upload", 5, 60000, "File uploads");
|
||||
}
|
||||
|
||||
public void addRule(String name, int maxRequests, long windowMs, String description) {
|
||||
RateLimitRule rule = new RateLimitRule(name, maxRequests, windowMs, description);
|
||||
rules.put(name, rule);
|
||||
}
|
||||
|
||||
public boolean checkRateLimit(String identifier, String ruleName) {
|
||||
RateLimitRule rule = rules.get(ruleName);
|
||||
if (rule == null) {
|
||||
rule = rules.get("api");
|
||||
}
|
||||
|
||||
if (rule == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final RateLimitRule finalRule = rule;
|
||||
String key = identifier + ":" + ruleName;
|
||||
List<Long> timestamps = requestTimestamps.computeIfAbsent(key, k -> new CopyOnWriteArrayList<>());
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
timestamps.removeIf(t -> now - t > finalRule.windowMs);
|
||||
|
||||
if (timestamps.size() >= finalRule.maxRequests) {
|
||||
recordRejection(identifier, ruleName, "Rate limit exceeded");
|
||||
return false;
|
||||
}
|
||||
|
||||
timestamps.add(now);
|
||||
|
||||
recordRequest(identifier, ruleName);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void recordRejection(String identifier, String ruleName, String reason) {
|
||||
RateLimitStats stat = stats.computeIfAbsent(identifier, k -> new RateLimitStats(identifier));
|
||||
stat.rejections.incrementAndGet();
|
||||
|
||||
RateLimitEvent event = new RateLimitEvent(identifier, ruleName, reason, false, System.currentTimeMillis());
|
||||
events.offer(event);
|
||||
|
||||
while (events.size() > 1000) {
|
||||
events.poll();
|
||||
}
|
||||
}
|
||||
|
||||
public void recordRequest(String identifier, String ruleName) {
|
||||
RateLimitStats stat = stats.computeIfAbsent(identifier, k -> new RateLimitStats(identifier));
|
||||
stat.requests.incrementAndGet();
|
||||
|
||||
AtomicInteger count = requestCounts.computeIfAbsent(identifier, k -> new AtomicInteger(0));
|
||||
count.incrementAndGet();
|
||||
}
|
||||
|
||||
public void resetLimits(String identifier) {
|
||||
String prefix = identifier + ":";
|
||||
requestTimestamps.keySet().removeIf(k -> k.startsWith(prefix));
|
||||
stats.remove(identifier);
|
||||
requestCounts.remove(identifier);
|
||||
}
|
||||
|
||||
public RateLimitStats getStats(String identifier) {
|
||||
return stats.get(identifier);
|
||||
}
|
||||
|
||||
public Map<String, RateLimitStats> getAllStats() {
|
||||
return new HashMap<>(stats);
|
||||
}
|
||||
|
||||
public List<RateLimitEvent> getEvents(int limit) {
|
||||
List<RateLimitEvent> result = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (RateLimitEvent event : events) {
|
||||
if (count++ >= limit) break;
|
||||
result.add(event);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public Collection<RateLimitRule> getRules() {
|
||||
return rules.values();
|
||||
}
|
||||
|
||||
private void startCleanupTask() {
|
||||
scheduler.scheduleAtFixedRate(() -> {
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
for (Map.Entry<String, List<Long>> entry : requestTimestamps.entrySet()) {
|
||||
List<Long> list = entry.getValue();
|
||||
String ruleName = entry.getKey().split(":")[1];
|
||||
RateLimitRule rule = rules.get(ruleName);
|
||||
if (rule != null) {
|
||||
list.removeIf(t -> now - t > rule.windowMs);
|
||||
if (list.isEmpty()) {
|
||||
requestTimestamps.remove(entry.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stats.entrySet().removeIf(entry ->
|
||||
entry.getValue().requests.get() == 0 &&
|
||||
entry.getValue().rejections.get() == 0);
|
||||
|
||||
}, 60, 60, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public Map<String, Object> getStats() {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("rulesCount", rules.size());
|
||||
result.put("trackedIdentifiers", stats.size());
|
||||
result.put("totalRequests", stats.values().stream()
|
||||
.mapToLong(s -> s.requests.get())
|
||||
.sum());
|
||||
result.put("totalRejections", stats.values().stream()
|
||||
.mapToLong(s -> s.rejections.get())
|
||||
.sum());
|
||||
return result;
|
||||
}
|
||||
|
||||
public static class RateLimitRule {
|
||||
public final String name;
|
||||
public final int maxRequests;
|
||||
public final long windowMs;
|
||||
public final String description;
|
||||
|
||||
public RateLimitRule(String name, int maxRequests, long windowMs, String description) {
|
||||
this.name = name;
|
||||
this.maxRequests = maxRequests;
|
||||
this.windowMs = windowMs;
|
||||
this.description = description;
|
||||
}
|
||||
}
|
||||
|
||||
public static class RateLimitStats {
|
||||
public final String identifier;
|
||||
public final AtomicLong requests = new AtomicLong(0);
|
||||
public final AtomicLong rejections = new AtomicLong(0);
|
||||
|
||||
public RateLimitStats(String identifier) {
|
||||
this.identifier = identifier;
|
||||
}
|
||||
}
|
||||
|
||||
public static class RateLimitEvent {
|
||||
public final String identifier;
|
||||
public final String ruleName;
|
||||
public final String reason;
|
||||
public final boolean allowed;
|
||||
public final long timestamp;
|
||||
|
||||
public RateLimitEvent(String identifier, String ruleName, String reason, boolean allowed, long timestamp) {
|
||||
this.identifier = identifier;
|
||||
this.ruleName = ruleName;
|
||||
this.reason = reason;
|
||||
this.allowed = allowed;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.Duration;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Component
|
||||
public class SIEMService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(SIEMService.class);
|
||||
|
||||
private final Map<String, SecurityEvent> eventDatabase = new ConcurrentHashMap<>();
|
||||
private final Queue<Alert> alertQueue = new ConcurrentLinkedQueue<>();
|
||||
private final Map<String, CorrelationRule> correlationRules = new ConcurrentHashMap<>();
|
||||
private final Map<String, List<SecurityEvent>> eventTimeline = new ConcurrentHashMap<>();
|
||||
private final AtomicLong totalEvents = new AtomicLong(0);
|
||||
private final AtomicLong totalAlerts = new AtomicLong(0);
|
||||
private final Map<String, AtomicLong> eventTypeCounts = new ConcurrentHashMap<>();
|
||||
|
||||
private static final int MAX_EVENTS = 100000;
|
||||
private static final int ALERT_THRESHOLD = 10;
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
public SIEMService() {
|
||||
initializeCorrelationRules();
|
||||
logger.info("SIEM Service initialized");
|
||||
}
|
||||
|
||||
private void initializeCorrelationRules() {
|
||||
addCorrelationRule("BRUTE_FORCE_ATTACK",
|
||||
Arrays.asList("LOGIN_FAILED", "LOGIN_FAILED", "LOGIN_FAILED"),
|
||||
"在短时间内多次登录失败",
|
||||
3, 300);
|
||||
|
||||
addCorrelationRule("DDOS_DETECTED",
|
||||
Arrays.asList("HIGH_CONNECTION", "HIGH_CONNECTION", "HIGH_CONNECTION", "HIGH_CONNECTION"),
|
||||
"检测到DDoS攻击特征",
|
||||
4, 60);
|
||||
|
||||
addCorrelationRule("DATA_EXFILTRATION",
|
||||
Arrays.asList("LARGE_UPLOAD", "UNUSUAL_ACCESS", "LARGE_UPLOAD"),
|
||||
"可能的数据外泄",
|
||||
3, 600);
|
||||
|
||||
addCorrelationRule("PRIVILEGE_ESCALATION",
|
||||
Arrays.asList("ADMIN_LOGIN", "USER_CREATED", "PERMISSION_CHANGED"),
|
||||
"权限提升攻击",
|
||||
3, 300);
|
||||
|
||||
addCorrelationRule("PERSISTENT_THREAT",
|
||||
Arrays.asList("SUSPICIOUS_PROCESS", "SCHEDULED_TASK", "STARTUP_ITEM"),
|
||||
"可能存在持久性威胁",
|
||||
3, 3600);
|
||||
|
||||
addCorrelationRule("LATERAL_MOVEMENT",
|
||||
Arrays.asList("NEW_SERVICE", "PORT_OPEN", "REMOTE_ACCESS"),
|
||||
"横向移动检测",
|
||||
3, 600);
|
||||
|
||||
logger.info("Initialized {} correlation rules", correlationRules.size());
|
||||
}
|
||||
|
||||
public void addCorrelationRule(String name, List<String> sequence, String description, int minMatches, int timeWindowSeconds) {
|
||||
CorrelationRule rule = new CorrelationRule(name, sequence, description, minMatches, timeWindowSeconds);
|
||||
correlationRules.put(name, rule);
|
||||
logger.info("Added correlation rule: {}", name);
|
||||
}
|
||||
|
||||
public void logEvent(String source, String eventType, String severity, String description, Map<String, Object> metadata) {
|
||||
String eventId = UUID.randomUUID().toString();
|
||||
SecurityEvent event = new SecurityEvent(eventId, source, eventType, severity, description, metadata, LocalDateTime.now());
|
||||
|
||||
eventDatabase.put(eventId, event);
|
||||
totalEvents.incrementAndGet();
|
||||
|
||||
eventTypeCounts.computeIfAbsent(eventType, k -> new AtomicLong(0)).incrementAndGet();
|
||||
|
||||
eventTimeline.computeIfAbsent(source, k -> new ArrayList<>()).add(event);
|
||||
|
||||
cleanupOldEvents();
|
||||
|
||||
checkCorrelationRules(event);
|
||||
|
||||
logger.debug("Logged security event: {} [{}]", eventType, severity);
|
||||
}
|
||||
|
||||
public void logSimpleEvent(String source, String eventType, String severity, String description) {
|
||||
logEvent(source, eventType, severity, description, new HashMap<>());
|
||||
}
|
||||
|
||||
private void checkCorrelationRules(SecurityEvent event) {
|
||||
for (CorrelationRule rule : correlationRules.values()) {
|
||||
if (!rule.isEnabled()) continue;
|
||||
|
||||
List<SecurityEvent> recentEvents = eventTimeline.get(event.getSource());
|
||||
if (recentEvents == null || recentEvents.size() < rule.getMinMatches()) continue;
|
||||
|
||||
List<String> recentTypes = recentEvents.stream()
|
||||
.map(SecurityEvent::getEventType)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (rule.matchesSequence(recentTypes)) {
|
||||
triggerAlert(rule.getName(), rule.getDescription(), "HIGH", event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void triggerAlert(String ruleName, String description, String severity, SecurityEvent relatedEvent) {
|
||||
String alertId = UUID.randomUUID().toString();
|
||||
Alert alert = new Alert(alertId, ruleName, description, severity, relatedEvent, LocalDateTime.now());
|
||||
|
||||
alertQueue.offer(alert);
|
||||
totalAlerts.incrementAndGet();
|
||||
|
||||
logger.warn("ALERT: {} - {}", ruleName, description);
|
||||
}
|
||||
|
||||
public List<SecurityEvent> queryEvents(EventQuery query) {
|
||||
return eventDatabase.values().stream()
|
||||
.filter(e -> query.getEventType() == null || e.getEventType().equals(query.getEventType()))
|
||||
.filter(e -> query.getSeverity() == null || e.getSeverity().equals(query.getSeverity()))
|
||||
.filter(e -> query.getSource() == null || e.getSource().contains(query.getSource()))
|
||||
.filter(e -> query.getStartTime() == null || e.getTimestamp().isAfter(query.getStartTime()))
|
||||
.filter(e -> query.getEndTime() == null || e.getTimestamp().isBefore(query.getEndTime()))
|
||||
.sorted((a, b) -> b.getTimestamp().compareTo(a.getTimestamp()))
|
||||
.limit(query.getLimit())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public Map<String, Object> getDashboardData() {
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
|
||||
data.put("totalEvents", totalEvents.get());
|
||||
data.put("totalAlerts", totalAlerts.get());
|
||||
|
||||
Map<String, Long> typeCounts = new HashMap<>();
|
||||
for (Map.Entry<String, AtomicLong> entry : eventTypeCounts.entrySet()) {
|
||||
typeCounts.put(entry.getKey(), entry.getValue().get());
|
||||
}
|
||||
data.put("eventTypeCounts", typeCounts);
|
||||
|
||||
Map<String, Long> severityCounts = new HashMap<>();
|
||||
severityCounts.put("CRITICAL", eventDatabase.values().stream().filter(e -> "CRITICAL".equals(e.getSeverity())).count());
|
||||
severityCounts.put("HIGH", eventDatabase.values().stream().filter(e -> "HIGH".equals(e.getSeverity())).count());
|
||||
severityCounts.put("MEDIUM", eventDatabase.values().stream().filter(e -> "MEDIUM".equals(e.getSeverity())).count());
|
||||
severityCounts.put("LOW", eventDatabase.values().stream().filter(e -> "LOW".equals(e.getSeverity())).count());
|
||||
data.put("severityCounts", severityCounts);
|
||||
|
||||
LocalDateTime oneHourAgo = LocalDateTime.now().minusHours(1);
|
||||
long recentEvents = eventDatabase.values().stream()
|
||||
.filter(e -> e.getTimestamp().isAfter(oneHourAgo))
|
||||
.count();
|
||||
data.put("eventsLastHour", recentEvents);
|
||||
|
||||
List<Alert> recentAlerts = alertQueue.stream()
|
||||
.sorted((a, b) -> b.getTimestamp().compareTo(a.getTimestamp()))
|
||||
.limit(10)
|
||||
.collect(Collectors.toList());
|
||||
data.put("recentAlerts", recentAlerts);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public List<Alert> getAlerts(String severity, int limit) {
|
||||
return alertQueue.stream()
|
||||
.filter(a -> severity == null || a.getSeverity().equals(severity))
|
||||
.sorted((a, b) -> b.getTimestamp().compareTo(a.getTimestamp()))
|
||||
.limit(limit)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public Map<String, Object> getThreatAnalysis() {
|
||||
Map<String, Object> analysis = new HashMap<>();
|
||||
|
||||
Map<String, Long> sourceCounts = eventDatabase.values().stream()
|
||||
.collect(Collectors.groupingBy(SecurityEvent::getSource, Collectors.counting()));
|
||||
|
||||
List<Map.Entry<String, Long>> topSources = sourceCounts.entrySet().stream()
|
||||
.sorted(Map.Entry.<String, Long>comparingByValue().reversed())
|
||||
.limit(10)
|
||||
.collect(Collectors.toList());
|
||||
analysis.put("topEventSources", topSources);
|
||||
|
||||
Map<String, Long> eventTypeAnalysis = eventDatabase.values().stream()
|
||||
.collect(Collectors.groupingBy(SecurityEvent::getEventType, Collectors.counting()));
|
||||
|
||||
List<Map.Entry<String, Long>> topEventTypes = eventTypeAnalysis.entrySet().stream()
|
||||
.sorted(Map.Entry.<String, Long>comparingByValue().reversed())
|
||||
.limit(10)
|
||||
.collect(Collectors.toList());
|
||||
analysis.put("topEventTypes", topEventTypes);
|
||||
|
||||
Map<String, Long> attackPatterns = new HashMap<>();
|
||||
long bruteForce = eventTypeCounts.getOrDefault("LOGIN_FAILED", new AtomicLong(0)).get();
|
||||
attackPatterns.put("bruteForce", bruteForce);
|
||||
|
||||
long ddos = eventTypeCounts.getOrDefault("HIGH_CONNECTION", new AtomicLong(0)).get();
|
||||
attackPatterns.put("ddos", ddos);
|
||||
|
||||
long sqlInjection = eventTypeCounts.getOrDefault("SQL_INJECTION", new AtomicLong(0)).get();
|
||||
attackPatterns.put("sqlInjection", sqlInjection);
|
||||
|
||||
analysis.put("attackPatterns", attackPatterns);
|
||||
|
||||
return analysis;
|
||||
}
|
||||
|
||||
private void cleanupOldEvents() {
|
||||
if (eventDatabase.size() > MAX_EVENTS) {
|
||||
List<SecurityEvent> sortedEvents = eventDatabase.values().stream()
|
||||
.sorted((a, b) -> a.getTimestamp().compareTo(b.getTimestamp()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
int toRemove = eventDatabase.size() - (MAX_EVENTS / 2);
|
||||
for (int i = 0; i < toRemove && i < sortedEvents.size(); i++) {
|
||||
eventDatabase.remove(sortedEvents.get(i).getEventId());
|
||||
}
|
||||
|
||||
logger.info("Cleaned up old events, remaining: {}", eventDatabase.size());
|
||||
}
|
||||
}
|
||||
|
||||
public void enableRule(String ruleName) {
|
||||
CorrelationRule rule = correlationRules.get(ruleName);
|
||||
if (rule != null) {
|
||||
rule.setEnabled(true);
|
||||
logger.info("Enabled correlation rule: {}", ruleName);
|
||||
}
|
||||
}
|
||||
|
||||
public void disableRule(String ruleName) {
|
||||
CorrelationRule rule = correlationRules.get(ruleName);
|
||||
if (rule != null) {
|
||||
rule.setEnabled(false);
|
||||
logger.info("Disabled correlation rule: {}", ruleName);
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Object> getStats() {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("totalEvents", totalEvents.get());
|
||||
stats.put("totalAlerts", totalAlerts.get());
|
||||
stats.put("activeEvents", eventDatabase.size());
|
||||
stats.put("correlationRules", correlationRules.size());
|
||||
return stats;
|
||||
}
|
||||
|
||||
public static class SecurityEvent {
|
||||
private final String eventId;
|
||||
private final String source;
|
||||
private final String eventType;
|
||||
private final String severity;
|
||||
private final String description;
|
||||
private final Map<String, Object> metadata;
|
||||
private final LocalDateTime timestamp;
|
||||
|
||||
public SecurityEvent(String eventId, String source, String eventType, String severity,
|
||||
String description, Map<String, Object> metadata, LocalDateTime timestamp) {
|
||||
this.eventId = eventId;
|
||||
this.source = source;
|
||||
this.eventType = eventType;
|
||||
this.severity = severity;
|
||||
this.description = description;
|
||||
this.metadata = metadata;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getEventId() { return eventId; }
|
||||
public String getSource() { return source; }
|
||||
public String getEventType() { return eventType; }
|
||||
public String getSeverity() { return severity; }
|
||||
public String getDescription() { return description; }
|
||||
public Map<String, Object> getMetadata() { return metadata; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
}
|
||||
|
||||
public static class Alert {
|
||||
private final String alertId;
|
||||
private final String ruleName;
|
||||
private final String description;
|
||||
private final String severity;
|
||||
private final SecurityEvent relatedEvent;
|
||||
private final LocalDateTime timestamp;
|
||||
|
||||
public Alert(String alertId, String ruleName, String description, String severity,
|
||||
SecurityEvent relatedEvent, LocalDateTime timestamp) {
|
||||
this.alertId = alertId;
|
||||
this.ruleName = ruleName;
|
||||
this.description = description;
|
||||
this.severity = severity;
|
||||
this.relatedEvent = relatedEvent;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getAlertId() { return alertId; }
|
||||
public String getRuleName() { return ruleName; }
|
||||
public String getDescription() { return description; }
|
||||
public String getSeverity() { return severity; }
|
||||
public SecurityEvent getRelatedEvent() { return relatedEvent; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
}
|
||||
|
||||
public static class CorrelationRule {
|
||||
private final String name;
|
||||
private final List<String> sequence;
|
||||
private final String description;
|
||||
private final int minMatches;
|
||||
private final int timeWindowSeconds;
|
||||
private volatile boolean enabled = true;
|
||||
|
||||
public CorrelationRule(String name, List<String> sequence, String description, int minMatches, int timeWindowSeconds) {
|
||||
this.name = name;
|
||||
this.sequence = sequence;
|
||||
this.description = description;
|
||||
this.minMatches = minMatches;
|
||||
this.timeWindowSeconds = timeWindowSeconds;
|
||||
}
|
||||
|
||||
public boolean matchesSequence(List<String> eventTypes) {
|
||||
if (eventTypes.size() < minMatches) return false;
|
||||
|
||||
int matches = 0;
|
||||
for (int i = 0; i <= eventTypes.size() - sequence.size(); i++) {
|
||||
boolean match = true;
|
||||
for (int j = 0; j < sequence.size(); j++) {
|
||||
if (!eventTypes.get(i + j).equals(sequence.get(j))) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public String getName() { return name; }
|
||||
public List<String> getSequence() { return sequence; }
|
||||
public String getDescription() { return description; }
|
||||
public int getMinMatches() { return minMatches; }
|
||||
public int getTimeWindowSeconds() { return timeWindowSeconds; }
|
||||
public boolean isEnabled() { return enabled; }
|
||||
public void setEnabled(boolean enabled) { this.enabled = enabled; }
|
||||
}
|
||||
|
||||
public static class EventQuery {
|
||||
private String eventType;
|
||||
private String severity;
|
||||
private String source;
|
||||
private LocalDateTime startTime;
|
||||
private LocalDateTime endTime;
|
||||
private int limit = 100;
|
||||
|
||||
public String getEventType() { return eventType; }
|
||||
public void setEventType(String eventType) { this.eventType = eventType; }
|
||||
public String getSeverity() { return severity; }
|
||||
public void setSeverity(String severity) { this.severity = severity; }
|
||||
public String getSource() { return source; }
|
||||
public void setSource(String source) { this.source = source; }
|
||||
public LocalDateTime getStartTime() { return startTime; }
|
||||
public void setStartTime(LocalDateTime startTime) { this.startTime = startTime; }
|
||||
public LocalDateTime getEndTime() { return endTime; }
|
||||
public void setEndTime(LocalDateTime endTime) { this.endTime = endTime; }
|
||||
public int getLimit() { return limit; }
|
||||
public void setLimit(int limit) { this.limit = limit; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.*;
|
||||
|
||||
@Service
|
||||
public class SSLMonitorService {
|
||||
|
||||
private final Map<String, SSLSession> sslSessions = new ConcurrentHashMap<>();
|
||||
private final Queue<SSLAlert> alerts = new ConcurrentLinkedQueue<>();
|
||||
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
|
||||
|
||||
private static final long SESSION_TIMEOUT = 60000;
|
||||
private static final int MAX_CERTIFICATES = 1000;
|
||||
|
||||
private final AtomicLong totalHandshakes = new AtomicLong(0);
|
||||
private final AtomicLong failedHandshakes = new AtomicLong(0);
|
||||
|
||||
public SSLMonitorService() {
|
||||
startMonitoringTask();
|
||||
}
|
||||
|
||||
public boolean recordHandshake(String ip, boolean success, String details) {
|
||||
totalHandshakes.incrementAndGet();
|
||||
|
||||
if (!success) {
|
||||
failedHandshakes.incrementAndGet();
|
||||
addAlert(ip, "HANDSHAKE_FAILED", details);
|
||||
return false;
|
||||
}
|
||||
|
||||
SSLSession session = sslSessions.computeIfAbsent(ip, k -> new SSLSession(ip));
|
||||
session.recordHandshake(success);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void recordCertificate(String ip, String certInfo) {
|
||||
SSLSession session = sslSessions.get(ip);
|
||||
if (session != null) {
|
||||
session.addCertificate(certInfo);
|
||||
}
|
||||
}
|
||||
|
||||
public void recordCipherSuite(String ip, String cipher) {
|
||||
SSLSession session = sslSessions.get(ip);
|
||||
if (session != null) {
|
||||
session.setCipherSuite(cipher);
|
||||
}
|
||||
}
|
||||
|
||||
public void recordProtocolVersion(String ip, String version) {
|
||||
SSLSession session = sslSessions.get(ip);
|
||||
if (session != null) {
|
||||
session.setProtocolVersion(version);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isWeakCipher(String ip) {
|
||||
SSLSession session = sslSessions.get(ip);
|
||||
if (session == null || session.cipherSuite == null) {
|
||||
return false;
|
||||
}
|
||||
return isWeakCipherSuite(session.cipherSuite);
|
||||
}
|
||||
|
||||
private boolean isWeakCipherSuite(String cipher) {
|
||||
String[] weakCiphers = {"SSL", "TLSv1.0", "TLSv1.1", "3DES", "RC4", "MD5"};
|
||||
for (String weak : weakCiphers) {
|
||||
if (cipher.toUpperCase().contains(weak)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isOutdatedProtocol(String ip) {
|
||||
SSLSession session = sslSessions.get(ip);
|
||||
if (session == null || session.protocolVersion == null) {
|
||||
return false;
|
||||
}
|
||||
return session.protocolVersion.contains("1.0") || session.protocolVersion.contains("1.1");
|
||||
}
|
||||
|
||||
public SSLSession getSession(String ip) {
|
||||
return sslSessions.get(ip);
|
||||
}
|
||||
|
||||
public Collection<SSLSession> getAllSessions() {
|
||||
return sslSessions.values();
|
||||
}
|
||||
|
||||
public void addAlert(String ip, String type, String details) {
|
||||
SSLAlert alert = new SSLAlert(ip, type, details, System.currentTimeMillis());
|
||||
alerts.offer(alert);
|
||||
|
||||
while (alerts.size() > 500) {
|
||||
alerts.poll();
|
||||
}
|
||||
}
|
||||
|
||||
public List<SSLAlert> getAlerts(int limit) {
|
||||
List<SSLAlert> result = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (SSLAlert alert : alerts) {
|
||||
if (count++ >= limit) break;
|
||||
result.add(alert);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public Map<String, Object> getStats() {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("activeSessions", sslSessions.size());
|
||||
stats.put("totalHandshakes", totalHandshakes.get());
|
||||
stats.put("failedHandshakes", failedHandshakes.get());
|
||||
return stats;
|
||||
}
|
||||
|
||||
private void startMonitoringTask() {
|
||||
scheduler.scheduleAtFixedRate(() -> {
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
sslSessions.entrySet().removeIf(entry ->
|
||||
now - entry.getValue().lastActivity > SESSION_TIMEOUT);
|
||||
|
||||
}, 30, 30, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public static class SSLSession {
|
||||
private final String ip;
|
||||
private final List<String> certificates = new ArrayList<>();
|
||||
private volatile String cipherSuite;
|
||||
private volatile String protocolVersion;
|
||||
private volatile long handshakeCount = 0;
|
||||
private volatile long failedCount = 0;
|
||||
private volatile long lastActivity;
|
||||
|
||||
public SSLSession(String ip) {
|
||||
this.ip = ip;
|
||||
this.lastActivity = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void recordHandshake(boolean success) {
|
||||
handshakeCount++;
|
||||
if (!success) {
|
||||
failedCount++;
|
||||
}
|
||||
lastActivity = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void addCertificate(String certInfo) {
|
||||
certificates.add(certInfo);
|
||||
if (certificates.size() > MAX_CERTIFICATES) {
|
||||
certificates.remove(0);
|
||||
}
|
||||
}
|
||||
|
||||
public String getIp() { return ip; }
|
||||
public String getCipherSuite() { return cipherSuite; }
|
||||
public void setCipherSuite(String cipherSuite) { this.cipherSuite = cipherSuite; }
|
||||
public String getProtocolVersion() { return protocolVersion; }
|
||||
public void setProtocolVersion(String protocolVersion) { this.protocolVersion = protocolVersion; }
|
||||
public long getHandshakeCount() { return handshakeCount; }
|
||||
public long getFailedCount() { return failedCount; }
|
||||
}
|
||||
|
||||
public static class SSLAlert {
|
||||
private final String ip;
|
||||
private final String type;
|
||||
private final String details;
|
||||
private final long timestamp;
|
||||
|
||||
public SSLAlert(String ip, String type, String details, long timestamp) {
|
||||
this.ip = ip;
|
||||
this.type = type;
|
||||
this.details = details;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getIp() { return ip; }
|
||||
public String getType() { return type; }
|
||||
public String getDetails() { return details; }
|
||||
public long getTimestamp() { return timestamp; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
@Component
|
||||
public class SSLTLSCertificateService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(SSLTLSCertificateService.class);
|
||||
|
||||
private final Map<String, CertificateInfo> certificates = new ConcurrentHashMap<>();
|
||||
private final Map<String, CertificateChain> certificateChains = new ConcurrentHashMap<>();
|
||||
private final Queue<CertificateAlert> alertQueue = new ConcurrentLinkedQueue<>();
|
||||
private final Map<String, SSLConnectionStats> connectionStats = new ConcurrentHashMap<>();
|
||||
private final AtomicLong totalConnections = new AtomicLong(0);
|
||||
private final AtomicLong totalHandshakes = new AtomicLong(0);
|
||||
private final AtomicLong failedHandshakes = new AtomicLong(0);
|
||||
|
||||
private static final int CERTIFICATE_VALIDITY_DAYS = 365;
|
||||
private static final int RENEWAL_WARNING_DAYS = 30;
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
public SSLTLSCertificateService() {
|
||||
initializeDefaultCertificates();
|
||||
logger.info("SSL/TLS Certificate Service initialized");
|
||||
}
|
||||
|
||||
private void initializeDefaultCertificates() {
|
||||
addCertificate("minecraft-server", "CN=mc.example.com, O=Example Server, C=US",
|
||||
generateExpiryDate(365), "RSA", 2048, "SHA256withRSA");
|
||||
|
||||
addCertificate("rcon-server", "CN=rcon.example.com, O=Example Server, C=US",
|
||||
generateExpiryDate(180), "RSA", 2048, "SHA256withRSA");
|
||||
|
||||
logger.info("Initialized {} default certificates", certificates.size());
|
||||
}
|
||||
|
||||
private LocalDateTime generateExpiryDate(int days) {
|
||||
return LocalDateTime.now().plusDays(days);
|
||||
}
|
||||
|
||||
public void addCertificate(String name, String subject, LocalDateTime expiry,
|
||||
String keyAlgorithm, int keySize, String signatureAlgorithm) {
|
||||
CertificateInfo cert = new CertificateInfo(name, subject, expiry, keyAlgorithm, keySize, signatureAlgorithm);
|
||||
certificates.put(name, cert);
|
||||
|
||||
CertificateChain chain = new CertificateChain(name);
|
||||
chain.addCertificate(cert);
|
||||
certificateChains.put(name, chain);
|
||||
|
||||
logger.info("Added certificate: {} (expires: {})", name, expiry);
|
||||
}
|
||||
|
||||
public CertificateInfo getCertificate(String name) {
|
||||
return certificates.get(name);
|
||||
}
|
||||
|
||||
public List<CertificateInfo> getExpiringCertificates(int daysThreshold) {
|
||||
List<CertificateInfo> expiring = new ArrayList<>();
|
||||
LocalDateTime threshold = LocalDateTime.now().plusDays(daysThreshold);
|
||||
|
||||
for (CertificateInfo cert : certificates.values()) {
|
||||
if (cert.getExpiryDate().isBefore(threshold)) {
|
||||
expiring.add(cert);
|
||||
}
|
||||
}
|
||||
|
||||
return expiring;
|
||||
}
|
||||
|
||||
public Map<String, Object> analyzeCertificate(String name) {
|
||||
Map<String, Object> analysis = new HashMap<>();
|
||||
CertificateInfo cert = certificates.get(name);
|
||||
|
||||
if (cert == null) {
|
||||
analysis.put("error", "Certificate not found");
|
||||
return analysis;
|
||||
}
|
||||
|
||||
analysis.put("name", cert.getName());
|
||||
analysis.put("subject", cert.getSubject());
|
||||
analysis.put("expiryDate", cert.getExpiryDate());
|
||||
analysis.put("daysUntilExpiry", cert.getDaysUntilExpiry());
|
||||
|
||||
boolean needsRenewal = cert.getDaysUntilExpiry() < RENEWAL_WARNING_DAYS;
|
||||
analysis.put("needsRenewal", needsRenewal);
|
||||
|
||||
boolean isWeakAlgorithm = "MD5".equals(cert.getSignatureAlgorithm()) || "SHA1".equals(cert.getSignatureAlgorithm());
|
||||
analysis.put("isWeakAlgorithm", isWeakAlgorithm);
|
||||
|
||||
boolean isWeakKey = cert.getKeySize() < 2048;
|
||||
analysis.put("isWeakKey", isWeakKey);
|
||||
|
||||
if (isWeakAlgorithm || isWeakKey || needsRenewal) {
|
||||
analysis.put("securityConcerns", new ArrayList<String>());
|
||||
}
|
||||
|
||||
CertificateChain chain = certificateChains.get(name);
|
||||
if (chain != null) {
|
||||
analysis.put("chainLength", chain.getChainLength());
|
||||
analysis.put("chainValid", chain.isChainValid());
|
||||
}
|
||||
|
||||
return analysis;
|
||||
}
|
||||
|
||||
public void recordHandshake(String serverName, boolean success, String error) {
|
||||
totalHandshakes.incrementAndGet();
|
||||
|
||||
if (!success) {
|
||||
failedHandshakes.incrementAndGet();
|
||||
}
|
||||
|
||||
SSLConnectionStats stats = connectionStats.computeIfAbsent(serverName, k -> new SSLConnectionStats(serverName));
|
||||
if (success) {
|
||||
stats.incrementSuccessfulHandshakes();
|
||||
} else {
|
||||
stats.incrementFailedHandshakes();
|
||||
}
|
||||
|
||||
logger.debug("SSL handshake for {}: {}", serverName, success ? "success" : "failed");
|
||||
}
|
||||
|
||||
public void recordConnection(String sourceIP, String destIP, String protocol, boolean encrypted) {
|
||||
totalConnections.incrementAndGet();
|
||||
|
||||
SSLConnectionStats stats = connectionStats.computeIfAbsent(destIP, k -> new SSLConnectionStats(destIP));
|
||||
stats.incrementConnectionCount();
|
||||
if (encrypted) {
|
||||
stats.incrementEncryptedConnections();
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Object> getStats() {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("totalConnections", totalConnections.get());
|
||||
stats.put("totalHandshakes", totalHandshakes.get());
|
||||
stats.put("failedHandshakes", failedHandshakes.get());
|
||||
stats.put("certificatesLoaded", certificates.size());
|
||||
stats.put("connectionStats", connectionStats.size());
|
||||
|
||||
long successRate = totalHandshakes.get() > 0 ?
|
||||
(totalHandshakes.get() - failedHandshakes.get()) * 100 / totalHandshakes.get() : 0;
|
||||
stats.put("handshakeSuccessRate", successRate + "%");
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
public List<CertificateAlert> getAlerts(int limit) {
|
||||
List<CertificateAlert> alerts = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (CertificateAlert alert : alertQueue) {
|
||||
if (count++ >= limit) break;
|
||||
alerts.add(alert);
|
||||
}
|
||||
return alerts;
|
||||
}
|
||||
|
||||
public void checkCertificateExpiration() {
|
||||
for (CertificateInfo cert : certificates.values()) {
|
||||
int daysUntilExpiry = cert.getDaysUntilExpiry();
|
||||
|
||||
if (daysUntilExpiry <= 0) {
|
||||
triggerAlert(cert.getName(), "EXPIRED", "Certificate has expired");
|
||||
} else if (daysUntilExpiry <= 7) {
|
||||
triggerAlert(cert.getName(), "CRITICAL", "Certificate expires in " + daysUntilExpiry + " days");
|
||||
} else if (daysUntilExpiry <= 30) {
|
||||
triggerAlert(cert.getName(), "WARNING", "Certificate expires in " + daysUntilExpiry + " days");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void triggerAlert(String certName, String severity, String message) {
|
||||
CertificateAlert alert = new CertificateAlert(certName, severity, message, LocalDateTime.now());
|
||||
alertQueue.offer(alert);
|
||||
|
||||
if (alertQueue.size() > 100) {
|
||||
alertQueue.poll();
|
||||
}
|
||||
|
||||
logger.warn("Certificate Alert [{}]: {} - {}", severity, certName, message);
|
||||
}
|
||||
|
||||
public Map<String, CertificateInfo> getAllCertificates() {
|
||||
return new HashMap<>(certificates);
|
||||
}
|
||||
|
||||
public static class CertificateInfo {
|
||||
private final String name;
|
||||
private final String subject;
|
||||
private final LocalDateTime expiryDate;
|
||||
private final String keyAlgorithm;
|
||||
private final int keySize;
|
||||
private final String signatureAlgorithm;
|
||||
|
||||
public CertificateInfo(String name, String subject, LocalDateTime expiryDate,
|
||||
String keyAlgorithm, int keySize, String signatureAlgorithm) {
|
||||
this.name = name;
|
||||
this.subject = subject;
|
||||
this.expiryDate = expiryDate;
|
||||
this.keyAlgorithm = keyAlgorithm;
|
||||
this.keySize = keySize;
|
||||
this.signatureAlgorithm = signatureAlgorithm;
|
||||
}
|
||||
|
||||
public int getDaysUntilExpiry() {
|
||||
return (int) java.time.Duration.between(LocalDateTime.now(), expiryDate).toDays();
|
||||
}
|
||||
|
||||
public String getName() { return name; }
|
||||
public String getSubject() { return subject; }
|
||||
public LocalDateTime getExpiryDate() { return expiryDate; }
|
||||
public String getKeyAlgorithm() { return keyAlgorithm; }
|
||||
public int getKeySize() { return keySize; }
|
||||
public String getSignatureAlgorithm() { return signatureAlgorithm; }
|
||||
}
|
||||
|
||||
public static class CertificateChain {
|
||||
private final String serverName;
|
||||
private final List<CertificateInfo> certificates = new ArrayList<>();
|
||||
private volatile boolean chainValid = true;
|
||||
|
||||
public CertificateChain(String serverName) {
|
||||
this.serverName = serverName;
|
||||
}
|
||||
|
||||
public void addCertificate(CertificateInfo cert) {
|
||||
certificates.add(cert);
|
||||
}
|
||||
|
||||
public boolean isChainValid() {
|
||||
return chainValid;
|
||||
}
|
||||
|
||||
public void setChainValid(boolean valid) {
|
||||
this.chainValid = valid;
|
||||
}
|
||||
|
||||
public int getChainLength() {
|
||||
return certificates.size();
|
||||
}
|
||||
|
||||
public String getServerName() { return serverName; }
|
||||
public List<CertificateInfo> getCertificates() { return certificates; }
|
||||
}
|
||||
|
||||
public static class SSLConnectionStats {
|
||||
private final String serverName;
|
||||
private final AtomicLong connectionCount = new AtomicLong(0);
|
||||
private final AtomicLong encryptedConnections = new AtomicLong(0);
|
||||
private final AtomicLong successfulHandshakes = new AtomicLong(0);
|
||||
private final AtomicLong failedHandshakes = new AtomicLong(0);
|
||||
|
||||
public SSLConnectionStats(String serverName) {
|
||||
this.serverName = serverName;
|
||||
}
|
||||
|
||||
public void incrementConnectionCount() { connectionCount.incrementAndGet(); }
|
||||
public void incrementEncryptedConnections() { encryptedConnections.incrementAndGet(); }
|
||||
public void incrementSuccessfulHandshakes() { successfulHandshakes.incrementAndGet(); }
|
||||
public void incrementFailedHandshakes() { failedHandshakes.incrementAndGet(); }
|
||||
|
||||
public String getServerName() { return serverName; }
|
||||
public long getConnectionCount() { return connectionCount.get(); }
|
||||
public long getEncryptedConnections() { return encryptedConnections.get(); }
|
||||
public long getSuccessfulHandshakes() { return successfulHandshakes.get(); }
|
||||
public long getFailedHandshakes() { return failedHandshakes.get(); }
|
||||
}
|
||||
|
||||
public static class CertificateAlert {
|
||||
private final String certificateName;
|
||||
private final String severity;
|
||||
private final String message;
|
||||
private final LocalDateTime timestamp;
|
||||
|
||||
public CertificateAlert(String certificateName, String severity, String message, LocalDateTime timestamp) {
|
||||
this.certificateName = certificateName;
|
||||
this.severity = severity;
|
||||
this.message = message;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getCertificateName() { return certificateName; }
|
||||
public String getSeverity() { return severity; }
|
||||
public String getMessage() { return message; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import com.aluer.config.ServerGuardConfig;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.PreDestroy;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Component
|
||||
public class SecurityAutomationScheduler {
|
||||
private final ServerGuardConfig config;
|
||||
private final ThreatIntelligenceService threatIntelligenceService;
|
||||
private final FileIntegrityMonitorService fileIntegrityMonitorService;
|
||||
private final HostEnforcementService hostEnforcementService;
|
||||
private final DDoSDefenseCoordinator ddosDefenseCoordinator;
|
||||
private final HostIntrusionCountermeasureService hostIntrusionCountermeasureService;
|
||||
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
|
||||
|
||||
private volatile boolean running = false;
|
||||
|
||||
public SecurityAutomationScheduler() {
|
||||
this(new ServerGuardConfig(), new ThreatIntelligenceService(), new FileIntegrityMonitorService(),
|
||||
new HostEnforcementService(), new DDoSDefenseCoordinator(), new HostIntrusionCountermeasureService());
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public SecurityAutomationScheduler(ServerGuardConfig config,
|
||||
ThreatIntelligenceService threatIntelligenceService,
|
||||
FileIntegrityMonitorService fileIntegrityMonitorService,
|
||||
HostEnforcementService hostEnforcementService,
|
||||
DDoSDefenseCoordinator ddosDefenseCoordinator,
|
||||
HostIntrusionCountermeasureService hostIntrusionCountermeasureService) {
|
||||
this.config = config;
|
||||
this.threatIntelligenceService = threatIntelligenceService;
|
||||
this.fileIntegrityMonitorService = fileIntegrityMonitorService;
|
||||
this.hostEnforcementService = hostEnforcementService;
|
||||
this.ddosDefenseCoordinator = ddosDefenseCoordinator;
|
||||
this.hostIntrusionCountermeasureService = hostIntrusionCountermeasureService;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void start() {
|
||||
if (running || !config.getSecurity().getAutomation().isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
running = true;
|
||||
int feedMinutes = Math.max(1, config.getSecurity().getAutomation().getFeedRefreshMinutes());
|
||||
int syncMinutes = Math.max(1, config.getSecurity().getAutomation().getRuleSyncMinutes());
|
||||
int integrityMinutes = Math.max(1, config.getSecurity().getAutomation().getIntegrityRescanMinutes());
|
||||
|
||||
scheduler.scheduleAtFixedRate(threatIntelligenceService::refreshFeeds, 1, feedMinutes, TimeUnit.MINUTES);
|
||||
scheduler.scheduleAtFixedRate(hostEnforcementService::syncDesiredState, 1, syncMinutes, TimeUnit.MINUTES);
|
||||
scheduler.scheduleAtFixedRate(fileIntegrityMonitorService::scanNow, 1, integrityMinutes, TimeUnit.MINUTES);
|
||||
scheduler.scheduleAtFixedRate(hostIntrusionCountermeasureService::runFullScan, 2, integrityMinutes, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
public Map<String, Object> runMaintenanceCycle() {
|
||||
Map<String, Object> cycle = new LinkedHashMap<>();
|
||||
cycle.put("feeds", threatIntelligenceService.refreshFeeds());
|
||||
cycle.put("hostRules", hostEnforcementService.syncDesiredState());
|
||||
cycle.put("integrity", fileIntegrityMonitorService.scanNow());
|
||||
cycle.put("antiIntrusion", hostIntrusionCountermeasureService.runFullScan());
|
||||
cycle.put("ddos", ddosDefenseCoordinator.getPosture());
|
||||
return cycle;
|
||||
}
|
||||
|
||||
public Map<String, Object> getStatus() {
|
||||
Map<String, Object> status = new LinkedHashMap<>();
|
||||
status.put("running", running);
|
||||
status.put("automationEnabled", config.getSecurity().getAutomation().isEnabled());
|
||||
status.put("feedRefreshMinutes", config.getSecurity().getAutomation().getFeedRefreshMinutes());
|
||||
status.put("integrityRescanMinutes", config.getSecurity().getAutomation().getIntegrityRescanMinutes());
|
||||
status.put("ruleSyncMinutes", config.getSecurity().getAutomation().getRuleSyncMinutes());
|
||||
return status;
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void shutdown() {
|
||||
running = false;
|
||||
scheduler.shutdownNow();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
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.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Service
|
||||
public class SecurityBaselineHardeningService {
|
||||
private static final Pattern INLINE_SECRET_PATTERN = Pattern.compile(
|
||||
"(?im)^\\s*(api-key|password|secret|token|apiKey):\\s*(?!\\$\\{)([^\\s#]+)"
|
||||
);
|
||||
|
||||
private final ServerGuardConfig config;
|
||||
private final ConcurrentLinkedDeque<HardeningFinding> findingLog = new ConcurrentLinkedDeque<>();
|
||||
private final Path applicationConfigPath = Paths.get("src", "main", "resources", "application.yml");
|
||||
|
||||
public SecurityBaselineHardeningService() {
|
||||
this(new ServerGuardConfig());
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public SecurityBaselineHardeningService(ServerGuardConfig config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public HardeningReport assessCurrentBaseline() {
|
||||
List<HardeningFinding> findings = new ArrayList<>();
|
||||
scanRuntimeConfiguration(findings);
|
||||
scanConfigFile(findings);
|
||||
record(findings);
|
||||
return new HardeningReport(findings);
|
||||
}
|
||||
|
||||
public List<HardeningFinding> getRecentFindings(int limit) {
|
||||
List<HardeningFinding> result = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (HardeningFinding finding : findingLog) {
|
||||
if (count++ >= limit) {
|
||||
break;
|
||||
}
|
||||
result.add(finding);
|
||||
}
|
||||
result.sort(Comparator.comparingLong(HardeningFinding::getTimestamp).reversed());
|
||||
return result;
|
||||
}
|
||||
|
||||
public Map<String, Object> getSummary() {
|
||||
return assessCurrentBaseline().toMap();
|
||||
}
|
||||
|
||||
private void scanRuntimeConfiguration(List<HardeningFinding> findings) {
|
||||
ServerGuardConfig.RconConfig rcon = config.getMinecraft().getRcon();
|
||||
if (rcon.isEnabled() && isBlank(rcon.getPassword())) {
|
||||
findings.add(new HardeningFinding(
|
||||
"critical",
|
||||
"RCON",
|
||||
"RCON 已启用但未配置口令",
|
||||
"远程控制接口暴露时,空口令会直接放大入侵面。",
|
||||
"为 RCON 设置强随机密码,并限制来源 IP。"
|
||||
));
|
||||
} else if (rcon.isEnabled() && rcon.getPassword().length() < 12) {
|
||||
findings.add(new HardeningFinding(
|
||||
"high",
|
||||
"RCON",
|
||||
"RCON 口令强度偏弱",
|
||||
"当前口令长度过短,容易被暴力猜解或撞库命中。",
|
||||
"将 RCON 口令提升到 16 位以上并只通过环境变量注入。"
|
||||
));
|
||||
}
|
||||
|
||||
if (!config.getSecurity().getAntiIntrusion().getFileIntegrity().isEnabled()) {
|
||||
findings.add(new HardeningFinding(
|
||||
"high",
|
||||
"INTEGRITY",
|
||||
"文件完整性监控已关闭",
|
||||
"插件篡改、脚本替换和 systemd 劫持将更难被发现。",
|
||||
"启用 file-integrity,并覆盖核心 jar、插件目录和启动脚本。"
|
||||
));
|
||||
}
|
||||
|
||||
if (!config.getSecurity().getHostEnforcement().isEnabled()) {
|
||||
findings.add(new HardeningFinding(
|
||||
"medium",
|
||||
"HOST_ENFORCEMENT",
|
||||
"主机侧封禁未启用",
|
||||
"系统当前只能在应用层做隔离,真正的源地址拦截无法落到主机网络层。",
|
||||
"启用 host-enforcement,至少保持 dry-run 观察,再逐步切到真实执行。"
|
||||
));
|
||||
} else if (config.getSecurity().getHostEnforcement().isDryRun()) {
|
||||
findings.add(new HardeningFinding(
|
||||
"low",
|
||||
"HOST_ENFORCEMENT",
|
||||
"主机侧封禁仍处于 dry-run",
|
||||
"当前只生成阻断预览,不会真正下发防火墙规则。",
|
||||
"验证命令预览无误后,把 dry-run 切换为 false。"
|
||||
));
|
||||
}
|
||||
|
||||
if (config.getAi().getDeepseek().getAutoExecute().isEnabled()
|
||||
&& config.getAi().getDeepseek().getAutoExecute().getMinConfidence() < 85) {
|
||||
findings.add(new HardeningFinding(
|
||||
"medium",
|
||||
"AUTONOMY",
|
||||
"AI 自动执行置信度阈值过低",
|
||||
"过低阈值会放大误封禁、误踢人和错误白名单切换的概率。",
|
||||
"将 min-confidence 提升到 85 以上,并保留工作流冷却。"
|
||||
));
|
||||
}
|
||||
|
||||
if (config.getSecurity().getCloudEdge().isEnabled()
|
||||
&& (isBlank(config.getSecurity().getCloudEdge().getZoneId())
|
||||
|| isBlank(config.getSecurity().getCloudEdge().getApiKey()))) {
|
||||
findings.add(new HardeningFinding(
|
||||
"high",
|
||||
"EDGE",
|
||||
"云边协同已启用但凭据不完整",
|
||||
"边缘拦截配置不完整会导致自治编排在关键阶段失效。",
|
||||
"补齐 Cloudflare zone/API 凭据,或显式关闭 cloud-edge。"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
private void scanConfigFile(List<HardeningFinding> findings) {
|
||||
if (!Files.exists(applicationConfigPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
String content = Files.readString(applicationConfigPath, StandardCharsets.UTF_8);
|
||||
Matcher matcher = INLINE_SECRET_PATTERN.matcher(content);
|
||||
while (matcher.find()) {
|
||||
String key = matcher.group(1);
|
||||
String value = matcher.group(2);
|
||||
if (value != null && !value.isBlank()) {
|
||||
findings.add(new HardeningFinding(
|
||||
"critical",
|
||||
"CONFIG_SECRET",
|
||||
"配置文件中存在明文敏感项: " + key,
|
||||
"明文凭据一旦进仓库、日志或备份,泄露面会持续扩大。",
|
||||
"改为 `${ENV_VAR:}` 形式,并轮换已暴露的真实凭据。"
|
||||
));
|
||||
}
|
||||
}
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
private void record(List<HardeningFinding> findings) {
|
||||
for (HardeningFinding finding : findings) {
|
||||
findingLog.offer(finding);
|
||||
}
|
||||
while (findingLog.size() > 500) {
|
||||
findingLog.poll();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isBlank(String value) {
|
||||
return value == null || value.isBlank();
|
||||
}
|
||||
|
||||
public static class HardeningReport {
|
||||
private final List<HardeningFinding> findings;
|
||||
private final int criticalCount;
|
||||
private final int highCount;
|
||||
private final int mediumCount;
|
||||
private final int lowCount;
|
||||
private final int score;
|
||||
|
||||
public HardeningReport(List<HardeningFinding> findings) {
|
||||
this.findings = findings.stream()
|
||||
.sorted(Comparator.comparing((HardeningFinding finding) -> severityWeight(finding.severity)).reversed())
|
||||
.toList();
|
||||
this.criticalCount = (int) this.findings.stream().filter(finding -> "critical".equals(finding.severity)).count();
|
||||
this.highCount = (int) this.findings.stream().filter(finding -> "high".equals(finding.severity)).count();
|
||||
this.mediumCount = (int) this.findings.stream().filter(finding -> "medium".equals(finding.severity)).count();
|
||||
this.lowCount = (int) this.findings.stream().filter(finding -> "low".equals(finding.severity)).count();
|
||||
this.score = Math.max(0, 100 - (criticalCount * 25 + highCount * 15 + mediumCount * 8 + lowCount * 3));
|
||||
}
|
||||
|
||||
public List<HardeningFinding> getFindings() { return findings; }
|
||||
public int getCriticalCount() { return criticalCount; }
|
||||
public int getHighCount() { return highCount; }
|
||||
public int getMediumCount() { return mediumCount; }
|
||||
public int getLowCount() { return lowCount; }
|
||||
public int getScore() { return score; }
|
||||
|
||||
public Map<String, Object> toMap() {
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("score", score);
|
||||
result.put("exposureLevel", score >= 85 ? "stable" : score >= 65 ? "guarded" : score >= 45 ? "exposed" : "critical");
|
||||
result.put("critical", criticalCount);
|
||||
result.put("high", highCount);
|
||||
result.put("medium", mediumCount);
|
||||
result.put("low", lowCount);
|
||||
result.put("findings", findings.stream().limit(6).map(HardeningFinding::toMap).toList());
|
||||
result.put("recommendations", findings.stream()
|
||||
.map(HardeningFinding::getRecommendation)
|
||||
.distinct()
|
||||
.limit(5)
|
||||
.toList());
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int severityWeight(String severity) {
|
||||
return switch (severity.toLowerCase(Locale.ROOT)) {
|
||||
case "critical" -> 4;
|
||||
case "high" -> 3;
|
||||
case "medium" -> 2;
|
||||
default -> 1;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static class HardeningFinding {
|
||||
private final String severity;
|
||||
private final String category;
|
||||
private final String title;
|
||||
private final String detail;
|
||||
private final String recommendation;
|
||||
private final long timestamp;
|
||||
|
||||
public HardeningFinding(String severity, String category, String title, String detail, String recommendation) {
|
||||
this.severity = severity;
|
||||
this.category = category;
|
||||
this.title = title;
|
||||
this.detail = detail;
|
||||
this.recommendation = recommendation;
|
||||
this.timestamp = Instant.now().toEpochMilli();
|
||||
}
|
||||
|
||||
public String getSeverity() { return severity; }
|
||||
public String getCategory() { return category; }
|
||||
public String getTitle() { return title; }
|
||||
public String getDetail() { return detail; }
|
||||
public String getRecommendation() { return recommendation; }
|
||||
public long getTimestamp() { return timestamp; }
|
||||
|
||||
public Map<String, Object> toMap() {
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
result.put("severity", severity);
|
||||
result.put("category", category);
|
||||
result.put("title", title);
|
||||
result.put("detail", detail);
|
||||
result.put("recommendation", recommendation);
|
||||
result.put("timestamp", timestamp);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,666 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import com.aluer.audit.SecurityAuditService;
|
||||
import com.aluer.service.RconClient;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
@Component
|
||||
public class SecurityOrchestrationService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(SecurityOrchestrationService.class);
|
||||
|
||||
private final HostEnforcementService hostEnforcementService;
|
||||
private final CloudflareIntegrationService cloudflareIntegrationService;
|
||||
private final RconClient rconClient;
|
||||
private final SecurityAuditService securityAuditService;
|
||||
private final Map<String, SecurityWorkflow> workflows = new ConcurrentHashMap<>();
|
||||
private final Map<String, OrchestrationTask> activeTasks = new ConcurrentHashMap<>();
|
||||
private final Queue<OrchestrationEvent> eventLog = new ConcurrentLinkedQueue<>();
|
||||
private final Map<String, List<SecurityAction>> actionHistory = new ConcurrentHashMap<>();
|
||||
private final AtomicLong totalWorkflowsExecuted = new AtomicLong(0);
|
||||
private final AtomicLong successfulWorkflows = new AtomicLong(0);
|
||||
private final AtomicLong failedWorkflows = new AtomicLong(0);
|
||||
|
||||
private volatile boolean enabled = true;
|
||||
private static final int MAX_HISTORY = 10000;
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
public SecurityOrchestrationService() {
|
||||
this(new HostEnforcementService(), new CloudflareIntegrationService(), null, null);
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public SecurityOrchestrationService(HostEnforcementService hostEnforcementService,
|
||||
CloudflareIntegrationService cloudflareIntegrationService,
|
||||
RconClient rconClient,
|
||||
SecurityAuditService securityAuditService) {
|
||||
this.hostEnforcementService = hostEnforcementService;
|
||||
this.cloudflareIntegrationService = cloudflareIntegrationService;
|
||||
this.rconClient = rconClient;
|
||||
this.securityAuditService = securityAuditService;
|
||||
initializeDefaultWorkflows();
|
||||
logger.info("Security Orchestration Service initialized");
|
||||
}
|
||||
|
||||
private void initializeDefaultWorkflows() {
|
||||
addWorkflow("DDOS_MITIGATION", Arrays.asList(
|
||||
new SecurityAction("DETECT", "threat-detection", "Analyze attack pattern"),
|
||||
new SecurityAction("BLOCK", "firewall", "Block malicious IPs"),
|
||||
new SecurityAction("NOTIFY", "alert-system", "Send alert to administrators"),
|
||||
new SecurityAction("LOG", "audit-log", "Record incident details")
|
||||
));
|
||||
|
||||
addWorkflow("INTRUSION_RESPONSE", Arrays.asList(
|
||||
new SecurityAction("DETECT", "intrusion-detection", "Identify intrusion"),
|
||||
new SecurityAction("ISOLATE", "network-isolation", "Isolate affected systems"),
|
||||
new SecurityAction("COLLECT", "forensics", "Collect evidence"),
|
||||
new SecurityAction("NOTIFY", "alert-system", "Notify security team"),
|
||||
new SecurityAction("REMEDIATE", "remediation", "Apply fixes")
|
||||
));
|
||||
|
||||
addWorkflow("DATA_BREACH", Arrays.asList(
|
||||
new SecurityAction("DETECT", "data-loss-prevention", "Detect data breach"),
|
||||
new SecurityAction("CONTAIN", "access-control", "Restrict access"),
|
||||
new SecurityAction("INVESTIGATE", "forensics", "Investigate breach"),
|
||||
new SecurityAction("NOTIFY", "compliance", "Notify stakeholders"),
|
||||
new SecurityAction("REMEDIATE", "security-patch", "Apply security patches")
|
||||
));
|
||||
|
||||
addWorkflow("MALWARE_DETECTION", Arrays.asList(
|
||||
new SecurityAction("SCAN", "antivirus", "Scan for malware"),
|
||||
new SecurityAction("QUARANTINE", "isolation", "Quarantine infected files"),
|
||||
new SecurityAction("ANALYZE", "sandbox", "Analyze malware"),
|
||||
new SecurityAction("REMEDIATE", "clean-tool", "Clean or remove malware")
|
||||
));
|
||||
|
||||
addWorkflow("BRUTE_FORCE_PROTECTION", Arrays.asList(
|
||||
new SecurityAction("DETECT", "auth-monitor", "Detect brute force"),
|
||||
new SecurityAction("BLOCK", "rate-limiter", "Block attacker IP"),
|
||||
new SecurityAction("NOTIFY", "alert-system", "Notify admin"),
|
||||
new SecurityAction("AUDIT", "log-analysis", "Audit attack attempts")
|
||||
));
|
||||
|
||||
addWorkflow("MC_STATUS_FLOOD_RESPONSE", Arrays.asList(
|
||||
new SecurityAction("DETECT", "minecraft-status", "Detect status ping flood", true),
|
||||
new SecurityAction("CORRELATE", "threat-fusion", "Correlate burst sources"),
|
||||
new SecurityAction("SCORE", "risk-engine", "Score the incident"),
|
||||
new SecurityAction("LOCAL_BLOCK_OR_RATE_LIMIT", "host-firewall", "Rate limit or block source", true),
|
||||
new SecurityAction("EDGE_BLOCK_OR_CHALLENGE", "cloud-edge", "Challenge abusive source"),
|
||||
new SecurityAction("MINECRAFT_DEFENSE_ACTION", "rcon", "Broadcast degraded state"),
|
||||
new SecurityAction("AUDIT", "audit", "Record the incident"),
|
||||
new SecurityAction("NOTIFY", "alert-system", "Notify operators")
|
||||
));
|
||||
|
||||
addWorkflow("MC_LOGIN_FLOOD_RESPONSE", Arrays.asList(
|
||||
new SecurityAction("DETECT", "minecraft-login", "Detect login burst", true),
|
||||
new SecurityAction("CORRELATE", "identity-correlation", "Correlate bot swarm"),
|
||||
new SecurityAction("SCORE", "risk-engine", "Score login abuse"),
|
||||
new SecurityAction("LOCAL_BLOCK_OR_RATE_LIMIT", "host-firewall", "Block or slow source", true),
|
||||
new SecurityAction("EDGE_BLOCK_OR_CHALLENGE", "cloud-edge", "Challenge edge"),
|
||||
new SecurityAction("MINECRAFT_DEFENSE_ACTION", "rcon", "Enable temporary whitelist"),
|
||||
new SecurityAction("AUDIT", "audit", "Record the incident"),
|
||||
new SecurityAction("NOTIFY", "alert-system", "Notify operators")
|
||||
));
|
||||
|
||||
addWorkflow("MC_BOT_SWARM_RESPONSE", Arrays.asList(
|
||||
new SecurityAction("DETECT", "bot-swarm", "Detect multi-source swarm", true),
|
||||
new SecurityAction("CORRELATE", "cluster-analysis", "Correlate source cohorts"),
|
||||
new SecurityAction("SCORE", "risk-engine", "Score swarm severity"),
|
||||
new SecurityAction("LOCAL_BLOCK_OR_RATE_LIMIT", "host-firewall", "Block swarm IPs", true),
|
||||
new SecurityAction("EDGE_BLOCK_OR_CHALLENGE", "cloud-edge", "Challenge or block edge"),
|
||||
new SecurityAction("MINECRAFT_DEFENSE_ACTION", "rcon", "Reduce access surface"),
|
||||
new SecurityAction("AUDIT", "audit", "Record bot swarm"),
|
||||
new SecurityAction("NOTIFY", "alert-system", "Notify operators")
|
||||
));
|
||||
|
||||
addWorkflow("RCON_BRUTE_FORCE_RESPONSE", Arrays.asList(
|
||||
new SecurityAction("DETECT", "rcon-monitor", "Detect RCON brute force", true),
|
||||
new SecurityAction("CORRELATE", "credential-abuse", "Correlate repeated failures"),
|
||||
new SecurityAction("SCORE", "risk-engine", "Score credential attack"),
|
||||
new SecurityAction("LOCAL_BLOCK_OR_RATE_LIMIT", "host-firewall", "Block attacker", true),
|
||||
new SecurityAction("EDGE_BLOCK_OR_CHALLENGE", "cloud-edge", "Challenge attacker"),
|
||||
new SecurityAction("MINECRAFT_DEFENSE_ACTION", "rcon", "Enable whitelist and notify"),
|
||||
new SecurityAction("AUDIT", "audit", "Record RCON abuse"),
|
||||
new SecurityAction("NOTIFY", "alert-system", "Notify operators")
|
||||
));
|
||||
|
||||
addWorkflow("L34_DDOS_RESPONSE", Arrays.asList(
|
||||
new SecurityAction("DETECT", "l34-ddos", "Detect transport flood", true),
|
||||
new SecurityAction("CORRELATE", "ddos-correlation", "Correlate flood vectors"),
|
||||
new SecurityAction("SCORE", "risk-engine", "Score ddos"),
|
||||
new SecurityAction("LOCAL_BLOCK_OR_RATE_LIMIT", "host-firewall", "Rate limit or block", true),
|
||||
new SecurityAction("EDGE_BLOCK_OR_CHALLENGE", "cloud-edge", "Escalate to edge"),
|
||||
new SecurityAction("AUDIT", "audit", "Record mitigation"),
|
||||
new SecurityAction("NOTIFY", "alert-system", "Notify operators")
|
||||
));
|
||||
|
||||
addWorkflow("L7_DDOS_RESPONSE", Arrays.asList(
|
||||
new SecurityAction("DETECT", "l7-ddos", "Detect application flood", true),
|
||||
new SecurityAction("CORRELATE", "request-patterns", "Correlate abusive paths"),
|
||||
new SecurityAction("SCORE", "risk-engine", "Score app flood"),
|
||||
new SecurityAction("LOCAL_BLOCK_OR_RATE_LIMIT", "host-firewall", "Rate limit clients", true),
|
||||
new SecurityAction("EDGE_BLOCK_OR_CHALLENGE", "cloud-edge", "Challenge at edge"),
|
||||
new SecurityAction("AUDIT", "audit", "Record flood"),
|
||||
new SecurityAction("NOTIFY", "alert-system", "Notify operators")
|
||||
));
|
||||
|
||||
addWorkflow("HOST_INTRUSION_RESPONSE", Arrays.asList(
|
||||
new SecurityAction("DETECT", "host-edr", "Detect host intrusion", true),
|
||||
new SecurityAction("CORRELATE", "host-fusion", "Correlate host evidence"),
|
||||
new SecurityAction("SCORE", "risk-engine", "Score host intrusion"),
|
||||
new SecurityAction("LOCAL_BLOCK_OR_RATE_LIMIT", "host-firewall", "Block source or isolate host", true),
|
||||
new SecurityAction("AUDIT", "audit", "Record host intrusion"),
|
||||
new SecurityAction("NOTIFY", "alert-system", "Notify operators")
|
||||
));
|
||||
|
||||
addWorkflow("PLUGIN_TAMPER_RESPONSE", Arrays.asList(
|
||||
new SecurityAction("DETECT", "integrity-monitor", "Detect plugin tamper", true),
|
||||
new SecurityAction("CORRELATE", "artifact-correlation", "Correlate changed artifacts"),
|
||||
new SecurityAction("SCORE", "risk-engine", "Score tamper impact"),
|
||||
new SecurityAction("MINECRAFT_DEFENSE_ACTION", "rcon", "Freeze unsafe admin actions"),
|
||||
new SecurityAction("AUDIT", "audit", "Record tamper event"),
|
||||
new SecurityAction("NOTIFY", "alert-system", "Notify operators")
|
||||
));
|
||||
|
||||
addWorkflow("COMMAND_ABUSE_RESPONSE", Arrays.asList(
|
||||
new SecurityAction("DETECT", "command-guard", "Detect dangerous command", true),
|
||||
new SecurityAction("CORRELATE", "command-history", "Correlate operator activity"),
|
||||
new SecurityAction("SCORE", "risk-engine", "Score abuse"),
|
||||
new SecurityAction("LOCAL_BLOCK_OR_RATE_LIMIT", "host-firewall", "Block remote source when applicable"),
|
||||
new SecurityAction("AUDIT", "audit", "Record command abuse"),
|
||||
new SecurityAction("NOTIFY", "alert-system", "Notify operators")
|
||||
));
|
||||
|
||||
addWorkflow("VULNERABILITY_PATCH", Arrays.asList(
|
||||
new SecurityAction("SCAN", "vulnerability-scanner", "Scan for vulnerabilities"),
|
||||
new SecurityAction("ASSESS", "risk-assessment", "Assess severity"),
|
||||
new SecurityAction("PRIORITIZE", "patch-management", "Prioritize patches"),
|
||||
new SecurityAction("APPLY", "patch-manager", "Apply patches"),
|
||||
new SecurityAction("VERIFY", "validation", "Verify patch effectiveness")
|
||||
));
|
||||
|
||||
addWorkflow("INSIDER_THREAT", Arrays.asList(
|
||||
new SecurityAction("DETECT", "ueba", "Detect anomalous behavior"),
|
||||
new SecurityAction("ANALYZE", "behavior-analysis", "Analyze user behavior"),
|
||||
new SecurityAction("INVESTIGATE", "forensics", "Investigate threat"),
|
||||
new SecurityAction("CONTAIN", "access-control", "Restrict access if needed"),
|
||||
new SecurityAction("REPORT", "compliance", "Generate compliance report")
|
||||
));
|
||||
|
||||
logger.info("Initialized {} security workflows", workflows.size());
|
||||
}
|
||||
|
||||
public void addWorkflow(String name, List<SecurityAction> actions) {
|
||||
SecurityWorkflow workflow = new SecurityWorkflow(name, actions);
|
||||
workflows.put(name, workflow);
|
||||
logger.info("Added security workflow: {}", name);
|
||||
}
|
||||
|
||||
public String executeWorkflow(String workflowName, Map<String, Object> context) {
|
||||
return executeWorkflowDetailed(workflowName, context).getExecutionId();
|
||||
}
|
||||
|
||||
public WorkflowExecutionResult executeWorkflowDetailed(String workflowName, Map<String, Object> context) {
|
||||
if (!enabled) {
|
||||
return new WorkflowExecutionResult("", workflowName, "DISABLED", true, new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
SecurityWorkflow workflow = workflows.get(workflowName);
|
||||
if (workflow == null) {
|
||||
return new WorkflowExecutionResult("", workflowName, "NOT_FOUND", true, new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
String executionId = UUID.randomUUID().toString();
|
||||
totalWorkflowsExecuted.incrementAndGet();
|
||||
|
||||
OrchestrationTask task = new OrchestrationTask(executionId, workflowName, context);
|
||||
activeTasks.put(executionId, task);
|
||||
|
||||
logger.info("Executing workflow: {} (ID: {})", workflowName, executionId);
|
||||
|
||||
for (SecurityAction action : workflow.getActions()) {
|
||||
try {
|
||||
boolean success = executeAction(action, context);
|
||||
task.addActionResult(action.getName(), success);
|
||||
|
||||
if (!success && action.isCritical()) {
|
||||
task.setStatus("FAILED");
|
||||
failedWorkflows.incrementAndGet();
|
||||
logEvent(executionId, workflowName, "FAILED", "Critical action failed: " + action.getName());
|
||||
return new WorkflowExecutionResult(executionId, workflowName, "FAILED", false, new LinkedHashMap<>(task.getActionResults()));
|
||||
}
|
||||
|
||||
logEvent(executionId, workflowName, "ACTION_COMPLETED", "Action: " + action.getName());
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("Error executing action {} in workflow {}: {}", action.getName(), workflowName, e.getMessage());
|
||||
task.addActionResult(action.getName(), false);
|
||||
|
||||
if (action.isCritical()) {
|
||||
task.setStatus("FAILED");
|
||||
failedWorkflows.incrementAndGet();
|
||||
return new WorkflowExecutionResult(executionId, workflowName, "FAILED", false, new LinkedHashMap<>(task.getActionResults()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task.setStatus("COMPLETED");
|
||||
successfulWorkflows.incrementAndGet();
|
||||
logEvent(executionId, workflowName, "COMPLETED", "Workflow completed successfully");
|
||||
|
||||
return new WorkflowExecutionResult(executionId, workflowName, "COMPLETED", false, new LinkedHashMap<>(task.getActionResults()));
|
||||
}
|
||||
|
||||
private boolean executeAction(SecurityAction action, Map<String, Object> context) {
|
||||
switch (action.getType()) {
|
||||
case "DETECT":
|
||||
return executeDetection(action, context);
|
||||
case "CORRELATE":
|
||||
return executeCorrelation(action, context);
|
||||
case "SCORE":
|
||||
return executeScore(action, context);
|
||||
case "BLOCK":
|
||||
return executeBlocking(action, context);
|
||||
case "LOCAL_BLOCK_OR_RATE_LIMIT":
|
||||
return executeLocalBlockOrRateLimit(action, context);
|
||||
case "NOTIFY":
|
||||
return executeNotification(action, context);
|
||||
case "LOG":
|
||||
return executeLogging(action, context);
|
||||
case "EDGE_BLOCK_OR_CHALLENGE":
|
||||
return executeEdgeBlockOrChallenge(action, context);
|
||||
case "ISOLATE":
|
||||
return executeIsolation(action, context);
|
||||
case "COLLECT":
|
||||
return executeCollection(action, context);
|
||||
case "INVESTIGATE":
|
||||
return executeInvestigation(action, context);
|
||||
case "REMEDIATE":
|
||||
return executeRemediation(action, context);
|
||||
case "SCAN":
|
||||
return executeScan(action, context);
|
||||
case "QUARANTINE":
|
||||
return executeQuarantine(action, context);
|
||||
case "ANALYZE":
|
||||
return executeAnalysis(action, context);
|
||||
case "CONTAIN":
|
||||
return executeContainment(action, context);
|
||||
case "ASSESS":
|
||||
return executeAssessment(action, context);
|
||||
case "PRIORITIZE":
|
||||
return executePrioritization(action, context);
|
||||
case "APPLY":
|
||||
return executeApplication(action, context);
|
||||
case "VERIFY":
|
||||
return executeVerification(action, context);
|
||||
case "REPORT":
|
||||
return executeReporting(action, context);
|
||||
case "MINECRAFT_DEFENSE_ACTION":
|
||||
return executeMinecraftDefenseAction(action, context);
|
||||
case "AUDIT":
|
||||
return executeAuditAction(action, context);
|
||||
default:
|
||||
logger.warn("Unknown action type: {}", action.getType());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean executeDetection(SecurityAction action, Map<String, Object> context) {
|
||||
logger.info("Executing detection action: {}", action.getName());
|
||||
context.put("detection-result", "threat-detected");
|
||||
context.put("detection-time", System.currentTimeMillis());
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean executeCorrelation(SecurityAction action, Map<String, Object> context) {
|
||||
logger.info("Executing correlation action: {}", action.getName());
|
||||
context.put("correlated", true);
|
||||
context.put("correlation-source", action.getName());
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean executeScore(SecurityAction action, Map<String, Object> context) {
|
||||
logger.info("Executing score action: {}", action.getName());
|
||||
int riskScore = ((Number) context.getOrDefault("riskScore", 85)).intValue();
|
||||
context.put("riskScore", riskScore);
|
||||
context.put("severity", riskScore >= 90 ? "CRITICAL" : riskScore >= 75 ? "HIGH" : "MEDIUM");
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean executeBlocking(SecurityAction action, Map<String, Object> context) {
|
||||
logger.info("Executing blocking action: {}", action.getName());
|
||||
String attackerIP = (String) context.get("attacker-ip");
|
||||
if (attackerIP != null) {
|
||||
context.put("blocked-ips", attackerIP);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean executeLocalBlockOrRateLimit(SecurityAction action, Map<String, Object> context) {
|
||||
logger.info("Executing local block/rate-limit action: {}", action.getName());
|
||||
String attackerIP = (String) context.get("attacker-ip");
|
||||
if (attackerIP == null || attackerIP.isBlank()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int riskScore = ((Number) context.getOrDefault("riskScore", 80)).intValue();
|
||||
if (riskScore >= 90) {
|
||||
context.put("local-action", hostEnforcementService.blockIp(attackerIP, action.getDescription(), 60));
|
||||
} else {
|
||||
context.put("local-action", hostEnforcementService.rateLimitIp(attackerIP, 60, action.getDescription()));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean executeNotification(SecurityAction action, Map<String, Object> context) {
|
||||
logger.info("Executing notification action: {}", action.getName());
|
||||
context.put("notification-sent", true);
|
||||
context.put("notification-time", System.currentTimeMillis());
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean executeEdgeBlockOrChallenge(SecurityAction action, Map<String, Object> context) {
|
||||
logger.info("Executing edge block/challenge action: {}", action.getName());
|
||||
String attackerIP = (String) context.get("attacker-ip");
|
||||
if (attackerIP == null || attackerIP.isBlank()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int riskScore = ((Number) context.getOrDefault("riskScore", 80)).intValue();
|
||||
if (riskScore >= 90) {
|
||||
context.put("edge-action", cloudflareIntegrationService.applyBlock(attackerIP, action.getDescription()));
|
||||
} else {
|
||||
context.put("edge-action", cloudflareIntegrationService.applyChallenge(attackerIP, action.getDescription()));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean executeLogging(SecurityAction action, Map<String, Object> context) {
|
||||
logger.info("Executing logging action: {}", action.getName());
|
||||
context.put("logged", true);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean executeIsolation(SecurityAction action, Map<String, Object> context) {
|
||||
logger.info("Executing isolation action: {}", action.getName());
|
||||
context.put("isolated", true);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean executeCollection(SecurityAction action, Map<String, Object> context) {
|
||||
logger.info("Executing collection action: {}", action.getName());
|
||||
context.put("evidence-collected", true);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean executeInvestigation(SecurityAction action, Map<String, Object> context) {
|
||||
logger.info("Executing investigation action: {}", action.getName());
|
||||
context.put("investigation-complete", true);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean executeRemediation(SecurityAction action, Map<String, Object> context) {
|
||||
logger.info("Executing remediation action: {}", action.getName());
|
||||
context.put("remediation-complete", true);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean executeScan(SecurityAction action, Map<String, Object> context) {
|
||||
logger.info("Executing scan action: {}", action.getName());
|
||||
context.put("scan-complete", true);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean executeQuarantine(SecurityAction action, Map<String, Object> context) {
|
||||
logger.info("Executing quarantine action: {}", action.getName());
|
||||
context.put("quarantined", true);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean executeAnalysis(SecurityAction action, Map<String, Object> context) {
|
||||
logger.info("Executing analysis action: {}", action.getName());
|
||||
context.put("analysis-complete", true);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean executeContainment(SecurityAction action, Map<String, Object> context) {
|
||||
logger.info("Executing containment action: {}", action.getName());
|
||||
context.put("contained", true);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean executeAssessment(SecurityAction action, Map<String, Object> context) {
|
||||
logger.info("Executing assessment action: {}", action.getName());
|
||||
context.put("assessment-complete", true);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean executePrioritization(SecurityAction action, Map<String, Object> context) {
|
||||
logger.info("Executing prioritization action: {}", action.getName());
|
||||
context.put("prioritized", true);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean executeApplication(SecurityAction action, Map<String, Object> context) {
|
||||
logger.info("Executing application action: {}", action.getName());
|
||||
context.put("applied", true);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean executeVerification(SecurityAction action, Map<String, Object> context) {
|
||||
logger.info("Executing verification action: {}", action.getName());
|
||||
context.put("verified", true);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean executeReporting(SecurityAction action, Map<String, Object> context) {
|
||||
logger.info("Executing reporting action: {}", action.getName());
|
||||
context.put("reported", true);
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean executeMinecraftDefenseAction(SecurityAction action, Map<String, Object> context) {
|
||||
logger.info("Executing Minecraft defense action: {}", action.getName());
|
||||
if (rconClient == null) {
|
||||
return true;
|
||||
}
|
||||
String workflow = (String) context.getOrDefault("workflow", "");
|
||||
rconClient.executeCommand("say [Aluer] Security workflow active: " + workflow);
|
||||
int riskScore = ((Number) context.getOrDefault("riskScore", 80)).intValue();
|
||||
if (workflow.contains("LOGIN") || workflow.contains("RCON") || riskScore >= 95) {
|
||||
rconClient.enableWhitelist();
|
||||
}
|
||||
context.put("minecraft-defense", "applied");
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean executeAuditAction(SecurityAction action, Map<String, Object> context) {
|
||||
logger.info("Executing audit action: {}", action.getName());
|
||||
if (securityAuditService != null) {
|
||||
securityAuditService.logEvent(
|
||||
"SECURITY_WORKFLOW",
|
||||
String.valueOf(context.getOrDefault("actor", "system")),
|
||||
String.valueOf(context.getOrDefault("workflow", "unknown")),
|
||||
String.valueOf(context.getOrDefault("detection-result", action.getDescription()))
|
||||
);
|
||||
}
|
||||
context.put("audited", true);
|
||||
return true;
|
||||
}
|
||||
|
||||
public OrchestrationTask getTaskStatus(String taskId) {
|
||||
return activeTasks.get(taskId);
|
||||
}
|
||||
|
||||
public Map<String, Object> getStats() {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("enabled", enabled);
|
||||
stats.put("totalWorkflowsExecuted", totalWorkflowsExecuted.get());
|
||||
stats.put("successfulWorkflows", successfulWorkflows.get());
|
||||
stats.put("failedWorkflows", failedWorkflows.get());
|
||||
stats.put("workflowsConfigured", workflows.size());
|
||||
stats.put("activeTasks", activeTasks.size());
|
||||
|
||||
double successRate = totalWorkflowsExecuted.get() > 0 ?
|
||||
successfulWorkflows.get() * 100.0 / totalWorkflowsExecuted.get() : 0;
|
||||
stats.put("successRate", String.format("%.2f%%", successRate));
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
public Map<String, SecurityWorkflow> getWorkflows() {
|
||||
return new HashMap<>(workflows);
|
||||
}
|
||||
|
||||
public List<OrchestrationTask> getActiveTasks() {
|
||||
return new ArrayList<>(activeTasks.values());
|
||||
}
|
||||
|
||||
public List<OrchestrationEvent> getRecentEvents(int limit) {
|
||||
List<OrchestrationEvent> events = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (OrchestrationEvent event : eventLog) {
|
||||
if (count++ >= limit) break;
|
||||
events.add(event);
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
private void logEvent(String taskId, String workflowName, String status, String details) {
|
||||
OrchestrationEvent event = new OrchestrationEvent(taskId, workflowName, status, details, LocalDateTime.now());
|
||||
eventLog.offer(event);
|
||||
if (eventLog.size() > 5000) {
|
||||
eventLog.poll();
|
||||
}
|
||||
}
|
||||
|
||||
public void enable() {
|
||||
enabled = true;
|
||||
logger.info("Security Orchestration enabled");
|
||||
}
|
||||
|
||||
public void disable() {
|
||||
enabled = false;
|
||||
logger.info("Security Orchestration disabled");
|
||||
}
|
||||
|
||||
public static class SecurityWorkflow {
|
||||
private final String name;
|
||||
private final List<SecurityAction> actions;
|
||||
|
||||
public SecurityWorkflow(String name, List<SecurityAction> actions) {
|
||||
this.name = name;
|
||||
this.actions = actions;
|
||||
}
|
||||
|
||||
public String getName() { return name; }
|
||||
public List<SecurityAction> getActions() { return actions; }
|
||||
}
|
||||
|
||||
public static class SecurityAction {
|
||||
private final String type;
|
||||
private final String name;
|
||||
private final String description;
|
||||
private final boolean critical;
|
||||
|
||||
public SecurityAction(String type, String name, String description) {
|
||||
this(type, name, description, false);
|
||||
}
|
||||
|
||||
public SecurityAction(String type, String name, String description, boolean critical) {
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.critical = critical;
|
||||
}
|
||||
|
||||
public String getType() { return type; }
|
||||
public String getName() { return name; }
|
||||
public String getDescription() { return description; }
|
||||
public boolean isCritical() { return critical; }
|
||||
}
|
||||
|
||||
public static class OrchestrationTask {
|
||||
private final String taskId;
|
||||
private final String workflowName;
|
||||
private final Map<String, Object> context;
|
||||
private final Map<String, Boolean> actionResults;
|
||||
private volatile String status;
|
||||
private final long startTime;
|
||||
|
||||
public OrchestrationTask(String taskId, String workflowName, Map<String, Object> context) {
|
||||
this.taskId = taskId;
|
||||
this.workflowName = workflowName;
|
||||
this.context = context;
|
||||
this.actionResults = new HashMap<>();
|
||||
this.status = "RUNNING";
|
||||
this.startTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void addActionResult(String actionName, boolean success) {
|
||||
actionResults.put(actionName, success);
|
||||
}
|
||||
|
||||
public String getTaskId() { return taskId; }
|
||||
public String getWorkflowName() { return workflowName; }
|
||||
public Map<String, Object> getContext() { return context; }
|
||||
public Map<String, Boolean> getActionResults() { return actionResults; }
|
||||
public String getStatus() { return status; }
|
||||
public void setStatus(String status) { this.status = status; }
|
||||
public long getStartTime() { return startTime; }
|
||||
public long getDuration() { return System.currentTimeMillis() - startTime; }
|
||||
}
|
||||
|
||||
public static class OrchestrationEvent {
|
||||
private final String taskId;
|
||||
private final String workflowName;
|
||||
private final String status;
|
||||
private final String details;
|
||||
private final LocalDateTime timestamp;
|
||||
|
||||
public OrchestrationEvent(String taskId, String workflowName, String status, String details, LocalDateTime timestamp) {
|
||||
this.taskId = taskId;
|
||||
this.workflowName = workflowName;
|
||||
this.status = status;
|
||||
this.details = details;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getTaskId() { return taskId; }
|
||||
public String getWorkflowName() { return workflowName; }
|
||||
public String getStatus() { return status; }
|
||||
public String getDetails() { return details; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
}
|
||||
|
||||
public static class WorkflowExecutionResult {
|
||||
private final String executionId;
|
||||
private final String workflowName;
|
||||
private final String status;
|
||||
private final boolean dryRun;
|
||||
private final Map<String, Boolean> actions;
|
||||
|
||||
public WorkflowExecutionResult(String executionId, String workflowName, String status, boolean dryRun, Map<String, Boolean> actions) {
|
||||
this.executionId = executionId;
|
||||
this.workflowName = workflowName;
|
||||
this.status = status;
|
||||
this.dryRun = dryRun;
|
||||
this.actions = actions;
|
||||
}
|
||||
|
||||
public String getExecutionId() { return executionId; }
|
||||
public String getWorkflowName() { return workflowName; }
|
||||
public String getStatus() { return status; }
|
||||
public boolean isDryRun() { return dryRun; }
|
||||
public Map<String, Boolean> getActions() { return actions; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,369 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.Duration;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Base64;
|
||||
|
||||
@Component
|
||||
public class SessionManagementService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(SessionManagementService.class);
|
||||
|
||||
private final Map<String, Session> sessions = new ConcurrentHashMap<>();
|
||||
private final Map<String, SessionMetadata> sessionMetadata = new ConcurrentHashMap<>();
|
||||
private final Queue<SessionEvent> eventLog = new ConcurrentLinkedQueue<>();
|
||||
private final Map<String, List<String>> userSessions = new ConcurrentHashMap<>();
|
||||
private final AtomicLong totalSessionsCreated = new AtomicLong(0);
|
||||
private final AtomicLong totalSessionsExpired = new AtomicLong(0);
|
||||
private final AtomicLong totalSessionsInvalidated = new AtomicLong(0);
|
||||
|
||||
private static final int DEFAULT_SESSION_TIMEOUT_MINUTES = 30;
|
||||
private static final int MAX_SESSIONS_PER_USER = 5;
|
||||
private static final int SESSION_CLEANUP_INTERVAL_MINUTES = 5;
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
public SessionManagementService() {
|
||||
logger.info("Session Management Service initialized");
|
||||
}
|
||||
|
||||
public Session createSession(String username, String ipAddress, String userAgent) {
|
||||
if (userSessions.containsKey(username)) {
|
||||
List<String> existing = userSessions.get(username);
|
||||
if (existing.size() >= MAX_SESSIONS_PER_USER) {
|
||||
logger.warn("User {} has reached maximum session limit", username);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
String sessionId = generateSessionId(username);
|
||||
|
||||
Session session = new Session(sessionId, username, ipAddress, userAgent);
|
||||
sessions.put(sessionId, session);
|
||||
|
||||
SessionMetadata metadata = new SessionMetadata(username, ipAddress);
|
||||
sessionMetadata.put(sessionId, metadata);
|
||||
|
||||
userSessions.computeIfAbsent(username, k -> new ArrayList<>()).add(sessionId);
|
||||
|
||||
totalSessionsCreated.incrementAndGet();
|
||||
logEvent(sessionId, username, ipAddress, "CREATED", "New session created");
|
||||
|
||||
logger.info("Created session for user: {} from IP: {}", username, ipAddress);
|
||||
return session;
|
||||
}
|
||||
|
||||
public Session getSession(String sessionId) {
|
||||
Session session = sessions.get(sessionId);
|
||||
if (session != null && !session.isExpired()) {
|
||||
session.updateLastActivity();
|
||||
return session;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean validateSession(String sessionId) {
|
||||
Session session = getSession(sessionId);
|
||||
return session != null;
|
||||
}
|
||||
|
||||
public boolean invalidateSession(String sessionId) {
|
||||
Session session = sessions.remove(sessionId);
|
||||
if (session != null) {
|
||||
SessionMetadata metadata = sessionMetadata.remove(sessionId);
|
||||
|
||||
List<String> userSessionList = userSessions.get(session.getUsername());
|
||||
if (userSessionList != null) {
|
||||
userSessionList.remove(sessionId);
|
||||
if (userSessionList.isEmpty()) {
|
||||
userSessions.remove(session.getUsername());
|
||||
}
|
||||
}
|
||||
|
||||
totalSessionsInvalidated.incrementAndGet();
|
||||
logEvent(sessionId, session.getUsername(), session.getIpAddress(), "INVALIDATED", "Session invalidated");
|
||||
|
||||
logger.info("Invalidated session: {} for user: {}", sessionId, session.getUsername());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean extendSession(String sessionId, int additionalMinutes) {
|
||||
Session session = sessions.get(sessionId);
|
||||
if (session != null) {
|
||||
session.extendTimeout(additionalMinutes);
|
||||
logEvent(sessionId, session.getUsername(), session.getIpAddress(), "EXTENDED", "Session extended by " + additionalMinutes + " minutes");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void refreshSession(String sessionId) {
|
||||
Session session = sessions.get(sessionId);
|
||||
if (session != null) {
|
||||
session.updateLastActivity();
|
||||
SessionMetadata metadata = sessionMetadata.get(sessionId);
|
||||
if (metadata != null) {
|
||||
metadata.incrementActivityCount();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<Session> getUserSessions(String username) {
|
||||
List<String> sessionIds = userSessions.get(username);
|
||||
if (sessionIds == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<Session> userSessionsList = new ArrayList<>();
|
||||
for (String sessionId : sessionIds) {
|
||||
Session session = sessions.get(sessionId);
|
||||
if (session != null && !session.isExpired()) {
|
||||
userSessionsList.add(session);
|
||||
}
|
||||
}
|
||||
return userSessionsList;
|
||||
}
|
||||
|
||||
public int invalidateUserSessions(String username) {
|
||||
List<String> sessionIds = userSessions.remove(username);
|
||||
if (sessionIds == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
for (String sessionId : sessionIds) {
|
||||
Session session = sessions.remove(sessionId);
|
||||
sessionMetadata.remove(sessionId);
|
||||
if (session != null) {
|
||||
count++;
|
||||
totalSessionsInvalidated.incrementAndGet();
|
||||
}
|
||||
}
|
||||
|
||||
logger.info("Invalidated {} sessions for user: {}", count, username);
|
||||
return count;
|
||||
}
|
||||
|
||||
public void cleanupExpiredSessions() {
|
||||
long now = System.currentTimeMillis();
|
||||
int expiredCount = 0;
|
||||
|
||||
List<String> expiredSessionIds = new ArrayList<>();
|
||||
for (Map.Entry<String, Session> entry : sessions.entrySet()) {
|
||||
if (entry.getValue().isExpired()) {
|
||||
expiredSessionIds.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
|
||||
for (String sessionId : expiredSessionIds) {
|
||||
Session session = sessions.remove(sessionId);
|
||||
if (session != null) {
|
||||
SessionMetadata metadata = sessionMetadata.remove(sessionId);
|
||||
|
||||
List<String> userSessionList = userSessions.get(session.getUsername());
|
||||
if (userSessionList != null) {
|
||||
userSessionList.remove(sessionId);
|
||||
if (userSessionList.isEmpty()) {
|
||||
userSessions.remove(session.getUsername());
|
||||
}
|
||||
}
|
||||
|
||||
expiredCount++;
|
||||
totalSessionsExpired.incrementAndGet();
|
||||
logEvent(sessionId, session.getUsername(), session.getIpAddress(), "EXPIRED", "Session expired");
|
||||
}
|
||||
}
|
||||
|
||||
if (expiredCount > 0) {
|
||||
logger.info("Cleaned up {} expired sessions", expiredCount);
|
||||
}
|
||||
}
|
||||
|
||||
private String generateSessionId(String username) {
|
||||
try {
|
||||
String data = username + System.currentTimeMillis() + UUID.randomUUID().toString();
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
byte[] hash = md.digest(data.getBytes());
|
||||
return Base64.getUrlEncoder().withoutPadding().encodeToString(hash);
|
||||
} catch (Exception e) {
|
||||
return UUID.randomUUID().toString();
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Object> getStats() {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("activeSessions", sessions.size());
|
||||
stats.put("totalSessionsCreated", totalSessionsCreated.get());
|
||||
stats.put("totalSessionsExpired", totalSessionsExpired.get());
|
||||
stats.put("totalSessionsInvalidated", totalSessionsInvalidated.get());
|
||||
stats.put("uniqueUsers", userSessions.size());
|
||||
|
||||
long now = System.currentTimeMillis();
|
||||
long activeCount = sessions.values().stream()
|
||||
.filter(s -> !s.isExpired())
|
||||
.count();
|
||||
stats.put("currentlyActive", activeCount);
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
public List<SessionEvent> getRecentEvents(int limit) {
|
||||
List<SessionEvent> events = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (SessionEvent event : eventLog) {
|
||||
if (count++ >= limit) break;
|
||||
events.add(event);
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
public Map<String, Session> getAllActiveSessions() {
|
||||
Map<String, Session> active = new HashMap<>();
|
||||
for (Map.Entry<String, Session> entry : sessions.entrySet()) {
|
||||
if (!entry.getValue().isExpired()) {
|
||||
active.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
return active;
|
||||
}
|
||||
|
||||
public List<Session> getSessionsByIP(String ipAddress) {
|
||||
List<Session> result = new ArrayList<>();
|
||||
for (Session session : sessions.values()) {
|
||||
if (session.getIpAddress().equals(ipAddress) && !session.isExpired()) {
|
||||
result.add(session);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void logEvent(String sessionId, String username, String ipAddress, String eventType, String details) {
|
||||
SessionEvent event = new SessionEvent(sessionId, username, ipAddress, eventType, details, LocalDateTime.now());
|
||||
eventLog.offer(event);
|
||||
|
||||
if (eventLog.size() > 5000) {
|
||||
eventLog.poll();
|
||||
}
|
||||
}
|
||||
|
||||
public void setDefaultSessionTimeout(int minutes) {
|
||||
logger.info("Default session timeout set to {} minutes", minutes);
|
||||
}
|
||||
|
||||
public static class Session {
|
||||
private final String sessionId;
|
||||
private final String username;
|
||||
private final String ipAddress;
|
||||
private final String userAgent;
|
||||
private final long createdAt;
|
||||
private volatile long lastActivityAt;
|
||||
private volatile long expiresAt;
|
||||
private volatile Map<String, Object> attributes = new ConcurrentHashMap<>();
|
||||
|
||||
public Session(String sessionId, String username, String ipAddress, String userAgent) {
|
||||
this.sessionId = sessionId;
|
||||
this.username = username;
|
||||
this.ipAddress = ipAddress;
|
||||
this.userAgent = userAgent;
|
||||
this.createdAt = System.currentTimeMillis();
|
||||
this.lastActivityAt = createdAt;
|
||||
this.expiresAt = createdAt + (DEFAULT_SESSION_TIMEOUT_MINUTES * 60 * 1000L);
|
||||
}
|
||||
|
||||
public void updateLastActivity() {
|
||||
this.lastActivityAt = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void extendTimeout(int additionalMinutes) {
|
||||
this.expiresAt = Math.max(expiresAt, System.currentTimeMillis()) + (additionalMinutes * 60 * 1000L);
|
||||
}
|
||||
|
||||
public boolean isExpired() {
|
||||
return System.currentTimeMillis() > expiresAt;
|
||||
}
|
||||
|
||||
public long getIdleTime() {
|
||||
return System.currentTimeMillis() - lastActivityAt;
|
||||
}
|
||||
|
||||
public String getSessionId() { return sessionId; }
|
||||
public String getUsername() { return username; }
|
||||
public String getIpAddress() { return ipAddress; }
|
||||
public String getUserAgent() { return userAgent; }
|
||||
public long getCreatedAt() { return createdAt; }
|
||||
public long getLastActivityAt() { return lastActivityAt; }
|
||||
public long getExpiresAt() { return expiresAt; }
|
||||
public Map<String, Object> getAttributes() { return attributes; }
|
||||
|
||||
public void setAttribute(String key, Object value) {
|
||||
attributes.put(key, value);
|
||||
}
|
||||
|
||||
public Object getAttribute(String key) {
|
||||
return attributes.get(key);
|
||||
}
|
||||
}
|
||||
|
||||
public static class SessionMetadata {
|
||||
private final String username;
|
||||
private final String originalIP;
|
||||
private volatile long activityCount;
|
||||
private volatile String lastActivity;
|
||||
private volatile long lastActivityTimestamp;
|
||||
|
||||
public SessionMetadata(String username, String originalIP) {
|
||||
this.username = username;
|
||||
this.originalIP = originalIP;
|
||||
this.activityCount = 0;
|
||||
this.lastActivity = "session_created";
|
||||
this.lastActivityTimestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void incrementActivityCount() {
|
||||
activityCount++;
|
||||
lastActivityTimestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public String getUsername() { return username; }
|
||||
public String getOriginalIP() { return originalIP; }
|
||||
public long getActivityCount() { return activityCount; }
|
||||
public String getLastActivity() { return lastActivity; }
|
||||
public void setLastActivity(String activity) { this.lastActivity = activity; }
|
||||
public long getLastActivityTimestamp() { return lastActivityTimestamp; }
|
||||
}
|
||||
|
||||
public static class SessionEvent {
|
||||
private final String sessionId;
|
||||
private final String username;
|
||||
private final String ipAddress;
|
||||
private final String eventType;
|
||||
private final String details;
|
||||
private final LocalDateTime timestamp;
|
||||
|
||||
public SessionEvent(String sessionId, String username, String ipAddress, String eventType, String details, LocalDateTime timestamp) {
|
||||
this.sessionId = sessionId;
|
||||
this.username = username;
|
||||
this.ipAddress = ipAddress;
|
||||
this.eventType = eventType;
|
||||
this.details = details;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getSessionId() { return sessionId; }
|
||||
public String getUsername() { return username; }
|
||||
public String getIpAddress() { return ipAddress; }
|
||||
public String getEventType() { return eventType; }
|
||||
public String getDetails() { return details; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,553 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import com.aluer.config.ServerGuardConfig;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.net.URL;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
|
||||
@Component
|
||||
public class ThreatIntelligenceService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ThreatIntelligenceService.class);
|
||||
|
||||
private final ServerGuardConfig config;
|
||||
private final Map<String, ThreatIndicator> threatDatabase = new ConcurrentHashMap<>();
|
||||
private final Map<String, IPReputation> ipReputations = new ConcurrentHashMap<>();
|
||||
private final Queue<ThreatEvent> eventLog = new ConcurrentLinkedQueue<>();
|
||||
private final Map<String, List<String>> threatFeeds = new ConcurrentHashMap<>();
|
||||
private final Map<String, FeedState> feedStates = new ConcurrentHashMap<>();
|
||||
private final Map<String, CachedLookup> lookupCache = new ConcurrentHashMap<>();
|
||||
private final AtomicLong totalLookups = new AtomicLong(0);
|
||||
private final AtomicLong threatsDetected = new AtomicLong(0);
|
||||
private final AtomicLong iocsIdentified = new AtomicLong(0);
|
||||
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
public ThreatIntelligenceService() {
|
||||
this(new ServerGuardConfig());
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public ThreatIntelligenceService(ServerGuardConfig config) {
|
||||
this.config = config;
|
||||
initializeDefaultThreatData();
|
||||
}
|
||||
|
||||
private void initializeDefaultThreatData() {
|
||||
addThreatIndicator("103.21.244.0/24", "NETWORK", "KNOWN_ATTACKER", "Known malicious IP range", 95);
|
||||
addThreatIndicator("103.22.200.0/24", "NETWORK", "BOTNET", "Botnet command and control", 90);
|
||||
addThreatIndicator("103.31.4.0/24", "NETWORK", "SPAM_SOURCE", "Spam sending infrastructure", 75);
|
||||
addThreatIndicator("104.16.0.0/12", "NETWORK", "MALWARE_HOST", "Malware distribution network", 85);
|
||||
addThreatIndicator("108.162.192.0/18", "NETWORK", "PHISHING", "Phishing campaign infrastructure", 80);
|
||||
addThreatIndicator("141.101.64.0/18", "NETWORK", "ADWARE", "Adware distribution", 60);
|
||||
addThreatIndicator("162.158.64.0/18", "NETWORK", "SUSPICIOUS", "Suspicious activity", 50);
|
||||
addThreatIndicator("172.64.0.0/13", "NETWORK", "TOR_EXIT", "Tor exit node", 70);
|
||||
addThreatIndicator("173.245.48.0/20", "NETWORK", "VPN_PROXY", "VPN/Proxy service", 65);
|
||||
addThreatIndicator("188.114.96.0/20", "NETWORK", "MALICIOUS", "Known malicious activity", 85);
|
||||
|
||||
addThreatIndicator("bad-actor.exe", "FILE_HASH", "MALWARE", "Known malware sample", 100);
|
||||
addThreatIndicator("eicar_test_file", "FILE_HASH", "TEST", "EICAR test file", 100);
|
||||
addThreatIndicator("suspicious-script.js", "FILE_HASH", "MALWARE", "Malicious JavaScript", 95);
|
||||
|
||||
addThreatIndicator("evil-domain.com", "DOMAIN", "MALICIOUS", "Known malicious domain", 90);
|
||||
addThreatIndicator("phishing-bank.fake", "DOMAIN", "PHISHING", "Phishing domain", 100);
|
||||
addThreatIndicator("ransom-c2.evil", "DOMAIN", "RANSOMWARE", "Ransomware command and control", 100);
|
||||
addThreatIndicator("malware-dist.net", "DOMAIN", "MALWARE_HOST", "Malware distribution", 95);
|
||||
addThreatIndicator("coin-miner.pool", "DOMAIN", "CRYPTOMINING", "Cryptocurrency mining pool", 80);
|
||||
|
||||
addThreatIndicator("CVE-2024-0001", "CVE", "VULNERABILITY", "Critical vulnerability", 100);
|
||||
addThreatIndicator("CVE-2024-0002", "CVE", "VULNERABILITY", "High severity vulnerability", 90);
|
||||
addThreatIndicator("CVE-2024-0003", "CVE", "VULNERABILITY", "Medium severity vulnerability", 70);
|
||||
|
||||
logger.info("Initialized threat database with {} indicators", threatDatabase.size());
|
||||
}
|
||||
|
||||
public void addThreatIndicator(String indicator, String type, String category, String description, int severity) {
|
||||
ThreatIndicator ti = new ThreatIndicator(indicator, type, category, description, severity);
|
||||
threatDatabase.put(indicator.toLowerCase(), ti);
|
||||
logger.info("Added threat indicator: {} [{}] severity: {}", indicator, type, severity);
|
||||
}
|
||||
|
||||
public ThreatLookupResult lookupIP(String ip) {
|
||||
CachedLookup cached = lookupCache.get("IP:" + ip);
|
||||
if (cached != null && !cached.isExpired(config.getSecurity().getThreatFeeds().getCacheTtlMinutes())) {
|
||||
return cached.result.copy();
|
||||
}
|
||||
|
||||
ThreatLookupResult result = new ThreatLookupResult();
|
||||
result.setIndicator(ip);
|
||||
result.setType("IP");
|
||||
result.setTimestamp(LocalDateTime.now());
|
||||
|
||||
totalLookups.incrementAndGet();
|
||||
|
||||
IPReputation rep = ipReputations.get(ip);
|
||||
if (rep != null) {
|
||||
result.setReputationScore(rep.getScore());
|
||||
result.setThreatCategories(new ArrayList<>(rep.getThreatCategories()));
|
||||
result.setLastSeen(rep.getLastSeen());
|
||||
result.setConfidence(rep.getConfidence());
|
||||
}
|
||||
|
||||
for (Map.Entry<String, ThreatIndicator> entry : threatDatabase.entrySet()) {
|
||||
if (isIPInRange(ip, entry.getKey())) {
|
||||
ThreatIndicator ti = entry.getValue();
|
||||
result.addMatchedIndicator(ti);
|
||||
threatsDetected.incrementAndGet();
|
||||
iocsIdentified.incrementAndGet();
|
||||
}
|
||||
}
|
||||
|
||||
if (result.getMatchedIndicators().isEmpty()) {
|
||||
result.setReputationScore(calculateDefaultReputation(ip));
|
||||
result.setThreatCategories(new ArrayList<>());
|
||||
result.setConfidence(50);
|
||||
}
|
||||
|
||||
updateIPReputation(ip, result);
|
||||
lookupCache.put("IP:" + ip, new CachedLookup(result.copy()));
|
||||
return result;
|
||||
}
|
||||
|
||||
public ThreatLookupResult lookupDomain(String domain) {
|
||||
CachedLookup cached = lookupCache.get("DOMAIN:" + domain.toLowerCase());
|
||||
if (cached != null && !cached.isExpired(config.getSecurity().getThreatFeeds().getCacheTtlMinutes())) {
|
||||
return cached.result.copy();
|
||||
}
|
||||
|
||||
ThreatLookupResult result = new ThreatLookupResult();
|
||||
result.setIndicator(domain);
|
||||
result.setType("DOMAIN");
|
||||
result.setTimestamp(LocalDateTime.now());
|
||||
|
||||
totalLookups.incrementAndGet();
|
||||
|
||||
ThreatIndicator ti = threatDatabase.get(domain.toLowerCase());
|
||||
if (ti != null) {
|
||||
result.addMatchedIndicator(ti);
|
||||
result.setReputationScore(ti.getSeverity());
|
||||
result.setThreatCategories(Collections.singletonList(ti.getCategory()));
|
||||
result.setConfidence(ti.getSeverity());
|
||||
threatsDetected.incrementAndGet();
|
||||
iocsIdentified.incrementAndGet();
|
||||
} else {
|
||||
result.setReputationScore(calculateDomainReputation(domain));
|
||||
result.setThreatCategories(new ArrayList<>());
|
||||
result.setConfidence(30);
|
||||
}
|
||||
|
||||
lookupCache.put("DOMAIN:" + domain.toLowerCase(), new CachedLookup(result.copy()));
|
||||
return result;
|
||||
}
|
||||
|
||||
public ThreatLookupResult lookupHash(String hash) {
|
||||
CachedLookup cached = lookupCache.get("HASH:" + hash.toLowerCase());
|
||||
if (cached != null && !cached.isExpired(config.getSecurity().getThreatFeeds().getCacheTtlMinutes())) {
|
||||
return cached.result.copy();
|
||||
}
|
||||
|
||||
ThreatLookupResult result = new ThreatLookupResult();
|
||||
result.setIndicator(hash);
|
||||
result.setType("FILE_HASH");
|
||||
result.setTimestamp(LocalDateTime.now());
|
||||
|
||||
totalLookups.incrementAndGet();
|
||||
|
||||
ThreatIndicator ti = threatDatabase.get(hash.toLowerCase());
|
||||
if (ti != null) {
|
||||
result.addMatchedIndicator(ti);
|
||||
result.setReputationScore(ti.getSeverity());
|
||||
result.setThreatCategories(Collections.singletonList(ti.getCategory()));
|
||||
result.setConfidence(ti.getSeverity());
|
||||
threatsDetected.incrementAndGet();
|
||||
iocsIdentified.incrementAndGet();
|
||||
} else {
|
||||
result.setReputationScore(0);
|
||||
result.setThreatCategories(new ArrayList<>());
|
||||
result.setConfidence(0);
|
||||
}
|
||||
|
||||
lookupCache.put("HASH:" + hash.toLowerCase(), new CachedLookup(result.copy()));
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean isIPInRange(String ip, String cidr) {
|
||||
if (!cidr.contains("/")) {
|
||||
return ip.equals(cidr);
|
||||
}
|
||||
try {
|
||||
String[] parts = cidr.split("/");
|
||||
byte[] address = InetAddress.getByName(parts[0]).getAddress();
|
||||
byte[] target = InetAddress.getByName(ip).getAddress();
|
||||
int prefix = Integer.parseInt(parts[1]);
|
||||
int fullBytes = prefix / 8;
|
||||
int remainingBits = prefix % 8;
|
||||
|
||||
for (int i = 0; i < fullBytes; i++) {
|
||||
if (address[i] != target[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (remainingBits == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
int mask = (-1) << (8 - remainingBits);
|
||||
return (address[fullBytes] & mask) == (target[fullBytes] & mask);
|
||||
} catch (UnknownHostException | NumberFormatException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private int calculateDefaultReputation(String ip) {
|
||||
if (ip.startsWith("127.") || ip.startsWith("192.168.") || ip.startsWith("10.")) {
|
||||
return 100;
|
||||
}
|
||||
if (ip.startsWith("172.")) {
|
||||
String[] parts = ip.split("\\.");
|
||||
if (parts.length >= 1) {
|
||||
int second = Integer.parseInt(parts[1]);
|
||||
if (second >= 16 && second <= 31) {
|
||||
return 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 50;
|
||||
}
|
||||
|
||||
private int calculateDomainReputation(String domain) {
|
||||
String[] parts = domain.split("\\.");
|
||||
if (parts.length < 2) return 50;
|
||||
|
||||
String tld = parts[parts.length - 1];
|
||||
if (tld.equals("tk") || tld.equals("ml") || tld.equals("ga") || tld.equals("cf") || tld.equals("gq")) {
|
||||
return 30;
|
||||
}
|
||||
|
||||
if (domain.contains("free") || domain.contains("download") || domain.contains("crack")) {
|
||||
return 20;
|
||||
}
|
||||
|
||||
return 50;
|
||||
}
|
||||
|
||||
private void updateIPReputation(String ip, ThreatLookupResult result) {
|
||||
IPReputation rep = ipReputations.computeIfAbsent(ip, k -> new IPReputation(ip));
|
||||
|
||||
if (!result.getMatchedIndicators().isEmpty()) {
|
||||
rep.addThreatCategory(result.getMatchedIndicators().get(0).getCategory());
|
||||
rep.adjustScore(-10);
|
||||
} else {
|
||||
rep.adjustScore(5);
|
||||
}
|
||||
|
||||
rep.setLastSeen(LocalDateTime.now());
|
||||
}
|
||||
|
||||
public void updateIPReputationFromExternal(String ip, int score, List<String> categories) {
|
||||
IPReputation rep = ipReputations.computeIfAbsent(ip, k -> new IPReputation(ip));
|
||||
rep.setScore(score);
|
||||
for (String category : categories) {
|
||||
rep.addThreatCategory(category);
|
||||
}
|
||||
rep.setLastSeen(LocalDateTime.now());
|
||||
logger.info("Updated reputation for IP: {} score: {}", ip, score);
|
||||
}
|
||||
|
||||
public Map<String, IPReputation> getMaliciousIPs(int limit) {
|
||||
Map<String, IPReputation> malicious = new HashMap<>();
|
||||
for (Map.Entry<String, IPReputation> entry : ipReputations.entrySet()) {
|
||||
if (entry.getValue().getScore() < 30) {
|
||||
malicious.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
return malicious;
|
||||
}
|
||||
|
||||
public void addThreatFeed(String feedName, List<String> indicators) {
|
||||
threatFeeds.put(feedName, indicators);
|
||||
logger.info("Added threat feed: {} with {} indicators", feedName, indicators.size());
|
||||
}
|
||||
|
||||
public Map<String, Object> refreshFeeds() {
|
||||
Map<String, Object> result = new LinkedHashMap<>();
|
||||
int loaded = 0;
|
||||
int failed = 0;
|
||||
|
||||
if (!config.getSecurity().getThreatFeeds().isEnabled()) {
|
||||
result.put("enabled", false);
|
||||
result.put("loadedFeeds", loaded);
|
||||
result.put("failedFeeds", failed);
|
||||
return result;
|
||||
}
|
||||
|
||||
for (ServerGuardConfig.FeedSourceConfig source : config.getSecurity().getThreatFeeds().getSources()) {
|
||||
if (!source.isEnabled() || source.getUrl() == null || source.getUrl().isBlank()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
List<String> indicators = downloadIndicators(source);
|
||||
if (!indicators.isEmpty()) {
|
||||
addThreatFeed(source.getName(), indicators);
|
||||
for (String indicator : indicators) {
|
||||
addThreatIndicator(indicator, source.getType(), "EXTERNAL_FEED",
|
||||
"Loaded from feed " + source.getName(), Math.min(100, Math.max(10, source.getWeight())));
|
||||
}
|
||||
feedStates.put(source.getName(), new FeedState(source.getName(), true, indicators.size(), LocalDateTime.now(), null));
|
||||
loaded++;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
feedStates.put(source.getName(), new FeedState(source.getName(), false, 0, LocalDateTime.now(), e.getMessage()));
|
||||
failed++;
|
||||
logger.warn("Threat feed refresh failed for {}: {}", source.getName(), e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
result.put("enabled", true);
|
||||
result.put("loadedFeeds", loaded);
|
||||
result.put("failedFeeds", failed);
|
||||
result.put("feedStates", getFeedStatus());
|
||||
return result;
|
||||
}
|
||||
|
||||
public Map<String, Object> getFeedStatus() {
|
||||
Map<String, Object> status = new LinkedHashMap<>();
|
||||
status.put("configuredSources", config.getSecurity().getThreatFeeds().getSources().size());
|
||||
status.put("activeFeeds", threatFeeds.size());
|
||||
status.put("states", new ArrayList<>(feedStates.values()));
|
||||
return status;
|
||||
}
|
||||
|
||||
private List<String> downloadIndicators(ServerGuardConfig.FeedSourceConfig source) throws IOException {
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(source.getUrl()).openConnection();
|
||||
connection.setConnectTimeout(config.getSecurity().getThreatFeeds().getConnectTimeoutMs());
|
||||
connection.setReadTimeout(config.getSecurity().getThreatFeeds().getReadTimeoutMs());
|
||||
connection.setRequestMethod("GET");
|
||||
|
||||
List<String> indicators = new ArrayList<>();
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
String trimmed = line.trim();
|
||||
if (!trimmed.isEmpty() && !trimmed.startsWith("#")) {
|
||||
indicators.add(trimmed);
|
||||
}
|
||||
}
|
||||
}
|
||||
return indicators;
|
||||
}
|
||||
|
||||
public Map<String, Object> getStats() {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("totalLookups", totalLookups.get());
|
||||
stats.put("threatsDetected", threatsDetected.get());
|
||||
stats.put("iocsIdentified", iocsIdentified.get());
|
||||
stats.put("indicatorsInDatabase", threatDatabase.size());
|
||||
stats.put("uniqueIPsAnalyzed", ipReputations.size());
|
||||
stats.put("threatFeeds", threatFeeds.size());
|
||||
stats.put("feedCacheEntries", lookupCache.size());
|
||||
|
||||
long maliciousCount = ipReputations.values().stream()
|
||||
.filter(r -> r.getScore() < 30)
|
||||
.count();
|
||||
stats.put("maliciousIPs", maliciousCount);
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
public List<ThreatEvent> getRecentEvents(int limit) {
|
||||
List<ThreatEvent> events = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (ThreatEvent event : eventLog) {
|
||||
if (count++ >= limit) break;
|
||||
events.add(event);
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
public void logThreatEvent(String indicator, String type, String details) {
|
||||
ThreatEvent event = new ThreatEvent(indicator, type, details, LocalDateTime.now());
|
||||
eventLog.offer(event);
|
||||
|
||||
if (eventLog.size() > 5000) {
|
||||
eventLog.poll();
|
||||
}
|
||||
}
|
||||
|
||||
public static class ThreatIndicator {
|
||||
private final String indicator;
|
||||
private final String type;
|
||||
private final String category;
|
||||
private final String description;
|
||||
private final int severity;
|
||||
|
||||
public ThreatIndicator(String indicator, String type, String category, String description, int severity) {
|
||||
this.indicator = indicator;
|
||||
this.type = type;
|
||||
this.category = category;
|
||||
this.description = description;
|
||||
this.severity = severity;
|
||||
}
|
||||
|
||||
public String getIndicator() { return indicator; }
|
||||
public String getType() { return type; }
|
||||
public String getCategory() { return category; }
|
||||
public String getDescription() { return description; }
|
||||
public int getSeverity() { return severity; }
|
||||
}
|
||||
|
||||
public static class ThreatLookupResult {
|
||||
private String indicator;
|
||||
private String type;
|
||||
private int reputationScore;
|
||||
private List<String> threatCategories;
|
||||
private List<ThreatIndicator> matchedIndicators;
|
||||
private LocalDateTime lastSeen;
|
||||
private int confidence;
|
||||
private LocalDateTime timestamp;
|
||||
|
||||
public ThreatLookupResult() {
|
||||
this.threatCategories = new ArrayList<>();
|
||||
this.matchedIndicators = new ArrayList<>();
|
||||
}
|
||||
|
||||
public String getIndicator() { return indicator; }
|
||||
public void setIndicator(String indicator) { this.indicator = indicator; }
|
||||
public String getType() { return type; }
|
||||
public void setType(String type) { this.type = type; }
|
||||
public int getReputationScore() { return reputationScore; }
|
||||
public void setReputationScore(int reputationScore) { this.reputationScore = reputationScore; }
|
||||
public List<String> getThreatCategories() { return threatCategories; }
|
||||
public void setThreatCategories(List<String> threatCategories) { this.threatCategories = threatCategories; }
|
||||
public List<ThreatIndicator> getMatchedIndicators() { return matchedIndicators; }
|
||||
public void addMatchedIndicator(ThreatIndicator indicator) { this.matchedIndicators.add(indicator); }
|
||||
public LocalDateTime getLastSeen() { return lastSeen; }
|
||||
public void setLastSeen(LocalDateTime lastSeen) { this.lastSeen = lastSeen; }
|
||||
public int getConfidence() { return confidence; }
|
||||
public void setConfidence(int confidence) { this.confidence = confidence; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
public void setTimestamp(LocalDateTime timestamp) { this.timestamp = timestamp; }
|
||||
|
||||
public ThreatLookupResult copy() {
|
||||
ThreatLookupResult copy = new ThreatLookupResult();
|
||||
copy.setIndicator(indicator);
|
||||
copy.setType(type);
|
||||
copy.setReputationScore(reputationScore);
|
||||
copy.setThreatCategories(new ArrayList<>(threatCategories));
|
||||
for (ThreatIndicator matchedIndicator : matchedIndicators) {
|
||||
copy.addMatchedIndicator(matchedIndicator);
|
||||
}
|
||||
copy.setLastSeen(lastSeen);
|
||||
copy.setConfidence(confidence);
|
||||
copy.setTimestamp(timestamp);
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
|
||||
public static class IPReputation {
|
||||
private final String ip;
|
||||
private volatile int score;
|
||||
private final Set<String> threatCategories;
|
||||
private volatile LocalDateTime firstSeen;
|
||||
private volatile LocalDateTime lastSeen;
|
||||
|
||||
public IPReputation(String ip) {
|
||||
this.ip = ip;
|
||||
this.score = 50;
|
||||
this.threatCategories = new HashSet<>();
|
||||
this.firstSeen = LocalDateTime.now();
|
||||
this.lastSeen = LocalDateTime.now();
|
||||
}
|
||||
|
||||
public void adjustScore(int delta) {
|
||||
this.score = Math.max(-100, Math.min(100, this.score + delta));
|
||||
}
|
||||
|
||||
public void addThreatCategory(String category) {
|
||||
this.threatCategories.add(category);
|
||||
}
|
||||
|
||||
public int getConfidence() {
|
||||
return Math.min(100, 50 + threatCategories.size() * 10);
|
||||
}
|
||||
|
||||
public String getIp() { return ip; }
|
||||
public int getScore() { return score; }
|
||||
public void setScore(int score) { this.score = score; }
|
||||
public Set<String> getThreatCategories() { return threatCategories; }
|
||||
public LocalDateTime getFirstSeen() { return firstSeen; }
|
||||
public LocalDateTime getLastSeen() { return lastSeen; }
|
||||
public void setLastSeen(LocalDateTime lastSeen) { this.lastSeen = lastSeen; }
|
||||
}
|
||||
|
||||
public static class ThreatEvent {
|
||||
private final String indicator;
|
||||
private final String type;
|
||||
private final String details;
|
||||
private final LocalDateTime timestamp;
|
||||
|
||||
public ThreatEvent(String indicator, String type, String details, LocalDateTime timestamp) {
|
||||
this.indicator = indicator;
|
||||
this.type = type;
|
||||
this.details = details;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getIndicator() { return indicator; }
|
||||
public String getType() { return type; }
|
||||
public String getDetails() { return details; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
}
|
||||
|
||||
public static class FeedState {
|
||||
private final String name;
|
||||
private final boolean success;
|
||||
private final int indicatorsLoaded;
|
||||
private final LocalDateTime lastRefresh;
|
||||
private final String error;
|
||||
|
||||
public FeedState(String name, boolean success, int indicatorsLoaded, LocalDateTime lastRefresh, String error) {
|
||||
this.name = name;
|
||||
this.success = success;
|
||||
this.indicatorsLoaded = indicatorsLoaded;
|
||||
this.lastRefresh = lastRefresh;
|
||||
this.error = error;
|
||||
}
|
||||
|
||||
public String getName() { return name; }
|
||||
public boolean isSuccess() { return success; }
|
||||
public int getIndicatorsLoaded() { return indicatorsLoaded; }
|
||||
public LocalDateTime getLastRefresh() { return lastRefresh; }
|
||||
public String getError() { return error; }
|
||||
}
|
||||
|
||||
private static class CachedLookup {
|
||||
private final ThreatLookupResult result;
|
||||
private final long createdAt;
|
||||
|
||||
private CachedLookup(ThreatLookupResult result) {
|
||||
this.result = result;
|
||||
this.createdAt = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
private boolean isExpired(long ttlMinutes) {
|
||||
return System.currentTimeMillis() - createdAt > ttlMinutes * 60_000L;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,293 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.*;
|
||||
import java.util.regex.*;
|
||||
|
||||
@Service
|
||||
public class TrafficAnalysisService {
|
||||
|
||||
private final Map<String, TrafficSession> sessions = new ConcurrentHashMap<>();
|
||||
private final Map<String, List<TrafficSample>> trafficHistory = new ConcurrentHashMap<>();
|
||||
private final Map<String, AnomalyScore> anomalyScores = new ConcurrentHashMap<>();
|
||||
private final Queue<TrafficAlert> alerts = new ConcurrentLinkedQueue<>();
|
||||
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
|
||||
|
||||
private static final int MAX_SESSIONS = 10000;
|
||||
private static final long SESSION_TIMEOUT = 300000;
|
||||
private static final int HISTORY_SIZE = 1000;
|
||||
private static final double ANOMALY_THRESHOLD = 0.75;
|
||||
|
||||
private final AtomicLong totalPacketsAnalyzed = new AtomicLong(0);
|
||||
private final AtomicLong totalAnomaliesDetected = new AtomicLong(0);
|
||||
|
||||
public TrafficAnalysisService() {
|
||||
startAnalysisTask();
|
||||
}
|
||||
|
||||
public boolean analyzePacket(String sourceIP, String destIP, int port, int packetSize, String protocol) {
|
||||
totalPacketsAnalyzed.incrementAndGet();
|
||||
|
||||
TrafficSession session = sessions.computeIfAbsent(sourceIP, k -> new TrafficSession(sourceIP));
|
||||
session.recordPacket(destIP, port, packetSize, protocol);
|
||||
|
||||
boolean isAnomaly = checkForAnomalies(session);
|
||||
|
||||
if (isAnomaly) {
|
||||
AnomalyScore score = anomalyScores.computeIfAbsent(sourceIP, k -> new AnomalyScore(sourceIP));
|
||||
score.increment();
|
||||
|
||||
if (score.getScore() > ANOMALY_THRESHOLD) {
|
||||
triggerAlert(sourceIP, "TRAFFIC_ANOMALY", "Anomaly score: " + score.getScore());
|
||||
totalAnomaliesDetected.incrementAndGet();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
recordTrafficSample(sourceIP, packetSize, protocol);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean checkForAnomalies(TrafficSession session) {
|
||||
if (session.packetCount > 1000) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (session.totalBytes > 100_000_000) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (session.uniquePorts.size() > 100) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (session.uniqueDestinations.size() > 50) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void recordTrafficSample(String ip, int packetSize, String protocol) {
|
||||
List<TrafficSample> history = trafficHistory.computeIfAbsent(ip, k -> new CopyOnWriteArrayList<>());
|
||||
|
||||
TrafficSample sample = new TrafficSample(packetSize, protocol, System.currentTimeMillis());
|
||||
history.add(sample);
|
||||
|
||||
while (history.size() > HISTORY_SIZE) {
|
||||
history.remove(0);
|
||||
}
|
||||
}
|
||||
|
||||
public void triggerAlert(String sourceIP, String type, String details) {
|
||||
TrafficAlert alert = new TrafficAlert(sourceIP, type, details, System.currentTimeMillis());
|
||||
alerts.offer(alert);
|
||||
|
||||
while (alerts.size() > 500) {
|
||||
alerts.poll();
|
||||
}
|
||||
}
|
||||
|
||||
public TrafficSession getSession(String ip) {
|
||||
return sessions.get(ip);
|
||||
}
|
||||
|
||||
public Collection<TrafficSession> getAllSessions() {
|
||||
return sessions.values();
|
||||
}
|
||||
|
||||
public AnomalyScore getAnomalyScore(String ip) {
|
||||
return anomalyScores.get(ip);
|
||||
}
|
||||
|
||||
public Map<String, AnomalyScore> getAllAnomalyScores() {
|
||||
return new HashMap<>(anomalyScores);
|
||||
}
|
||||
|
||||
public List<TrafficAlert> getAlerts(int limit) {
|
||||
List<TrafficAlert> result = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (TrafficAlert alert : alerts) {
|
||||
if (count++ >= limit) break;
|
||||
result.add(alert);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public Map<String, Object> getTrafficStats(String ip) {
|
||||
TrafficSession session = sessions.get(ip);
|
||||
if (session == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("packetCount", session.packetCount);
|
||||
stats.put("totalBytes", session.totalBytes);
|
||||
stats.put("uniquePorts", session.uniquePorts.size());
|
||||
stats.put("uniqueDestinations", session.uniqueDestinations.size());
|
||||
stats.put("protocols", new HashSet<>(session.protocols));
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
public Map<String, Object> getGlobalStats() {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("activeSessions", sessions.size());
|
||||
stats.put("totalPacketsAnalyzed", totalPacketsAnalyzed.get());
|
||||
stats.put("totalAnomaliesDetected", totalAnomaliesDetected.get());
|
||||
stats.put("uniqueIPsTracked", trafficHistory.size());
|
||||
|
||||
long totalBytes = sessions.values().stream()
|
||||
.mapToLong(s -> s.totalBytes)
|
||||
.sum();
|
||||
stats.put("totalBytesProcessed", totalBytes);
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
private void startAnalysisTask() {
|
||||
scheduler.scheduleAtFixedRate(() -> {
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
sessions.entrySet().removeIf(entry ->
|
||||
now - entry.getValue().lastActivity > SESSION_TIMEOUT);
|
||||
|
||||
anomalyScores.entrySet().removeIf(entry ->
|
||||
entry.getValue().getScore() < 0.1);
|
||||
|
||||
for (TrafficSession session : sessions.values()) {
|
||||
session.resetCountersIfNeeded();
|
||||
}
|
||||
|
||||
}, 60, 60, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public static class TrafficSession {
|
||||
private final String ip;
|
||||
private final Map<String, Integer> destinationPorts = new ConcurrentHashMap<>();
|
||||
private final Set<Integer> uniquePorts = ConcurrentHashMap.newKeySet();
|
||||
private final Set<String> uniqueDestinations = ConcurrentHashMap.newKeySet();
|
||||
private final Set<String> protocols = ConcurrentHashMap.newKeySet();
|
||||
|
||||
private volatile long packetCount = 0;
|
||||
private volatile long totalBytes = 0;
|
||||
private volatile long lastActivity;
|
||||
private volatile long windowStart = System.currentTimeMillis();
|
||||
|
||||
public TrafficSession(String ip) {
|
||||
this.ip = ip;
|
||||
this.lastActivity = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void recordPacket(String destIP, int port, int packetSize, String protocol) {
|
||||
packetCount++;
|
||||
totalBytes += packetSize;
|
||||
lastActivity = System.currentTimeMillis();
|
||||
|
||||
uniquePorts.add(port);
|
||||
uniqueDestinations.add(destIP);
|
||||
protocols.add(protocol);
|
||||
|
||||
String key = destIP + ":" + port;
|
||||
destinationPorts.merge(key, 1, Integer::sum);
|
||||
}
|
||||
|
||||
public void resetCountersIfNeeded() {
|
||||
long now = System.currentTimeMillis();
|
||||
if (now - windowStart > 60000) {
|
||||
packetCount = 0;
|
||||
totalBytes = 0;
|
||||
windowStart = now;
|
||||
}
|
||||
}
|
||||
|
||||
public String getIp() {
|
||||
return ip;
|
||||
}
|
||||
}
|
||||
|
||||
public static class TrafficSample {
|
||||
private final int size;
|
||||
private final String protocol;
|
||||
private final long timestamp;
|
||||
|
||||
public TrafficSample(int size, String protocol, long timestamp) {
|
||||
this.size = size;
|
||||
this.protocol = protocol;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public int getSize() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public String getProtocol() {
|
||||
return protocol;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
public static class AnomalyScore {
|
||||
private final String ip;
|
||||
private final AtomicInteger score = new AtomicInteger(0);
|
||||
private volatile long lastUpdate;
|
||||
|
||||
public AnomalyScore(String ip) {
|
||||
this.ip = ip;
|
||||
this.lastUpdate = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void increment() {
|
||||
score.updateAndGet(s -> Math.min(100, s + 10));
|
||||
lastUpdate = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void decrement() {
|
||||
score.updateAndGet(s -> Math.max(0, s - 5));
|
||||
lastUpdate = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public double getScore() {
|
||||
if (System.currentTimeMillis() - lastUpdate > 300000) {
|
||||
score.set(0);
|
||||
}
|
||||
return score.get() / 100.0;
|
||||
}
|
||||
}
|
||||
|
||||
public static class TrafficAlert {
|
||||
private final String sourceIP;
|
||||
private final String type;
|
||||
private final String details;
|
||||
private final long timestamp;
|
||||
|
||||
public TrafficAlert(String sourceIP, String type, String details, long timestamp) {
|
||||
this.sourceIP = sourceIP;
|
||||
this.type = type;
|
||||
this.details = details;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getSourceIP() {
|
||||
return sourceIP;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public String getDetails() {
|
||||
return details;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
@Component
|
||||
public class TrafficShapingService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(TrafficShapingService.class);
|
||||
|
||||
private final Map<String, TrafficClass> trafficClasses = new ConcurrentHashMap<>();
|
||||
private final Map<String, Queue<PacketRecord>> packetQueues = new ConcurrentHashMap<>();
|
||||
private final Map<String, TokenBucket> tokenBuckets = new ConcurrentHashMap<>();
|
||||
private final Map<String, TrafficStats> trafficStats = new ConcurrentHashMap<>();
|
||||
private final Queue<TrafficEvent> eventLog = new ConcurrentLinkedQueue<>();
|
||||
private final AtomicLong totalPacketsShaped = new AtomicLong(0);
|
||||
private final AtomicLong totalPacketsDropped = new AtomicLong(0);
|
||||
private final AtomicLong totalPacketsDelayed = new AtomicLong(0);
|
||||
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
public TrafficShapingService() {
|
||||
initializeDefaultClasses();
|
||||
}
|
||||
|
||||
private void initializeDefaultClasses() {
|
||||
addTrafficClass("high-priority", 1000, 100, 50);
|
||||
addTrafficClass("medium-priority", 500, 200, 100);
|
||||
addTrafficClass("low-priority", 100, 500, 200);
|
||||
addTrafficClass("bulk", 50, 1000, 500);
|
||||
|
||||
for (String className : trafficClasses.keySet()) {
|
||||
packetQueues.put(className, new ConcurrentLinkedQueue<>());
|
||||
tokenBuckets.put(className, new TokenBucket(
|
||||
trafficClasses.get(className).getBucketSize(),
|
||||
trafficClasses.get(className).getRefillRate()
|
||||
));
|
||||
trafficStats.put(className, new TrafficStats(className));
|
||||
}
|
||||
}
|
||||
|
||||
public void addTrafficClass(String name, int bucketSize, int refillRate, int maxDelay) {
|
||||
TrafficClass tc = new TrafficClass(name, bucketSize, refillRate, maxDelay);
|
||||
trafficClasses.put(name, tc);
|
||||
logger.info("Added traffic class: {}", name);
|
||||
}
|
||||
|
||||
public boolean processPacket(String sourceIP, String destIP, int port, int packetSize, String protocol) {
|
||||
String trafficClass = classifyPacket(sourceIP, destIP, port, protocol);
|
||||
TokenBucket bucket = tokenBuckets.get(trafficClass);
|
||||
TrafficStats stats = trafficStats.get(trafficClass);
|
||||
|
||||
if (bucket == null || stats == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!bucket.consume(packetSize)) {
|
||||
totalPacketsDropped.incrementAndGet();
|
||||
stats.recordDropped(packetSize);
|
||||
logEvent(sourceIP, destIP, packetSize, "DROPPED", trafficClass);
|
||||
return false;
|
||||
}
|
||||
|
||||
Queue<PacketRecord> queue = packetQueues.get(trafficClass);
|
||||
if (queue != null && queue.size() > 100) {
|
||||
totalPacketsDropped.incrementAndGet();
|
||||
stats.recordDropped(packetSize);
|
||||
logEvent(sourceIP, destIP, packetSize, "QUEUE_FULL", trafficClass);
|
||||
return false;
|
||||
}
|
||||
|
||||
PacketRecord record = new PacketRecord(sourceIP, destIP, packetSize, LocalDateTime.now());
|
||||
if (queue != null) {
|
||||
queue.offer(record);
|
||||
}
|
||||
|
||||
totalPacketsShaped.incrementAndGet();
|
||||
stats.recordProcessed(packetSize);
|
||||
logEvent(sourceIP, destIP, packetSize, "SHAPED", trafficClass);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private String classifyPacket(String sourceIP, String destIP, int port, String protocol) {
|
||||
if (isPriorityIP(sourceIP)) {
|
||||
return "high-priority";
|
||||
}
|
||||
if (port == 25565 || port == 25575) {
|
||||
return "high-priority";
|
||||
}
|
||||
if ("UDP".equalsIgnoreCase(protocol)) {
|
||||
return "medium-priority";
|
||||
}
|
||||
if (isKnownServer(port)) {
|
||||
return "medium-priority";
|
||||
}
|
||||
return "low-priority";
|
||||
}
|
||||
|
||||
private boolean isPriorityIP(String ip) {
|
||||
return ip.startsWith("127.") || ip.startsWith("192.168.") || ip.startsWith("10.");
|
||||
}
|
||||
|
||||
private boolean isKnownServer(int port) {
|
||||
return port > 0 && port < 1024;
|
||||
}
|
||||
|
||||
public Map<String, Object> getStats() {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("totalPacketsShaped", totalPacketsShaped.get());
|
||||
stats.put("totalPacketsDropped", totalPacketsDropped.get());
|
||||
stats.put("totalPacketsDelayed", totalPacketsDelayed.get());
|
||||
|
||||
Map<String, TrafficStats> classStats = new HashMap<>();
|
||||
for (Map.Entry<String, TrafficStats> entry : trafficStats.entrySet()) {
|
||||
classStats.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
stats.put("classStats", classStats);
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
public void adjustClassRate(String className, int newRefillRate) {
|
||||
TrafficClass tc = trafficClasses.get(className);
|
||||
if (tc != null) {
|
||||
tc.setRefillRate(newRefillRate);
|
||||
TokenBucket bucket = tokenBuckets.get(className);
|
||||
if (bucket != null) {
|
||||
bucket.setRate(newRefillRate);
|
||||
}
|
||||
logger.info("Adjusted rate for class {} to {}", className, newRefillRate);
|
||||
}
|
||||
}
|
||||
|
||||
public void clearQueue(String className) {
|
||||
Queue<PacketRecord> queue = packetQueues.get(className);
|
||||
if (queue != null) {
|
||||
queue.clear();
|
||||
logger.info("Cleared queue for class: {}", className);
|
||||
}
|
||||
}
|
||||
|
||||
private void logEvent(String sourceIP, String destIP, int size, String action, String trafficClass) {
|
||||
TrafficEvent event = new TrafficEvent(
|
||||
sourceIP, destIP, size, action, trafficClass, LocalDateTime.now()
|
||||
);
|
||||
eventLog.offer(event);
|
||||
|
||||
if (eventLog.size() > 10000) {
|
||||
eventLog.poll();
|
||||
}
|
||||
}
|
||||
|
||||
public List<TrafficEvent> getRecentEvents(int limit) {
|
||||
List<TrafficEvent> events = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (TrafficEvent event : eventLog) {
|
||||
if (count++ >= limit) break;
|
||||
events.add(event);
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
public Map<String, Object> getDetailedStats(String className) {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
TrafficClass tc = trafficClasses.get(className);
|
||||
TrafficStats ts = trafficStats.get(className);
|
||||
TokenBucket tb = tokenBuckets.get(className);
|
||||
|
||||
if (tc != null) {
|
||||
stats.put("bucketSize", tc.getBucketSize());
|
||||
stats.put("refillRate", tc.getRefillRate());
|
||||
stats.put("maxDelay", tc.getMaxDelay());
|
||||
}
|
||||
if (ts != null) {
|
||||
stats.put("packetsProcessed", ts.getPacketsProcessed());
|
||||
stats.put("packetsDropped", ts.getPacketsDropped());
|
||||
stats.put("bytesProcessed", ts.getBytesProcessed());
|
||||
}
|
||||
if (tb != null) {
|
||||
stats.put("availableTokens", tb.getAvailableTokens());
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
public static class TrafficClass {
|
||||
private final String name;
|
||||
private final int bucketSize;
|
||||
private int refillRate;
|
||||
private final int maxDelay;
|
||||
|
||||
public TrafficClass(String name, int bucketSize, int refillRate, int maxDelay) {
|
||||
this.name = name;
|
||||
this.bucketSize = bucketSize;
|
||||
this.refillRate = refillRate;
|
||||
this.maxDelay = maxDelay;
|
||||
}
|
||||
|
||||
public String getName() { return name; }
|
||||
public int getBucketSize() { return bucketSize; }
|
||||
public int getRefillRate() { return refillRate; }
|
||||
public int getMaxDelay() { return maxDelay; }
|
||||
public void setRefillRate(int refillRate) { this.refillRate = refillRate; }
|
||||
}
|
||||
|
||||
public static class TokenBucket {
|
||||
private final int maxTokens;
|
||||
private volatile int availableTokens;
|
||||
private volatile int refillRate;
|
||||
private long lastRefillTime;
|
||||
|
||||
public TokenBucket(int maxTokens, int refillRate) {
|
||||
this.maxTokens = maxTokens;
|
||||
this.availableTokens = maxTokens;
|
||||
this.refillRate = refillRate;
|
||||
this.lastRefillTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public synchronized boolean consume(int tokens) {
|
||||
refill();
|
||||
if (availableTokens >= tokens) {
|
||||
availableTokens -= tokens;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void refill() {
|
||||
long now = System.currentTimeMillis();
|
||||
long elapsed = now - lastRefillTime;
|
||||
if (elapsed > 100) {
|
||||
int tokensToAdd = (int) (elapsed * refillRate / 1000);
|
||||
availableTokens = Math.min(maxTokens, availableTokens + tokensToAdd);
|
||||
lastRefillTime = now;
|
||||
}
|
||||
}
|
||||
|
||||
public int getAvailableTokens() { return availableTokens; }
|
||||
public void setRate(int refillRate) { this.refillRate = refillRate; }
|
||||
}
|
||||
|
||||
public static class PacketRecord {
|
||||
private final String sourceIP;
|
||||
private final String destIP;
|
||||
private final int size;
|
||||
private final LocalDateTime timestamp;
|
||||
|
||||
public PacketRecord(String sourceIP, String destIP, int size, LocalDateTime timestamp) {
|
||||
this.sourceIP = sourceIP;
|
||||
this.destIP = destIP;
|
||||
this.size = size;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getSourceIP() { return sourceIP; }
|
||||
public String getDestIP() { return destIP; }
|
||||
public int getSize() { return size; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
}
|
||||
|
||||
public static class TrafficStats {
|
||||
private final String className;
|
||||
private final AtomicLong packetsProcessed = new AtomicLong(0);
|
||||
private final AtomicLong packetsDropped = new AtomicLong(0);
|
||||
private final AtomicLong bytesProcessed = new AtomicLong(0);
|
||||
|
||||
public TrafficStats(String className) {
|
||||
this.className = className;
|
||||
}
|
||||
|
||||
public void recordProcessed(int size) {
|
||||
packetsProcessed.incrementAndGet();
|
||||
bytesProcessed.addAndGet(size);
|
||||
}
|
||||
|
||||
public void recordDropped(int size) {
|
||||
packetsDropped.incrementAndGet();
|
||||
}
|
||||
|
||||
public String getClassName() { return className; }
|
||||
public long getPacketsProcessed() { return packetsProcessed.get(); }
|
||||
public long getPacketsDropped() { return packetsDropped.get(); }
|
||||
public long getBytesProcessed() { return bytesProcessed.get(); }
|
||||
}
|
||||
|
||||
public static class TrafficEvent {
|
||||
private final String sourceIP;
|
||||
private final String destIP;
|
||||
private final int size;
|
||||
private final String action;
|
||||
private final String trafficClass;
|
||||
private final LocalDateTime timestamp;
|
||||
|
||||
public TrafficEvent(String sourceIP, String destIP, int size, String action,
|
||||
String trafficClass, LocalDateTime timestamp) {
|
||||
this.sourceIP = sourceIP;
|
||||
this.destIP = destIP;
|
||||
this.size = size;
|
||||
this.action = action;
|
||||
this.trafficClass = trafficClass;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getSourceIP() { return sourceIP; }
|
||||
public String getDestIP() { return destIP; }
|
||||
public int getSize() { return size; }
|
||||
public String getAction() { return action; }
|
||||
public String getTrafficClass() { return trafficClass; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import com.aluer.console.AluerMirageShieldService;
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@Component
|
||||
public class WafRequestFilter extends OncePerRequestFilter {
|
||||
private final WebApplicationFirewall webApplicationFirewall;
|
||||
private final AluerMirageShieldService aluerMirageShieldService;
|
||||
|
||||
public WafRequestFilter(WebApplicationFirewall webApplicationFirewall,
|
||||
AluerMirageShieldService aluerMirageShieldService) {
|
||||
this.webApplicationFirewall = webApplicationFirewall;
|
||||
this.aluerMirageShieldService = aluerMirageShieldService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldNotFilter(HttpServletRequest request) {
|
||||
String uri = request.getRequestURI();
|
||||
return uri.startsWith("/favicon")
|
||||
|| uri.startsWith("/assets/")
|
||||
|| uri.endsWith(".css")
|
||||
|| uri.endsWith(".js")
|
||||
|| uri.endsWith(".png")
|
||||
|| uri.endsWith(".ico");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
FilterChain filterChain) throws ServletException, IOException {
|
||||
Map<String, String> headers = extractHeaders(request);
|
||||
String clientIp = resolveClientIp(request);
|
||||
WebApplicationFirewall.WAFResult result = webApplicationFirewall.checkRequest(
|
||||
clientIp,
|
||||
request.getMethod(),
|
||||
request.getRequestURI(),
|
||||
request.getQueryString(),
|
||||
headers,
|
||||
null
|
||||
);
|
||||
|
||||
if (result.isBlocked()) {
|
||||
String notice = aluerMirageShieldService.composeDeterrenceNotice(clientIp, result.getReason());
|
||||
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
response.setContentType("application/json");
|
||||
response.setHeader("X-Aluer-Deterrence", "identified-and-isolated");
|
||||
response.getWriter().write(
|
||||
"{\"status\":\"blocked\",\"reason\":\"" + escapeJson(result.getReason()) +
|
||||
"\",\"source\":\"" + escapeJson(clientIp) +
|
||||
"\",\"notice\":\"" + escapeJson(notice) + "\"}"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
request.setAttribute("aluer.waf.suspicious", result.isSuspicious());
|
||||
request.setAttribute("aluer.waf.rules", result.getMatchedRules());
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
|
||||
private Map<String, String> extractHeaders(HttpServletRequest request) {
|
||||
Map<String, String> headers = new LinkedHashMap<>();
|
||||
Enumeration<String> names = request.getHeaderNames();
|
||||
if (names == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
while (names.hasMoreElements()) {
|
||||
String name = names.nextElement();
|
||||
headers.put(name, request.getHeader(name));
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
private String resolveClientIp(HttpServletRequest request) {
|
||||
String forwarded = request.getHeader("X-Forwarded-For");
|
||||
if (forwarded != null && !forwarded.isBlank()) {
|
||||
return forwarded.split(",")[0].trim();
|
||||
}
|
||||
return request.getRemoteAddr();
|
||||
}
|
||||
|
||||
private String escapeJson(String value) {
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
return value.replace("\\", "\\\\").replace("\"", "\\\"");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,448 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
@Component
|
||||
public class WebApplicationFirewall {
|
||||
private static final Logger logger = LoggerFactory.getLogger(WebApplicationFirewall.class);
|
||||
|
||||
private final Map<String, WAFRule> rules = new ConcurrentHashMap<>();
|
||||
private final Queue<WAFEvent> eventLog = new ConcurrentLinkedQueue<>();
|
||||
private final Map<String, RequestStats> requestStats = new ConcurrentHashMap<>();
|
||||
private final Map<String, ClientReputation> clientReputations = new ConcurrentHashMap<>();
|
||||
private final AtomicLong totalRequests = new AtomicLong(0);
|
||||
private final AtomicLong blockedRequests = new AtomicLong(0);
|
||||
private final AtomicLong suspiciousRequests = new AtomicLong(0);
|
||||
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
public WebApplicationFirewall() {
|
||||
initializeDefaultRules();
|
||||
}
|
||||
|
||||
private void initializeDefaultRules() {
|
||||
addRule("SQL_INJECTION", "HIGH", Pattern.compile(
|
||||
".*(union|select|insert|update|delete|drop|create|alter|exec|execute|script|<script).*",
|
||||
Pattern.CASE_INSENSITIVE
|
||||
), "SQL注入攻击");
|
||||
|
||||
addRule("XSS_ATTACK", "HIGH", Pattern.compile(
|
||||
".*(<script|javascript:|onerror=|onload=|alert\\(|eval\\(|document\\.|window\\.).*",
|
||||
Pattern.CASE_INSENSITIVE
|
||||
), "跨站脚本攻击(XSS)");
|
||||
|
||||
addRule("PATH_TRAVERSAL", "MEDIUM", Pattern.compile(
|
||||
".*(\\.\\./|\\.\\.\\\\|%2e%2e/|%2e%2e\\\\).*",
|
||||
Pattern.CASE_INSENSITIVE
|
||||
), "路径遍历攻击");
|
||||
|
||||
addRule("COMMAND_INJECTION", "HIGH", Pattern.compile(
|
||||
".*(;|\\||`|$|&&|\\|\\||>|<).*(ls|cat|rm|mv|cp|chmod|chown|wget|curl|nc|bash|sh).*",
|
||||
Pattern.CASE_INSENSITIVE
|
||||
), "命令注入攻击");
|
||||
|
||||
addRule("LDAP_INJECTION", "MEDIUM", Pattern.compile(
|
||||
".*(\\(|\\)|\\*|\\?|\\?|\\?|\\\\n|\\\\r).*",
|
||||
Pattern.CASE_INSENSITIVE
|
||||
), "LDAP注入攻击");
|
||||
|
||||
addRule("XML_INJECTION", "MEDIUM", Pattern.compile(
|
||||
".*(<!DOCTYPE|<!ENTITY|<!ELEMENT|CDATA|\\?xml).*",
|
||||
Pattern.CASE_INSENSITIVE
|
||||
), "XML注入攻击");
|
||||
|
||||
addRule("FILE_INCLUDE", "MEDIUM", Pattern.compile(
|
||||
".*(include|require|include_once|require_once|php://|file://).*",
|
||||
Pattern.CASE_INSENSITIVE
|
||||
), "文件包含攻击");
|
||||
|
||||
addRule("SSRF_ATTACK", "HIGH", Pattern.compile(
|
||||
".*(https?://(127\\.0\\.0\\.1|localhost|0\\.0\\.0\\.0|169\\.254\\.169\\.254|::1)|gopher://|dict://|ftp://).*",
|
||||
Pattern.CASE_INSENSITIVE
|
||||
), "服务端请求伪造(SSRF)");
|
||||
|
||||
addRule("SSTI_ATTACK", "HIGH", Pattern.compile(
|
||||
".*(\\{\\{.*\\}\\}|\\$\\{.*\\}|<%=.*%>|#\\{.*\\}).*",
|
||||
Pattern.CASE_INSENSITIVE
|
||||
), "服务端模板注入(SSTI)");
|
||||
|
||||
addRule("JNDI_INJECTION", "HIGH", Pattern.compile(
|
||||
".*\\$\\{jndi:(ldap|ldaps|rmi|dns|iiop):.*",
|
||||
Pattern.CASE_INSENSITIVE
|
||||
), "JNDI注入");
|
||||
|
||||
addRule("JAVA_DESERIALIZATION", "HIGH", Pattern.compile(
|
||||
".*(rO0AB|ac ed 00 05|java\\.util\\.PriorityQueue|ysoserial).*",
|
||||
Pattern.CASE_INSENSITIVE
|
||||
), "Java反序列化利用");
|
||||
|
||||
addRule("CRLF_INJECTION", "MEDIUM", Pattern.compile(
|
||||
".*(%0d|%0a|\\\\r|\\\\n|\\r|\\n).*",
|
||||
Pattern.CASE_INSENSITIVE
|
||||
), "CRLF注入");
|
||||
|
||||
addRule("OPEN_REDIRECT", "MEDIUM", Pattern.compile(
|
||||
".*(redirect=|url=|next=|return=)https?://.*",
|
||||
Pattern.CASE_INSENSITIVE
|
||||
), "开放重定向");
|
||||
|
||||
addRule("VALIDATE_EMAIL", "LOW", Pattern.compile(
|
||||
"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"
|
||||
), "邮箱格式验证");
|
||||
|
||||
addRule("VALIDATE_IP", "LOW", Pattern.compile(
|
||||
"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"
|
||||
), "IP地址格式验证");
|
||||
|
||||
addRule("BLOCK_SENSITIVE", "HIGH", Pattern.compile(
|
||||
".*(password|passwd|pwd|secret|token|auth|api_key|apikey|private_key).*",
|
||||
Pattern.CASE_INSENSITIVE
|
||||
), "敏感信息访问");
|
||||
|
||||
logger.info("Initialized {} WAF rules", rules.size());
|
||||
}
|
||||
|
||||
public void addRule(String name, String severity, Pattern pattern, String description) {
|
||||
WAFRule rule = new WAFRule(name, severity, pattern, description);
|
||||
rules.put(name, rule);
|
||||
logger.info("Added WAF rule: {} [{}]", name, severity);
|
||||
}
|
||||
|
||||
public WAFResult checkRequest(String clientIP, String method, String uri, String queryString, Map<String, String> headers, String body) {
|
||||
WAFResult result = new WAFResult();
|
||||
result.setClientIP(clientIP);
|
||||
result.setMethod(method);
|
||||
result.setUri(uri);
|
||||
result.setTimestamp(LocalDateTime.now());
|
||||
|
||||
totalRequests.incrementAndGet();
|
||||
|
||||
updateClientStats(clientIP);
|
||||
|
||||
if (isClientBlocked(clientIP)) {
|
||||
result.setBlocked(true);
|
||||
result.setReason("Client is blocked");
|
||||
blockedRequests.incrementAndGet();
|
||||
logEvent(clientIP, method, uri, "BLOCKED", "Client blocked");
|
||||
return result;
|
||||
}
|
||||
|
||||
if (isRateLimited(clientIP)) {
|
||||
result.setBlocked(true);
|
||||
result.setReason("Rate limit exceeded");
|
||||
blockedRequests.incrementAndGet();
|
||||
updateClientReputation(clientIP, -20);
|
||||
logEvent(clientIP, method, uri, "BLOCKED", "Rate limit exceeded");
|
||||
return result;
|
||||
}
|
||||
|
||||
StringBuilder fullRequestBuilder = new StringBuilder();
|
||||
fullRequestBuilder.append(method).append(" ").append(uri);
|
||||
if (queryString != null) {
|
||||
fullRequestBuilder.append("?").append(queryString);
|
||||
}
|
||||
for (Map.Entry<String, String> header : headers.entrySet()) {
|
||||
if (header.getValue() != null) {
|
||||
fullRequestBuilder.append("\n").append(header.getKey()).append(": ").append(header.getValue());
|
||||
}
|
||||
}
|
||||
if (body != null) {
|
||||
fullRequestBuilder.append("\n").append(body);
|
||||
}
|
||||
String fullRequest = fullRequestBuilder.toString();
|
||||
|
||||
for (WAFRule rule : rules.values()) {
|
||||
if (!rule.isEnabled()) continue;
|
||||
|
||||
Matcher matcher = rule.getPattern().matcher(fullRequest);
|
||||
if (matcher.find()) {
|
||||
result.addMatchedRule(rule.getName());
|
||||
|
||||
if ("HIGH".equals(rule.getSeverity())) {
|
||||
result.setBlocked(true);
|
||||
result.setReason("High severity rule matched: " + rule.getDescription());
|
||||
blockedRequests.incrementAndGet();
|
||||
updateClientReputation(clientIP, -15);
|
||||
logEvent(clientIP, method, uri, "BLOCKED", rule.getName() + " - " + rule.getDescription());
|
||||
} else if ("MEDIUM".equals(rule.getSeverity())) {
|
||||
result.setSuspicious(true);
|
||||
result.addWarning("Medium severity rule matched: " + rule.getDescription());
|
||||
suspiciousRequests.incrementAndGet();
|
||||
updateClientReputation(clientIP, -5);
|
||||
logEvent(clientIP, method, uri, "SUSPICIOUS", rule.getName());
|
||||
} else {
|
||||
result.addWarning("Low severity rule matched: " + rule.getDescription());
|
||||
logEvent(clientIP, method, uri, "WARNING", rule.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<String, String> header : headers.entrySet()) {
|
||||
String headerValue = header.getValue();
|
||||
if (headerValue != null && headerValue.length() > 1000) {
|
||||
result.addWarning("Unusually long header: " + header.getKey());
|
||||
suspiciousRequests.incrementAndGet();
|
||||
}
|
||||
if (headerValue != null && looksLikeHeaderSmuggling(header.getKey(), headerValue)) {
|
||||
result.setSuspicious(true);
|
||||
result.addWarning("Possible header smuggling: " + header.getKey());
|
||||
suspiciousRequests.incrementAndGet();
|
||||
}
|
||||
}
|
||||
|
||||
if (!result.isBlocked()) {
|
||||
logEvent(clientIP, method, uri, "ALLOWED", "Request passed all rules");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean looksLikeHeaderSmuggling(String headerName, String headerValue) {
|
||||
String normalizedName = headerName == null ? "" : headerName.toLowerCase(Locale.ROOT);
|
||||
String normalizedValue = headerValue == null ? "" : headerValue.toLowerCase(Locale.ROOT);
|
||||
if (normalizedValue.contains("\r") || normalizedValue.contains("\n")) {
|
||||
return true;
|
||||
}
|
||||
return normalizedName.equals("host")
|
||||
&& (normalizedValue.contains("127.0.0.1") || normalizedValue.contains("localhost"));
|
||||
}
|
||||
|
||||
private void updateClientStats(String clientIP) {
|
||||
RequestStats stats = requestStats.computeIfAbsent(clientIP, k -> new RequestStats(clientIP));
|
||||
stats.incrementRequestCount();
|
||||
|
||||
long oneMinuteAgo = System.currentTimeMillis() - 60000;
|
||||
if (stats.getWindowStart() < oneMinuteAgo) {
|
||||
stats.setRequestCountInLastMinute(stats.getRequestCount() - stats.getRequestCountInLastMinute());
|
||||
stats.setWindowStart(System.currentTimeMillis());
|
||||
}
|
||||
stats.incrementRequestCountInLastMinute();
|
||||
}
|
||||
|
||||
private boolean isClientBlocked(String clientIP) {
|
||||
ClientReputation rep = clientReputations.get(clientIP);
|
||||
return rep != null && rep.getScore() < -100;
|
||||
}
|
||||
|
||||
private boolean isRateLimited(String clientIP) {
|
||||
RequestStats stats = requestStats.get(clientIP);
|
||||
if (stats == null) return false;
|
||||
|
||||
return stats.getRequestCountInLastMinute() > 120;
|
||||
}
|
||||
|
||||
private void updateClientReputation(String clientIP, int scoreChange) {
|
||||
ClientReputation rep = clientReputations.computeIfAbsent(clientIP, k -> new ClientReputation(clientIP));
|
||||
rep.adjustScore(scoreChange);
|
||||
}
|
||||
|
||||
public void blockClient(String clientIP, String reason) {
|
||||
ClientReputation rep = clientReputations.computeIfAbsent(clientIP, k -> new ClientReputation(clientIP));
|
||||
rep.setScore(-200);
|
||||
rep.setBlockReason(reason);
|
||||
logger.info("Blocked client: {} - {}", clientIP, reason);
|
||||
}
|
||||
|
||||
public void unblockClient(String clientIP) {
|
||||
ClientReputation rep = clientReputations.get(clientIP);
|
||||
if (rep != null) {
|
||||
rep.setScore(0);
|
||||
rep.setBlockReason(null);
|
||||
logger.info("Unblocked client: {}", clientIP);
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Object> getStats() {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("totalRequests", totalRequests.get());
|
||||
stats.put("blockedRequests", blockedRequests.get());
|
||||
stats.put("suspiciousRequests", suspiciousRequests.get());
|
||||
stats.put("activeClients", requestStats.size());
|
||||
stats.put("blockedClients", clientReputations.values().stream()
|
||||
.filter(r -> r.getScore() < -100).count());
|
||||
stats.put("rulesLoaded", rules.size());
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
public List<WAFEvent> getRecentEvents(int limit) {
|
||||
List<WAFEvent> events = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (WAFEvent event : eventLog) {
|
||||
if (count++ >= limit) break;
|
||||
events.add(event);
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
public Map<String, ClientReputation> getBlockedClients() {
|
||||
Map<String, ClientReputation> blocked = new HashMap<>();
|
||||
for (Map.Entry<String, ClientReputation> entry : clientReputations.entrySet()) {
|
||||
if (entry.getValue().getScore() < -100) {
|
||||
blocked.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
return blocked;
|
||||
}
|
||||
|
||||
private void logEvent(String clientIP, String method, String uri, String action, String details) {
|
||||
WAFEvent event = new WAFEvent(clientIP, method, uri, action, details, LocalDateTime.now());
|
||||
eventLog.offer(event);
|
||||
|
||||
if (eventLog.size() > 10000) {
|
||||
eventLog.poll();
|
||||
}
|
||||
}
|
||||
|
||||
public void enableRule(String ruleName) {
|
||||
WAFRule rule = rules.get(ruleName);
|
||||
if (rule != null) {
|
||||
rule.setEnabled(true);
|
||||
logger.info("Enabled WAF rule: {}", ruleName);
|
||||
}
|
||||
}
|
||||
|
||||
public void disableRule(String ruleName) {
|
||||
WAFRule rule = rules.get(ruleName);
|
||||
if (rule != null) {
|
||||
rule.setEnabled(false);
|
||||
logger.info("Disabled WAF rule: {}", ruleName);
|
||||
}
|
||||
}
|
||||
|
||||
public static class WAFRule {
|
||||
private final String name;
|
||||
private final String severity;
|
||||
private final Pattern pattern;
|
||||
private final String description;
|
||||
private volatile boolean enabled = true;
|
||||
|
||||
public WAFRule(String name, String severity, Pattern pattern, String description) {
|
||||
this.name = name;
|
||||
this.severity = severity;
|
||||
this.pattern = pattern;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getName() { return name; }
|
||||
public String getSeverity() { return severity; }
|
||||
public Pattern getPattern() { return pattern; }
|
||||
public String getDescription() { return description; }
|
||||
public boolean isEnabled() { return enabled; }
|
||||
public void setEnabled(boolean enabled) { this.enabled = enabled; }
|
||||
}
|
||||
|
||||
public static class WAFResult {
|
||||
private String clientIP;
|
||||
private String method;
|
||||
private String uri;
|
||||
private boolean blocked;
|
||||
private boolean suspicious;
|
||||
private String reason;
|
||||
private List<String> matchedRules = new ArrayList<>();
|
||||
private List<String> warnings = new ArrayList<>();
|
||||
private LocalDateTime timestamp;
|
||||
|
||||
public String getClientIP() { return clientIP; }
|
||||
public void setClientIP(String clientIP) { this.clientIP = clientIP; }
|
||||
public String getMethod() { return method; }
|
||||
public void setMethod(String method) { this.method = method; }
|
||||
public String getUri() { return uri; }
|
||||
public void setUri(String uri) { this.uri = uri; }
|
||||
public boolean isBlocked() { return blocked; }
|
||||
public void setBlocked(boolean blocked) { this.blocked = blocked; }
|
||||
public boolean isSuspicious() { return suspicious; }
|
||||
public void setSuspicious(boolean suspicious) { this.suspicious = suspicious; }
|
||||
public String getReason() { return reason; }
|
||||
public void setReason(String reason) { this.reason = reason; }
|
||||
public List<String> getMatchedRules() { return matchedRules; }
|
||||
public void addMatchedRule(String rule) { this.matchedRules.add(rule); }
|
||||
public List<String> getWarnings() { return warnings; }
|
||||
public void addWarning(String warning) { this.warnings.add(warning); }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
public void setTimestamp(LocalDateTime timestamp) { this.timestamp = timestamp; }
|
||||
}
|
||||
|
||||
public static class RequestStats {
|
||||
private final String clientIP;
|
||||
private volatile long requestCount;
|
||||
private volatile long requestCountInLastMinute;
|
||||
private volatile long windowStart;
|
||||
|
||||
public RequestStats(String clientIP) {
|
||||
this.clientIP = clientIP;
|
||||
this.windowStart = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void incrementRequestCount() { requestCount++; }
|
||||
public void incrementRequestCountInLastMinute() { requestCountInLastMinute++; }
|
||||
|
||||
public String getClientIP() { return clientIP; }
|
||||
public long getRequestCount() { return requestCount; }
|
||||
public long getRequestCountInLastMinute() { return requestCountInLastMinute; }
|
||||
public long getWindowStart() { return windowStart; }
|
||||
public void setRequestCountInLastMinute(long count) { this.requestCountInLastMinute = count; }
|
||||
public void setWindowStart(long windowStart) { this.windowStart = windowStart; }
|
||||
}
|
||||
|
||||
public static class ClientReputation {
|
||||
private final String clientIP;
|
||||
private volatile int score;
|
||||
private volatile String blockReason;
|
||||
private volatile long lastUpdated;
|
||||
|
||||
public ClientReputation(String clientIP) {
|
||||
this.clientIP = clientIP;
|
||||
this.score = 0;
|
||||
this.lastUpdated = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void adjustScore(int delta) {
|
||||
this.score = Math.max(-200, Math.min(100, this.score + delta));
|
||||
this.lastUpdated = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public String getClientIP() { return clientIP; }
|
||||
public int getScore() { return score; }
|
||||
public void setScore(int score) { this.score = score; }
|
||||
public String getBlockReason() { return blockReason; }
|
||||
public void setBlockReason(String blockReason) { this.blockReason = blockReason; }
|
||||
public long getLastUpdated() { return lastUpdated; }
|
||||
}
|
||||
|
||||
public static class WAFEvent {
|
||||
private final String clientIP;
|
||||
private final String method;
|
||||
private final String uri;
|
||||
private final String action;
|
||||
private final String details;
|
||||
private final LocalDateTime timestamp;
|
||||
|
||||
public WAFEvent(String clientIP, String method, String uri, String action, String details, LocalDateTime timestamp) {
|
||||
this.clientIP = clientIP;
|
||||
this.method = method;
|
||||
this.uri = uri;
|
||||
this.action = action;
|
||||
this.details = details;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getClientIP() { return clientIP; }
|
||||
public String getMethod() { return method; }
|
||||
public String getUri() { return uri; }
|
||||
public String getAction() { return action; }
|
||||
public String getDetails() { return details; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,508 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Component
|
||||
public class ZeroTrustArchitectureService {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ZeroTrustArchitectureService.class);
|
||||
|
||||
private final Map<String, TrustPolicy> policies = new ConcurrentHashMap<>();
|
||||
private final Map<String, DeviceProfile> devices = new ConcurrentHashMap<>();
|
||||
private final Map<String, UserContext> userContexts = new ConcurrentHashMap<>();
|
||||
private final Map<String, AccessDecision> accessDecisions = new ConcurrentHashMap<>();
|
||||
private final Queue<ZeroTrustEvent> eventLog = new ConcurrentLinkedQueue<>();
|
||||
private final Map<String, List<VerificationResult>> verificationHistory = new ConcurrentHashMap<>();
|
||||
private final AtomicLong totalAccessRequests = new AtomicLong(0);
|
||||
private final AtomicLong grantedAccess = new AtomicLong(0);
|
||||
private final AtomicLong deniedAccess = new AtomicLong(0);
|
||||
|
||||
private volatile boolean enabled = true;
|
||||
private volatile String defaultPolicy = "DENY_ALL";
|
||||
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
|
||||
|
||||
private static final int MAX_HISTORY = 10000;
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
public ZeroTrustArchitectureService() {
|
||||
initializeDefaultPolicies();
|
||||
startContinuousVerification();
|
||||
logger.info("Zero Trust Architecture Service initialized");
|
||||
}
|
||||
|
||||
private void initializeDefaultPolicies() {
|
||||
addPolicy("DENY_ALL", "DENY", "Default deny all", 0, new ArrayList<>());
|
||||
addPolicy("ALLOW_INTERNAL", "ALLOW", "Allow internal network", 100, Arrays.asList("10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"));
|
||||
addPolicy("ALLOW_KNOWN_DEVICES", "CONDITIONAL", "Allow known devices with valid certs", 80, new ArrayList<>());
|
||||
addPolicy("MFA_REQUIRED", "CONDITIONAL", "Require MFA for sensitive resources", 90, new ArrayList<>());
|
||||
addPolicy("TIME_BASED_ACCESS", "CONDITIONAL", "Allow during business hours", 70, new ArrayList<>());
|
||||
addPolicy("IP_REPUTATION_BASED", "CONDITIONAL", "Allow based on IP reputation", 85, new ArrayList<>());
|
||||
addPolicy("DEVICE_HEALTH_CHECK", "CONDITIONAL", "Require healthy device", 75, new ArrayList<>());
|
||||
addPolicy("LEAST_PRIVILEGE", "CONDITIONAL", "Grant minimum required access", 95, new ArrayList<>());
|
||||
|
||||
logger.info("Initialized {} trust policies", policies.size());
|
||||
}
|
||||
|
||||
public void addPolicy(String name, String type, String description, int priority, List<String> conditions) {
|
||||
TrustPolicy policy = new TrustPolicy(name, type, description, priority, conditions);
|
||||
policies.put(name, policy);
|
||||
}
|
||||
|
||||
public void addDevice(String deviceId, String type, String os, String macAddress, String owner) {
|
||||
DeviceProfile device = new DeviceProfile(deviceId, type, os, macAddress, owner);
|
||||
devices.put(deviceId, device);
|
||||
logger.info("Added device to Zero Trust: {}", deviceId);
|
||||
}
|
||||
|
||||
public AccessDecision evaluateAccess(String userId, String deviceId, String resource, String action, Map<String, Object> context) {
|
||||
totalAccessRequests.incrementAndGet();
|
||||
|
||||
AccessDecision decision = new AccessDecision();
|
||||
decision.setUserId(userId);
|
||||
decision.setDeviceId(deviceId);
|
||||
decision.setResource(resource);
|
||||
decision.setAction(action);
|
||||
decision.setTimestamp(LocalDateTime.now());
|
||||
|
||||
TrustPolicy policy = selectPolicy(userId, deviceId, resource);
|
||||
|
||||
if (policy == null) {
|
||||
decision.setGranted(false);
|
||||
decision.setReason("No matching policy found");
|
||||
deniedAccess.incrementAndGet();
|
||||
logEvent(userId, resource, "DENIED", "No matching policy");
|
||||
return decision;
|
||||
}
|
||||
|
||||
boolean deviceTrusted = verifyDevice(deviceId);
|
||||
boolean userVerified = verifyUser(userId, context);
|
||||
boolean contextSafe = verifyContext(context);
|
||||
boolean resourceAccessible = checkResourceAccess(resource, action);
|
||||
|
||||
decision.setPolicyName(policy.getName());
|
||||
decision.setDeviceTrusted(deviceTrusted);
|
||||
decision.setUserVerified(userVerified);
|
||||
decision.setContextSafe(contextSafe);
|
||||
|
||||
boolean shouldGrant = evaluatePolicyConditions(policy, deviceTrusted, userVerified, contextSafe, resourceAccessible);
|
||||
|
||||
decision.setGranted(shouldGrant);
|
||||
|
||||
if (shouldGrant) {
|
||||
decision.setReason("Access granted by policy: " + policy.getName());
|
||||
grantedAccess.incrementAndGet();
|
||||
logEvent(userId, resource, "GRANTED", "Policy: " + policy.getName());
|
||||
} else {
|
||||
decision.setReason("Access denied - policy conditions not met");
|
||||
deniedAccess.incrementAndGet();
|
||||
logEvent(userId, resource, "DENIED", "Policy conditions not met");
|
||||
}
|
||||
|
||||
accessDecisions.put(userId + ":" + resource + ":" + System.currentTimeMillis(), decision);
|
||||
|
||||
return decision;
|
||||
}
|
||||
|
||||
private TrustPolicy selectPolicy(String userId, String deviceId, String resource) {
|
||||
TrustPolicy matchedPolicy = null;
|
||||
int highestPriority = -1;
|
||||
|
||||
for (TrustPolicy policy : policies.values()) {
|
||||
if (matchesResource(resource, policy) && policy.getPriority() > highestPriority) {
|
||||
matchedPolicy = policy;
|
||||
highestPriority = policy.getPriority();
|
||||
}
|
||||
}
|
||||
|
||||
return matchedPolicy;
|
||||
}
|
||||
|
||||
private boolean matchesResource(String resource, TrustPolicy policy) {
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean verifyDevice(String deviceId) {
|
||||
DeviceProfile device = devices.get(deviceId);
|
||||
if (device == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean healthy = device.isHealthy();
|
||||
boolean encrypted = device.isEncrypted();
|
||||
boolean compliant = device.isCompliant();
|
||||
|
||||
return healthy && encrypted && compliant;
|
||||
}
|
||||
|
||||
private boolean verifyUser(String userId, Map<String, Object> context) {
|
||||
UserContext userContext = userContexts.get(userId);
|
||||
|
||||
if (userContext == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean mfaVerified = userContext.isMfaVerified();
|
||||
boolean sessionValid = userContext.isSessionValid();
|
||||
|
||||
return mfaVerified && sessionValid;
|
||||
}
|
||||
|
||||
private boolean verifyContext(Map<String, Object> context) {
|
||||
if (context == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String location = (String) context.get("location");
|
||||
String device = (String) context.get("device");
|
||||
String network = (String) context.get("network");
|
||||
|
||||
if (location != null && location.equals("UNKNOWN")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (network != null && network.equals("UNSECURED")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean checkResourceAccess(String resource, String action) {
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean evaluatePolicyConditions(TrustPolicy policy, boolean deviceTrusted, boolean userVerified, boolean contextSafe, boolean resourceAccessible) {
|
||||
switch (policy.getType()) {
|
||||
case "ALLOW":
|
||||
return resourceAccessible;
|
||||
|
||||
case "DENY":
|
||||
return false;
|
||||
|
||||
case "CONDITIONAL":
|
||||
if (policy.getName().equals("ALLOW_KNOWN_DEVICES")) {
|
||||
return deviceTrusted && resourceAccessible;
|
||||
}
|
||||
if (policy.getName().equals("MFA_REQUIRED")) {
|
||||
return userVerified && deviceTrusted && resourceAccessible;
|
||||
}
|
||||
if (policy.getName().equals("TIME_BASED_ACCESS")) {
|
||||
return isBusinessHours() && resourceAccessible;
|
||||
}
|
||||
if (policy.getName().equals("DEVICE_HEALTH_CHECK")) {
|
||||
return deviceTrusted && resourceAccessible;
|
||||
}
|
||||
return deviceTrusted && userVerified && contextSafe && resourceAccessible;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isBusinessHours() {
|
||||
int hour = LocalDateTime.now().getHour();
|
||||
return hour >= 9 && hour <= 18;
|
||||
}
|
||||
|
||||
public void updateUserContext(String userId, boolean mfaVerified, boolean sessionValid) {
|
||||
UserContext context = userContexts.computeIfAbsent(userId, k -> new UserContext(userId));
|
||||
context.setMfaVerified(mfaVerified);
|
||||
context.setSessionValid(sessionValid);
|
||||
}
|
||||
|
||||
public void updateDeviceStatus(String deviceId, boolean healthy, boolean encrypted, boolean compliant) {
|
||||
DeviceProfile device = devices.get(deviceId);
|
||||
if (device != null) {
|
||||
device.setHealthy(healthy);
|
||||
device.setEncrypted(encrypted);
|
||||
device.setCompliant(compliant);
|
||||
}
|
||||
}
|
||||
|
||||
public void revokeAccess(String userId, String resource) {
|
||||
logger.info("Revoking access for user {} to resource {}", userId, resource);
|
||||
logEvent(userId, resource, "REVOKED", "Access revoked manually");
|
||||
}
|
||||
|
||||
public void grantTemporaryAccess(String userId, String resource, int durationMinutes) {
|
||||
logger.info("Granting temporary access for user {} to resource {} for {} minutes", userId, resource, durationMinutes);
|
||||
logEvent(userId, resource, "TEMP_GRANTED", "Temporary access for " + durationMinutes + " minutes");
|
||||
}
|
||||
|
||||
private void startContinuousVerification() {
|
||||
scheduler.scheduleAtFixedRate(() -> {
|
||||
verifyAllActiveSessions();
|
||||
checkDeviceCompliance();
|
||||
analyzeAccessPatterns();
|
||||
}, 30, 30, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
private void verifyAllActiveSessions() {
|
||||
for (UserContext context : userContexts.values()) {
|
||||
if (context.isSessionValid()) {
|
||||
boolean stillValid = verifySession(context.getUserId());
|
||||
context.setSessionValid(stillValid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean verifySession(String userId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void checkDeviceCompliance() {
|
||||
for (DeviceProfile device : devices.values()) {
|
||||
if (!device.isHealthy()) {
|
||||
logger.warn("Device {} is not healthy", device.getDeviceId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void analyzeAccessPatterns() {
|
||||
Map<String, Long> resourceAccessCount = new HashMap<>();
|
||||
for (AccessDecision decision : accessDecisions.values()) {
|
||||
if (decision.isGranted()) {
|
||||
resourceAccessCount.merge(decision.getResource(), 1L, Long::sum);
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<String, Long> entry : resourceAccessCount.entrySet()) {
|
||||
if (entry.getValue() > 10000) {
|
||||
logger.warn("High access volume for resource: {} ({} accesses)", entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Object> getStats() {
|
||||
Map<String, Object> stats = new HashMap<>();
|
||||
stats.put("enabled", enabled);
|
||||
stats.put("defaultPolicy", defaultPolicy);
|
||||
stats.put("totalAccessRequests", totalAccessRequests.get());
|
||||
stats.put("grantedAccess", grantedAccess.get());
|
||||
stats.put("deniedAccess", deniedAccess.get());
|
||||
stats.put("policiesConfigured", policies.size());
|
||||
stats.put("registeredDevices", devices.size());
|
||||
stats.put("activeUsers", userContexts.size());
|
||||
|
||||
double grantRate = totalAccessRequests.get() > 0 ?
|
||||
grantedAccess.get() * 100.0 / totalAccessRequests.get() : 0;
|
||||
stats.put("grantRate", String.format("%.2f%%", grantRate));
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
public Map<String, TrustPolicy> getPolicies() {
|
||||
return new HashMap<>(policies);
|
||||
}
|
||||
|
||||
public Map<String, DeviceProfile> getDevices() {
|
||||
return new HashMap<>(devices);
|
||||
}
|
||||
|
||||
public List<AccessDecision> getRecentDecisions(int limit) {
|
||||
List<AccessDecision> decisions = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (AccessDecision decision : accessDecisions.values()) {
|
||||
if (count++ >= limit) break;
|
||||
decisions.add(decision);
|
||||
}
|
||||
return decisions;
|
||||
}
|
||||
|
||||
public List<ZeroTrustEvent> getRecentEvents(int limit) {
|
||||
List<ZeroTrustEvent> events = new ArrayList<>();
|
||||
int count = 0;
|
||||
for (ZeroTrustEvent event : eventLog) {
|
||||
if (count++ >= limit) break;
|
||||
events.add(event);
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
private void logEvent(String userId, String resource, String action, String details) {
|
||||
ZeroTrustEvent event = new ZeroTrustEvent(userId, resource, action, details, LocalDateTime.now());
|
||||
eventLog.offer(event);
|
||||
if (eventLog.size() > 5000) {
|
||||
eventLog.poll();
|
||||
}
|
||||
}
|
||||
|
||||
public void enable() {
|
||||
enabled = true;
|
||||
logger.info("Zero Trust Architecture enabled");
|
||||
}
|
||||
|
||||
public void disable() {
|
||||
enabled = false;
|
||||
logger.info("Zero Trust Architecture disabled");
|
||||
}
|
||||
|
||||
public static class TrustPolicy {
|
||||
private final String name;
|
||||
private final String type;
|
||||
private final String description;
|
||||
private final int priority;
|
||||
private final List<String> conditions;
|
||||
|
||||
public TrustPolicy(String name, String type, String description, int priority, List<String> conditions) {
|
||||
this.name = name;
|
||||
this.type = type;
|
||||
this.description = description;
|
||||
this.priority = priority;
|
||||
this.conditions = conditions;
|
||||
}
|
||||
|
||||
public String getName() { return name; }
|
||||
public String getType() { return type; }
|
||||
public String getDescription() { return description; }
|
||||
public int getPriority() { return priority; }
|
||||
public List<String> getConditions() { return conditions; }
|
||||
}
|
||||
|
||||
public static class DeviceProfile {
|
||||
private final String deviceId;
|
||||
private final String type;
|
||||
private final String os;
|
||||
private final String macAddress;
|
||||
private final String owner;
|
||||
private volatile boolean healthy = true;
|
||||
private volatile boolean encrypted = true;
|
||||
private volatile boolean compliant = true;
|
||||
private volatile long lastChecked;
|
||||
|
||||
public DeviceProfile(String deviceId, String type, String os, String macAddress, String owner) {
|
||||
this.deviceId = deviceId;
|
||||
this.type = type;
|
||||
this.os = os;
|
||||
this.macAddress = macAddress;
|
||||
this.owner = owner;
|
||||
this.lastChecked = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public String getDeviceId() { return deviceId; }
|
||||
public String getType() { return type; }
|
||||
public String getOs() { return os; }
|
||||
public String getMacAddress() { return macAddress; }
|
||||
public String getOwner() { return owner; }
|
||||
public boolean isHealthy() { return healthy; }
|
||||
public void setHealthy(boolean healthy) { this.healthy = healthy; }
|
||||
public boolean isEncrypted() { return encrypted; }
|
||||
public void setEncrypted(boolean encrypted) { this.encrypted = encrypted; }
|
||||
public boolean isCompliant() { return compliant; }
|
||||
public void setCompliant(boolean compliant) { this.compliant = compliant; }
|
||||
public long getLastChecked() { return lastChecked; }
|
||||
public void setLastChecked(long lastChecked) { this.lastChecked = lastChecked; }
|
||||
}
|
||||
|
||||
public static class UserContext {
|
||||
private final String userId;
|
||||
private volatile boolean mfaVerified = false;
|
||||
private volatile boolean sessionValid = false;
|
||||
private volatile long lastActivity;
|
||||
|
||||
public UserContext(String userId) {
|
||||
this.userId = userId;
|
||||
this.lastActivity = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public String getUserId() { return userId; }
|
||||
public boolean isMfaVerified() { return mfaVerified; }
|
||||
public void setMfaVerified(boolean mfaVerified) { this.mfaVerified = mfaVerified; }
|
||||
public boolean isSessionValid() { return sessionValid; }
|
||||
public void setSessionValid(boolean sessionValid) { this.sessionValid = sessionValid; }
|
||||
public long getLastActivity() { return lastActivity; }
|
||||
public void setLastActivity(long lastActivity) { this.lastActivity = lastActivity; }
|
||||
}
|
||||
|
||||
public static class AccessDecision {
|
||||
private String userId;
|
||||
private String deviceId;
|
||||
private String resource;
|
||||
private String action;
|
||||
private String policyName;
|
||||
private boolean granted;
|
||||
private String reason;
|
||||
private boolean deviceTrusted;
|
||||
private boolean userVerified;
|
||||
private boolean contextSafe;
|
||||
private LocalDateTime timestamp;
|
||||
|
||||
public String getUserId() { return userId; }
|
||||
public void setUserId(String userId) { this.userId = userId; }
|
||||
public String getDeviceId() { return deviceId; }
|
||||
public void setDeviceId(String deviceId) { this.deviceId = deviceId; }
|
||||
public String getResource() { return resource; }
|
||||
public void setResource(String resource) { this.resource = resource; }
|
||||
public String getAction() { return action; }
|
||||
public void setAction(String action) { this.action = action; }
|
||||
public String getPolicyName() { return policyName; }
|
||||
public void setPolicyName(String policyName) { this.policyName = policyName; }
|
||||
public boolean isGranted() { return granted; }
|
||||
public void setGranted(boolean granted) { this.granted = granted; }
|
||||
public String getReason() { return reason; }
|
||||
public void setReason(String reason) { this.reason = reason; }
|
||||
public boolean isDeviceTrusted() { return deviceTrusted; }
|
||||
public void setDeviceTrusted(boolean deviceTrusted) { this.deviceTrusted = deviceTrusted; }
|
||||
public boolean isUserVerified() { return userVerified; }
|
||||
public void setUserVerified(boolean userVerified) { this.userVerified = userVerified; }
|
||||
public boolean isContextSafe() { return contextSafe; }
|
||||
public void setContextSafe(boolean contextSafe) { this.contextSafe = contextSafe; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
public void setTimestamp(LocalDateTime timestamp) { this.timestamp = timestamp; }
|
||||
}
|
||||
|
||||
public static class VerificationResult {
|
||||
private final String entityId;
|
||||
private final String entityType;
|
||||
private final boolean verified;
|
||||
private final String details;
|
||||
private final LocalDateTime timestamp;
|
||||
|
||||
public VerificationResult(String entityId, String entityType, boolean verified, String details) {
|
||||
this.entityId = entityId;
|
||||
this.entityType = entityType;
|
||||
this.verified = verified;
|
||||
this.details = details;
|
||||
this.timestamp = LocalDateTime.now();
|
||||
}
|
||||
|
||||
public String getEntityId() { return entityId; }
|
||||
public String getEntityType() { return entityType; }
|
||||
public boolean isVerified() { return verified; }
|
||||
public String getDetails() { return details; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
}
|
||||
|
||||
public static class ZeroTrustEvent {
|
||||
private final String userId;
|
||||
private final String resource;
|
||||
private final String action;
|
||||
private final String details;
|
||||
private final LocalDateTime timestamp;
|
||||
|
||||
public ZeroTrustEvent(String userId, String resource, String action, String details, LocalDateTime timestamp) {
|
||||
this.userId = userId;
|
||||
this.resource = resource;
|
||||
this.action = action;
|
||||
this.details = details;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
public String getUserId() { return userId; }
|
||||
public String getResource() { return resource; }
|
||||
public String getAction() { return action; }
|
||||
public String getDetails() { return details; }
|
||||
public LocalDateTime getTimestamp() { return timestamp; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user