mirror of
https://github.com/MengMengCode/CLICD.git
synced 2026-08-07 14:14:44 +08:00
first commit
This commit is contained in:
@@ -0,0 +1,771 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"clicd/internal/config"
|
||||
)
|
||||
|
||||
// SecurityAlert represents a detected abuse event.
|
||||
type SecurityAlert struct {
|
||||
ID string `json:"id"`
|
||||
ContainerName string `json:"container_name"`
|
||||
Type string `json:"type"` // port_scan, horizontal_scan, brute_force, ddos, spam, malware, mining, proxy, reflection
|
||||
Severity string `json:"severity"` // low, medium, high, critical
|
||||
SourceIP string `json:"source_ip"`
|
||||
TargetIP string `json:"target_ip"`
|
||||
TargetPort int `json:"target_port"`
|
||||
Detail string `json:"detail"`
|
||||
LogLine string `json:"log_line"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
Count int `json:"count"`
|
||||
}
|
||||
|
||||
// SecurityScanner monitors container network activity for abuse patterns.
|
||||
type SecurityScanner struct {
|
||||
mu sync.Mutex
|
||||
alerts []SecurityAlert
|
||||
nextID int
|
||||
scanCount map[string]int
|
||||
stopChan chan struct{}
|
||||
}
|
||||
|
||||
type connEntry struct {
|
||||
dstIP string
|
||||
dstPort int
|
||||
proto string
|
||||
state string
|
||||
line string
|
||||
}
|
||||
|
||||
type trafficStats struct {
|
||||
total int
|
||||
totalSynSent int
|
||||
destCounts map[string]int
|
||||
destPorts map[string]map[int]int
|
||||
portDestCounts map[int]map[string]int
|
||||
portTotalCounts map[int]int
|
||||
udpDestCounts map[int]map[string]int
|
||||
udpTotalCounts map[int]int
|
||||
synSentByDst map[string]int
|
||||
}
|
||||
|
||||
var scanner *SecurityScanner
|
||||
var scannerStarted bool
|
||||
|
||||
var bruteForcePorts = map[int]string{
|
||||
21: "FTP",
|
||||
22: "SSH",
|
||||
23: "Telnet",
|
||||
135: "MS-RPC",
|
||||
139: "NetBIOS",
|
||||
445: "SMB",
|
||||
3306: "MySQL",
|
||||
3389: "RDP",
|
||||
5432: "PostgreSQL",
|
||||
5900: "VNC",
|
||||
5901: "VNC",
|
||||
5985: "WinRM",
|
||||
5986: "WinRM",
|
||||
6379: "Redis",
|
||||
9200: "Elasticsearch",
|
||||
27017: "MongoDB",
|
||||
}
|
||||
|
||||
var smtpPorts = map[int]string{
|
||||
25: "SMTP",
|
||||
465: "SMTPS",
|
||||
587: "SMTP submission",
|
||||
2525: "SMTP alternate",
|
||||
}
|
||||
|
||||
var reflectionPorts = map[int]string{
|
||||
17: "QOTD",
|
||||
19: "Chargen",
|
||||
53: "DNS",
|
||||
69: "TFTP",
|
||||
111: "Portmap",
|
||||
123: "NTP",
|
||||
137: "NetBIOS",
|
||||
161: "SNMP",
|
||||
389: "CLDAP",
|
||||
500: "IKE",
|
||||
1900: "SSDP",
|
||||
3702: "WS-Discovery",
|
||||
4500: "IPsec NAT-T",
|
||||
5353: "mDNS",
|
||||
11211: "Memcached",
|
||||
}
|
||||
|
||||
var miningPorts = map[int]string{
|
||||
3333: "Stratum",
|
||||
3334: "Stratum",
|
||||
3335: "Stratum",
|
||||
4444: "Stratum",
|
||||
5555: "Stratum",
|
||||
7777: "Stratum",
|
||||
8888: "Stratum",
|
||||
9999: "Stratum",
|
||||
14433: "Stratum",
|
||||
14444: "Stratum",
|
||||
}
|
||||
|
||||
var proxyPorts = map[int]string{
|
||||
1080: "SOCKS",
|
||||
3128: "HTTP proxy",
|
||||
8118: "Privoxy",
|
||||
9001: "Tor OR",
|
||||
9030: "Tor directory",
|
||||
9050: "Tor SOCKS",
|
||||
1194: "OpenVPN",
|
||||
51820: "WireGuard",
|
||||
}
|
||||
|
||||
var malwarePorts = map[int]string{
|
||||
1337: "common backdoor",
|
||||
31337: "Back Orifice",
|
||||
4444: "Metasploit/reverse shell",
|
||||
5555: "Android debug/reverse shell",
|
||||
6666: "IRC botnet",
|
||||
6667: "IRC botnet",
|
||||
6697: "IRC over TLS",
|
||||
9050: "Tor/C2 proxy",
|
||||
}
|
||||
|
||||
func InitScanner() {
|
||||
if scannerStarted {
|
||||
return
|
||||
}
|
||||
scannerStarted = true
|
||||
scanner = newSecurityScanner()
|
||||
go scanner.monitorLoop()
|
||||
}
|
||||
|
||||
func newSecurityScanner() *SecurityScanner {
|
||||
return &SecurityScanner{
|
||||
alerts: make([]SecurityAlert, 0),
|
||||
scanCount: make(map[string]int),
|
||||
stopChan: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func ensureScanner() *SecurityScanner {
|
||||
if scanner == nil {
|
||||
scanner = newSecurityScanner()
|
||||
}
|
||||
return scanner
|
||||
}
|
||||
|
||||
func (ss *SecurityScanner) monitorLoop() {
|
||||
ticker := time.NewTicker(30 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ss.stopChan:
|
||||
return
|
||||
case <-ticker.C:
|
||||
ss.checkAllContainers()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ss *SecurityScanner) checkAllContainers() {
|
||||
for _, c := range config.AppConfig.Containers {
|
||||
if c.Status != "running" || c.IP == "" {
|
||||
continue
|
||||
}
|
||||
ss.checkContainer(c.Name, c.IP)
|
||||
}
|
||||
}
|
||||
|
||||
func (ss *SecurityScanner) checkContainer(name, ip string) {
|
||||
lines := readConntrackLines(ip)
|
||||
if len(lines) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
stats := newTrafficStats()
|
||||
for _, line := range lines {
|
||||
conn, ok := parseConntrackLine(line, ip)
|
||||
if !ok || conn.dstIP == "" || conn.dstIP == ip {
|
||||
continue
|
||||
}
|
||||
stats.add(conn)
|
||||
}
|
||||
|
||||
if stats.total == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
ss.detectPortScans(name, ip, stats)
|
||||
ss.detectBruteForce(name, ip, stats)
|
||||
ss.detectSpam(name, ip, stats)
|
||||
ss.detectMassAbuse(name, ip, stats)
|
||||
ss.detectReflectionAbuse(name, ip, stats)
|
||||
ss.detectMining(name, ip, stats)
|
||||
ss.detectProxyAndTor(name, ip, stats)
|
||||
ss.detectMalware(name, ip, stats)
|
||||
}
|
||||
|
||||
func newTrafficStats() *trafficStats {
|
||||
return &trafficStats{
|
||||
destCounts: make(map[string]int),
|
||||
destPorts: make(map[string]map[int]int),
|
||||
portDestCounts: make(map[int]map[string]int),
|
||||
portTotalCounts: make(map[int]int),
|
||||
udpDestCounts: make(map[int]map[string]int),
|
||||
udpTotalCounts: make(map[int]int),
|
||||
synSentByDst: make(map[string]int),
|
||||
}
|
||||
}
|
||||
|
||||
func (ts *trafficStats) add(conn connEntry) {
|
||||
ts.total++
|
||||
ts.destCounts[conn.dstIP]++
|
||||
|
||||
if conn.dstPort > 0 {
|
||||
if ts.destPorts[conn.dstIP] == nil {
|
||||
ts.destPorts[conn.dstIP] = make(map[int]int)
|
||||
}
|
||||
ts.destPorts[conn.dstIP][conn.dstPort]++
|
||||
|
||||
if ts.portDestCounts[conn.dstPort] == nil {
|
||||
ts.portDestCounts[conn.dstPort] = make(map[string]int)
|
||||
}
|
||||
ts.portDestCounts[conn.dstPort][conn.dstIP]++
|
||||
ts.portTotalCounts[conn.dstPort]++
|
||||
|
||||
if conn.proto == "udp" {
|
||||
if ts.udpDestCounts[conn.dstPort] == nil {
|
||||
ts.udpDestCounts[conn.dstPort] = make(map[string]int)
|
||||
}
|
||||
ts.udpDestCounts[conn.dstPort][conn.dstIP]++
|
||||
ts.udpTotalCounts[conn.dstPort]++
|
||||
}
|
||||
}
|
||||
|
||||
if conn.state == "SYN_SENT" {
|
||||
ts.totalSynSent++
|
||||
ts.synSentByDst[conn.dstIP]++
|
||||
}
|
||||
}
|
||||
|
||||
func (ss *SecurityScanner) detectPortScans(name, ip string, stats *trafficStats) {
|
||||
for dstIP, portCounts := range stats.destPorts {
|
||||
uniquePorts := len(portCounts)
|
||||
switch {
|
||||
case uniquePorts >= 20:
|
||||
ss.addAlert(name, "port_scan", "high", ip, dstIP, 0,
|
||||
fmt.Sprintf("端口扫描: 同一目标 %s 出现 %d 个不同目标端口", dstIP, uniquePorts),
|
||||
"")
|
||||
case uniquePorts >= 8:
|
||||
ss.addAlert(name, "port_scan", "medium", ip, dstIP, 0,
|
||||
fmt.Sprintf("可疑端口探测: 同一目标 %s 出现 %d 个不同目标端口", dstIP, uniquePorts),
|
||||
"")
|
||||
}
|
||||
}
|
||||
|
||||
for port, targets := range stats.portDestCounts {
|
||||
uniqueTargets := len(targets)
|
||||
if service, ok := bruteForcePorts[port]; ok {
|
||||
if uniqueTargets >= 30 {
|
||||
ss.addAlert(name, "brute_force", "critical", ip, "*", port,
|
||||
fmt.Sprintf("横向爆破: 目标服务 %s(%d) 覆盖 %d 个不同 IP", service, port, uniqueTargets),
|
||||
"")
|
||||
} else if uniqueTargets >= 10 {
|
||||
ss.addAlert(name, "brute_force", "high", ip, "*", port,
|
||||
fmt.Sprintf("疑似横向爆破: 目标服务 %s(%d) 覆盖 %d 个不同 IP", service, port, uniqueTargets),
|
||||
"")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if uniqueTargets >= 40 {
|
||||
ss.addAlert(name, "horizontal_scan", "high", ip, "*", port,
|
||||
fmt.Sprintf("横向扫描: 同一端口 %d 覆盖 %d 个不同目标", port, uniqueTargets),
|
||||
"")
|
||||
} else if uniqueTargets >= 15 {
|
||||
ss.addAlert(name, "horizontal_scan", "medium", ip, "*", port,
|
||||
fmt.Sprintf("可疑横向探测: 同一端口 %d 覆盖 %d 个不同目标", port, uniqueTargets),
|
||||
"")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ss *SecurityScanner) detectBruteForce(name, ip string, stats *trafficStats) {
|
||||
for dstIP, portCounts := range stats.destPorts {
|
||||
for port, count := range portCounts {
|
||||
service, sensitive := bruteForcePorts[port]
|
||||
if !sensitive {
|
||||
continue
|
||||
}
|
||||
|
||||
if count >= 20 {
|
||||
ss.addAlert(name, "brute_force", "critical", ip, dstIP, port,
|
||||
fmt.Sprintf("暴力破解: %s(%d) 当前连接数 %d", service, port, count),
|
||||
"")
|
||||
} else if count >= 10 {
|
||||
ss.addAlert(name, "brute_force", "high", ip, dstIP, port,
|
||||
fmt.Sprintf("疑似暴力破解: %s(%d) 当前连接数 %d", service, port, count),
|
||||
"")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ss *SecurityScanner) detectSpam(name, ip string, stats *trafficStats) {
|
||||
total, targets := countPorts(stats.portTotalCounts, stats.portDestCounts, smtpPorts)
|
||||
if total == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if targets >= 10 || total >= 30 {
|
||||
ss.addAlert(name, "spam", "critical", ip, "*", 25,
|
||||
fmt.Sprintf("疑似垃圾邮件: SMTP 相关端口当前连接 %d 条,覆盖 %d 个目标", total, targets),
|
||||
"")
|
||||
} else if targets >= 2 || total >= 5 {
|
||||
ss.addAlert(name, "spam", "high", ip, "*", 25,
|
||||
fmt.Sprintf("可疑邮件发送: SMTP 相关端口当前连接 %d 条,覆盖 %d 个目标", total, targets),
|
||||
"")
|
||||
}
|
||||
}
|
||||
|
||||
func (ss *SecurityScanner) detectMassAbuse(name, ip string, stats *trafficStats) {
|
||||
targets := len(stats.destCounts)
|
||||
switch {
|
||||
case targets >= 100:
|
||||
ss.addAlert(name, "ddos", "critical", ip, "*", 0,
|
||||
fmt.Sprintf("大规模对外连接: 当前覆盖 %d 个不同目标", targets),
|
||||
"")
|
||||
case targets >= 35:
|
||||
ss.addAlert(name, "ddos", "high", ip, "*", 0,
|
||||
fmt.Sprintf("大量对外连接: 当前覆盖 %d 个不同目标", targets),
|
||||
"")
|
||||
}
|
||||
|
||||
switch {
|
||||
case stats.total >= 500:
|
||||
ss.addAlert(name, "ddos", "critical", ip, "*", 0,
|
||||
fmt.Sprintf("异常大量连接: 当前 conntrack 出站记录 %d 条", stats.total),
|
||||
"")
|
||||
case stats.total >= 200:
|
||||
ss.addAlert(name, "ddos", "high", ip, "*", 0,
|
||||
fmt.Sprintf("高连接数: 当前 conntrack 出站记录 %d 条", stats.total),
|
||||
"")
|
||||
}
|
||||
|
||||
if stats.totalSynSent >= 100 {
|
||||
ss.addAlert(name, "ddos", "critical", ip, "*", 0,
|
||||
fmt.Sprintf("大量半开连接: 当前 SYN_SENT %d 条", stats.totalSynSent),
|
||||
"")
|
||||
}
|
||||
|
||||
for dstIP, count := range stats.synSentByDst {
|
||||
if count >= 50 {
|
||||
ss.addAlert(name, "ddos", "critical", ip, dstIP, 0,
|
||||
fmt.Sprintf("SYN 洪水: 单一目标半开连接 %d 条", count),
|
||||
"")
|
||||
} else if count >= 20 {
|
||||
ss.addAlert(name, "ddos", "high", ip, dstIP, 0,
|
||||
fmt.Sprintf("可疑 SYN 洪水: 单一目标半开连接 %d 条", count),
|
||||
"")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ss *SecurityScanner) detectReflectionAbuse(name, ip string, stats *trafficStats) {
|
||||
for port, service := range reflectionPorts {
|
||||
total := stats.udpTotalCounts[port]
|
||||
targets := len(stats.udpDestCounts[port])
|
||||
if total == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if targets >= 30 || total >= 100 {
|
||||
ss.addAlert(name, "reflection", "critical", ip, "*", port,
|
||||
fmt.Sprintf("UDP 反射放大: %s(%d) 当前 UDP 连接 %d 条,覆盖 %d 个目标", service, port, total, targets),
|
||||
"")
|
||||
} else if targets >= 10 || total >= 30 {
|
||||
ss.addAlert(name, "reflection", "high", ip, "*", port,
|
||||
fmt.Sprintf("疑似 UDP 反射放大: %s(%d) 当前 UDP 连接 %d 条,覆盖 %d 个目标", service, port, total, targets),
|
||||
"")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ss *SecurityScanner) detectMining(name, ip string, stats *trafficStats) {
|
||||
for port, service := range miningPorts {
|
||||
total := stats.portTotalCounts[port]
|
||||
if total == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
severity := "high"
|
||||
if total >= 5 {
|
||||
severity = "critical"
|
||||
}
|
||||
ss.addAlert(name, "mining", severity, ip, "*", port,
|
||||
fmt.Sprintf("疑似挖矿连接: %s/%d 当前连接 %d 条", service, port, total),
|
||||
"")
|
||||
}
|
||||
}
|
||||
|
||||
func (ss *SecurityScanner) detectProxyAndTor(name, ip string, stats *trafficStats) {
|
||||
for port, service := range proxyPorts {
|
||||
total := stats.portTotalCounts[port]
|
||||
targets := len(stats.portDestCounts[port])
|
||||
if total == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if port == 1194 || port == 51820 {
|
||||
if targets < 3 && total < 10 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
severity := "high"
|
||||
if targets >= 10 || total >= 30 {
|
||||
severity = "critical"
|
||||
}
|
||||
ss.addAlert(name, "proxy", severity, ip, "*", port,
|
||||
fmt.Sprintf("疑似代理/VPN/Tor 滥用: %s(%d) 当前连接 %d 条,覆盖 %d 个目标", service, port, total, targets),
|
||||
"")
|
||||
}
|
||||
|
||||
total8080 := stats.portTotalCounts[8080]
|
||||
targets8080 := len(stats.portDestCounts[8080])
|
||||
if targets8080 >= 5 || total8080 >= 20 {
|
||||
ss.addAlert(name, "proxy", "high", ip, "*", 8080,
|
||||
fmt.Sprintf("疑似开放代理流量: HTTP 代理常用端口 8080 当前连接 %d 条,覆盖 %d 个目标", total8080, targets8080),
|
||||
"")
|
||||
}
|
||||
}
|
||||
|
||||
func (ss *SecurityScanner) detectMalware(name, ip string, stats *trafficStats) {
|
||||
for port, label := range malwarePorts {
|
||||
total := stats.portTotalCounts[port]
|
||||
if total == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
ss.addAlert(name, "malware", "critical", ip, "*", port,
|
||||
fmt.Sprintf("疑似恶意软件/C2 连接: %s 端口 %d 当前连接 %d 条", label, port, total),
|
||||
"")
|
||||
}
|
||||
}
|
||||
|
||||
func readConntrackLines(ip string) []string {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
cmd := exec.CommandContext(ctx, "conntrack", "-L", "-s", ip)
|
||||
output, err := cmd.Output()
|
||||
if err == nil && len(output) > 0 {
|
||||
return splitNonEmptyLines(string(output))
|
||||
}
|
||||
|
||||
var lines []string
|
||||
for _, path := range []string{"/proc/net/nf_conntrack", "/proc/net/ip_conntrack"} {
|
||||
data, readErr := os.ReadFile(path)
|
||||
if readErr != nil {
|
||||
continue
|
||||
}
|
||||
for _, line := range strings.Split(string(data), "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(line, "src="+ip+" ") {
|
||||
lines = append(lines, line)
|
||||
}
|
||||
}
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
func splitNonEmptyLines(raw string) []string {
|
||||
lines := make([]string, 0)
|
||||
for _, line := range strings.Split(raw, "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
if line != "" {
|
||||
lines = append(lines, line)
|
||||
}
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
func parseConntrackLine(line, containerIP string) (connEntry, bool) {
|
||||
srcIP := extractField(line, "src=")
|
||||
if srcIP != containerIP {
|
||||
return connEntry{}, false
|
||||
}
|
||||
|
||||
dstIP := extractField(line, "dst=")
|
||||
dstPort, _ := strconv.Atoi(extractField(line, "dport="))
|
||||
|
||||
return connEntry{
|
||||
dstIP: dstIP,
|
||||
dstPort: dstPort,
|
||||
proto: extractProtocol(line),
|
||||
state: extractConnState(line),
|
||||
line: line,
|
||||
}, true
|
||||
}
|
||||
|
||||
func extractProtocol(line string) string {
|
||||
for _, field := range strings.Fields(line) {
|
||||
switch field {
|
||||
case "tcp", "udp", "icmp", "icmpv6", "sctp":
|
||||
return field
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func extractConnState(line string) string {
|
||||
for _, field := range strings.Fields(line) {
|
||||
switch field {
|
||||
case "SYN_SENT", "SYN_RECV", "ESTABLISHED", "TIME_WAIT", "CLOSE", "CLOSE_WAIT", "FIN_WAIT", "LAST_ACK", "UNREPLIED":
|
||||
return field
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func countPorts(totalCounts map[int]int, destCounts map[int]map[string]int, ports map[int]string) (int, int) {
|
||||
total := 0
|
||||
targets := make(map[string]struct{})
|
||||
for port := range ports {
|
||||
total += totalCounts[port]
|
||||
for dstIP := range destCounts[port] {
|
||||
targets[dstIP] = struct{}{}
|
||||
}
|
||||
}
|
||||
return total, len(targets)
|
||||
}
|
||||
|
||||
func (ss *SecurityScanner) addAlert(name, alertType, severity, srcIP, dstIP string, port int, detail, logLine string) {
|
||||
ss.mu.Lock()
|
||||
defer ss.mu.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
cutoff := now.Add(-5 * time.Minute)
|
||||
|
||||
for i := range ss.alerts {
|
||||
a := &ss.alerts[i]
|
||||
if a.ContainerName != name || a.Type != alertType || a.TargetIP != dstIP || a.TargetPort != port {
|
||||
continue
|
||||
}
|
||||
t, err := time.Parse("2006-01-02 15:04:05", a.Timestamp)
|
||||
if err != nil || t.Before(cutoff) {
|
||||
continue
|
||||
}
|
||||
|
||||
a.Count++
|
||||
a.Detail = detail
|
||||
a.LogLine = logLine
|
||||
a.Timestamp = now.Format("2006-01-02 15:04:05")
|
||||
if severityRank(severity) > severityRank(a.Severity) {
|
||||
a.Severity = severity
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
ss.nextID++
|
||||
alert := SecurityAlert{
|
||||
ID: fmt.Sprintf("alert-%d", ss.nextID),
|
||||
ContainerName: name,
|
||||
Type: alertType,
|
||||
Severity: severity,
|
||||
SourceIP: srcIP,
|
||||
TargetIP: dstIP,
|
||||
TargetPort: port,
|
||||
Detail: detail,
|
||||
LogLine: logLine,
|
||||
Timestamp: now.Format("2006-01-02 15:04:05"),
|
||||
Count: 1,
|
||||
}
|
||||
|
||||
ss.alerts = append(ss.alerts, alert)
|
||||
config.AddAuditLog("security_"+alertType, name, fmt.Sprintf("[%s] %s", severity, detail), "system")
|
||||
|
||||
if len(ss.alerts) > 200 {
|
||||
ss.alerts = ss.alerts[len(ss.alerts)-200:]
|
||||
}
|
||||
}
|
||||
|
||||
func severityRank(severity string) int {
|
||||
switch severity {
|
||||
case "critical":
|
||||
return 4
|
||||
case "high":
|
||||
return 3
|
||||
case "medium":
|
||||
return 2
|
||||
case "low":
|
||||
return 1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// HandleSecurityAlerts returns all security alerts.
|
||||
func HandleSecurityAlerts(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
jsonResponse(w, http.StatusMethodNotAllowed, APIResponse{Success: false, Message: "Method not allowed"})
|
||||
return
|
||||
}
|
||||
|
||||
ss := ensureScanner()
|
||||
ss.mu.Lock()
|
||||
reversed := make([]SecurityAlert, len(ss.alerts))
|
||||
for i, a := range ss.alerts {
|
||||
reversed[len(ss.alerts)-1-i] = a
|
||||
}
|
||||
ss.mu.Unlock()
|
||||
|
||||
if reversed == nil {
|
||||
reversed = []SecurityAlert{}
|
||||
}
|
||||
|
||||
jsonResponse(w, http.StatusOK, APIResponse{Success: true, Data: reversed})
|
||||
}
|
||||
|
||||
// HandleSecurityCheck triggers immediate security check for a container.
|
||||
func HandleSecurityCheck(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
jsonResponse(w, http.StatusMethodNotAllowed, APIResponse{Success: false, Message: "Method not allowed"})
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
ContainerName string `json:"container_name"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
jsonResponse(w, http.StatusBadRequest, APIResponse{Success: false, Message: "Invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
c := config.FindContainerByName(req.ContainerName)
|
||||
if c == nil || c.IP == "" {
|
||||
jsonResponse(w, http.StatusNotFound, APIResponse{Success: false, Message: "Container not found or not running"})
|
||||
return
|
||||
}
|
||||
|
||||
ensureScanner().checkContainer(c.Name, c.IP)
|
||||
jsonResponse(w, http.StatusOK, APIResponse{Success: true, Message: "Security check completed"})
|
||||
}
|
||||
|
||||
// HandleSecurityLogs returns connection logs for a container.
|
||||
func HandleSecurityLogs(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
jsonResponse(w, http.StatusMethodNotAllowed, APIResponse{Success: false, Message: "Method not allowed"})
|
||||
return
|
||||
}
|
||||
|
||||
containerName := r.URL.Query().Get("container")
|
||||
if containerName == "" {
|
||||
jsonResponse(w, http.StatusBadRequest, APIResponse{Success: false, Message: "Container name required"})
|
||||
return
|
||||
}
|
||||
|
||||
c := config.FindContainerByName(containerName)
|
||||
if c == nil || c.IP == "" {
|
||||
jsonResponse(w, http.StatusOK, APIResponse{Success: true, Data: []map[string]interface{}{}})
|
||||
return
|
||||
}
|
||||
|
||||
jsonResponse(w, http.StatusOK, APIResponse{Success: true, Data: getConnectionLogs(c.IP)})
|
||||
}
|
||||
|
||||
func getConnectionLogs(ip string) []map[string]interface{} {
|
||||
logs := make([]map[string]interface{}, 0)
|
||||
|
||||
for _, line := range readConntrackLines(ip) {
|
||||
srcIP := extractField(line, "src=")
|
||||
dstIP := extractField(line, "dst=")
|
||||
srcPort := extractField(line, "sport=")
|
||||
dstPort := extractField(line, "dport=")
|
||||
|
||||
sPort, _ := strconv.Atoi(srcPort)
|
||||
dPort, _ := strconv.Atoi(dstPort)
|
||||
|
||||
logs = append(logs, map[string]interface{}{
|
||||
"src_ip": srcIP,
|
||||
"dst_ip": dstIP,
|
||||
"src_port": sPort,
|
||||
"dst_port": dPort,
|
||||
"protocol": extractProtocol(line),
|
||||
"state": extractConnState(line),
|
||||
})
|
||||
|
||||
if len(logs) >= 100 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return logs
|
||||
}
|
||||
|
||||
func extractField(line, prefix string) string {
|
||||
idx := strings.Index(line, prefix)
|
||||
if idx == -1 {
|
||||
return ""
|
||||
}
|
||||
start := idx + len(prefix)
|
||||
end := start
|
||||
for end < len(line) && line[end] != ' ' && line[end] != '\t' {
|
||||
end++
|
||||
}
|
||||
return line[start:end]
|
||||
}
|
||||
|
||||
// HandleContainerSecuritySummary returns security status for dashboard.
|
||||
func HandleContainerSecuritySummary(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
jsonResponse(w, http.StatusMethodNotAllowed, APIResponse{Success: false, Message: "Method not allowed"})
|
||||
return
|
||||
}
|
||||
|
||||
ss := ensureScanner()
|
||||
ss.mu.Lock()
|
||||
critical := 0
|
||||
high := 0
|
||||
medium := 0
|
||||
low := 0
|
||||
for _, a := range ss.alerts {
|
||||
switch a.Severity {
|
||||
case "critical":
|
||||
critical++
|
||||
case "high":
|
||||
high++
|
||||
case "medium":
|
||||
medium++
|
||||
case "low":
|
||||
low++
|
||||
}
|
||||
}
|
||||
total := len(ss.alerts)
|
||||
ss.mu.Unlock()
|
||||
|
||||
summary := map[string]interface{}{
|
||||
"total_alerts": total,
|
||||
"critical": critical,
|
||||
"high": high,
|
||||
"medium": medium,
|
||||
"low": low,
|
||||
}
|
||||
|
||||
jsonResponse(w, http.StatusOK, APIResponse{Success: true, Data: summary})
|
||||
}
|
||||
Reference in New Issue
Block a user