fix(security-audit): HTTPS/Port-Checks erkennen nftables Set-Syntax nicht
strings.Contains(nftStr, "dport 443") fand den Substring nicht, wenn
nftables mehrere Ports als Set ausgibt ("tcp dport { 80, 443 } accept" statt
"tcp dport 443 accept") — das ist bei archivmail-Installationen der
Normalfall (80+443 stehen zusammen in einer Regel). Dashboard zeigte
dadurch fälschlich "Kein HTTPS — Verbindungen unverschlüsselt", obwohl
Port 443 korrekt offen war (bestätigt auf 131 und 132).
Neue Helper-Funktion nftHasPort() per Regex erkennt beide Formen
(Einzelport und Set). Betrifft die Checks für HTTPS/443, Port 3000 und
Port 8080.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
c274df5023
commit
04e5b0f74a
@@ -6,12 +6,39 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ── Security Audit ──────────────────────────────────────────────────────────
|
// ── Security Audit ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
// nftDportRe matches "dport 443" as well as the set form nft uses for
|
||||||
|
// multiple ports on one rule, e.g. "dport { 80, 443 }".
|
||||||
|
var nftDportRe = regexp.MustCompile(`dport\s+(\{[^}]*\}|\d+)`)
|
||||||
|
|
||||||
|
// nftHasPort reports whether nft's "list ruleset" output opens the given
|
||||||
|
// port, either as a standalone "dport <port>" rule or as a member of a
|
||||||
|
// "dport { ... }" set. A plain strings.Contains(nftStr, "dport 443") misses
|
||||||
|
// the set form because "{ 80, " sits between "dport" and "443", producing a
|
||||||
|
// false "no HTTPS" warning even when port 443 is correctly whitelisted.
|
||||||
|
func nftHasPort(nftStr, port string) bool {
|
||||||
|
for _, m := range nftDportRe.FindAllStringSubmatch(nftStr, -1) {
|
||||||
|
group := m[1]
|
||||||
|
if group == port {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(group, "{") {
|
||||||
|
for _, p := range strings.Split(strings.Trim(group, "{} "), ",") {
|
||||||
|
if strings.TrimSpace(p) == port {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
type securityCheck struct {
|
type securityCheck struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Status string `json:"status"` // "ok" | "warning" | "error"
|
Status string `json:"status"` // "ok" | "warning" | "error"
|
||||||
@@ -49,7 +76,7 @@ func (s *Server) handleSecurityAudit(w http.ResponseWriter, r *http.Request) {
|
|||||||
// 2. Port 3000 (Next.js) extern erreichbar?
|
// 2. Port 3000 (Next.js) extern erreichbar?
|
||||||
if !firewallActive {
|
if !firewallActive {
|
||||||
checks = append(checks, securityCheck{Name: "Port 3000 (Next.js)", Status: "error", Message: "Keine Firewall aktiv — Port möglicherweise extern erreichbar"})
|
checks = append(checks, securityCheck{Name: "Port 3000 (Next.js)", Status: "error", Message: "Keine Firewall aktiv — Port möglicherweise extern erreichbar"})
|
||||||
} else if strings.Contains(nftStr, "dport 3000") {
|
} else if nftHasPort(nftStr, "3000") {
|
||||||
checks = append(checks, securityCheck{Name: "Port 3000 (Next.js)", Status: "warning", Message: "Port 3000 explizit in Firewall-Regeln — prüfen ob gewollt"})
|
checks = append(checks, securityCheck{Name: "Port 3000 (Next.js)", Status: "warning", Message: "Port 3000 explizit in Firewall-Regeln — prüfen ob gewollt"})
|
||||||
} else {
|
} else {
|
||||||
checks = append(checks, securityCheck{Name: "Port 3000 (Next.js)", Status: "ok", Message: "Blockiert (nicht in Whitelist)"})
|
checks = append(checks, securityCheck{Name: "Port 3000 (Next.js)", Status: "ok", Message: "Blockiert (nicht in Whitelist)"})
|
||||||
@@ -58,14 +85,14 @@ func (s *Server) handleSecurityAudit(w http.ResponseWriter, r *http.Request) {
|
|||||||
// 3. Port 8080 (Go Backend) extern erreichbar?
|
// 3. Port 8080 (Go Backend) extern erreichbar?
|
||||||
if !firewallActive {
|
if !firewallActive {
|
||||||
checks = append(checks, securityCheck{Name: "Port 8080 (Go Backend)", Status: "error", Message: "Keine Firewall aktiv — Port möglicherweise extern erreichbar"})
|
checks = append(checks, securityCheck{Name: "Port 8080 (Go Backend)", Status: "error", Message: "Keine Firewall aktiv — Port möglicherweise extern erreichbar"})
|
||||||
} else if strings.Contains(nftStr, "dport 8080") {
|
} else if nftHasPort(nftStr, "8080") {
|
||||||
checks = append(checks, securityCheck{Name: "Port 8080 (Go Backend)", Status: "warning", Message: "Port 8080 explizit in Firewall-Regeln — prüfen ob gewollt"})
|
checks = append(checks, securityCheck{Name: "Port 8080 (Go Backend)", Status: "warning", Message: "Port 8080 explizit in Firewall-Regeln — prüfen ob gewollt"})
|
||||||
} else {
|
} else {
|
||||||
checks = append(checks, securityCheck{Name: "Port 8080 (Go Backend)", Status: "ok", Message: "Blockiert (nicht in Whitelist)"})
|
checks = append(checks, securityCheck{Name: "Port 8080 (Go Backend)", Status: "ok", Message: "Blockiert (nicht in Whitelist)"})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. HTTPS aktiv?
|
// 4. HTTPS aktiv?
|
||||||
if firewallActive && strings.Contains(nftStr, "dport 443") {
|
if firewallActive && nftHasPort(nftStr, "443") {
|
||||||
checks = append(checks, securityCheck{Name: "HTTPS (TLS)", Status: "ok", Message: "Port 443 in Firewall freigegeben"})
|
checks = append(checks, securityCheck{Name: "HTTPS (TLS)", Status: "ok", Message: "Port 443 in Firewall freigegeben"})
|
||||||
} else {
|
} else {
|
||||||
checks = append(checks, securityCheck{Name: "HTTPS (TLS)", Status: "warning", Message: "Kein HTTPS — Verbindungen unverschlüsselt (certbot empfohlen)"})
|
checks = append(checks, securityCheck{Name: "HTTPS (TLS)", Status: "warning", Message: "Kein HTTPS — Verbindungen unverschlüsselt (certbot empfohlen)"})
|
||||||
|
|||||||
Reference in New Issue
Block a user