mirror of
https://github.com/DeBrosOfficial/orama.git
synced 2026-03-17 13:16:58 +00:00
271 lines
8.7 KiB
Go
271 lines
8.7 KiB
Go
package checks
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/DeBrosOfficial/network/pkg/inspector"
|
|
)
|
|
|
|
func init() {
|
|
inspector.RegisterChecker("wireguard", CheckWireGuard)
|
|
}
|
|
|
|
const wgSub = "wireguard"
|
|
|
|
// CheckWireGuard runs all WireGuard health checks.
|
|
func CheckWireGuard(data *inspector.ClusterData) []inspector.CheckResult {
|
|
var results []inspector.CheckResult
|
|
|
|
for _, nd := range data.Nodes {
|
|
if nd.WireGuard == nil {
|
|
continue
|
|
}
|
|
results = append(results, checkWGPerNode(nd, data)...)
|
|
}
|
|
|
|
results = append(results, checkWGCrossNode(data)...)
|
|
|
|
return results
|
|
}
|
|
|
|
func checkWGPerNode(nd *inspector.NodeData, data *inspector.ClusterData) []inspector.CheckResult {
|
|
var r []inspector.CheckResult
|
|
wg := nd.WireGuard
|
|
node := nd.Node.Name()
|
|
|
|
// 5.1 Interface up
|
|
if wg.InterfaceUp {
|
|
r = append(r, inspector.Pass("wg.interface_up", "WireGuard interface up", wgSub, node,
|
|
fmt.Sprintf("wg0 up, IP=%s", wg.WgIP), inspector.Critical))
|
|
} else {
|
|
r = append(r, inspector.Fail("wg.interface_up", "WireGuard interface up", wgSub, node,
|
|
"wg0 interface is DOWN", inspector.Critical))
|
|
return r
|
|
}
|
|
|
|
// 5.2 Service active
|
|
if wg.ServiceActive {
|
|
r = append(r, inspector.Pass("wg.service_active", "wg-quick@wg0 service active", wgSub, node,
|
|
"service is active", inspector.Critical))
|
|
} else {
|
|
r = append(r, inspector.Warn("wg.service_active", "wg-quick@wg0 service active", wgSub, node,
|
|
"service not active (interface up but service not managed by systemd?)", inspector.High))
|
|
}
|
|
|
|
// 5.5 Correct IP in 10.0.0.0/24
|
|
if wg.WgIP != "" && strings.HasPrefix(wg.WgIP, "10.0.0.") {
|
|
r = append(r, inspector.Pass("wg.correct_ip", "WG IP in expected range", wgSub, node,
|
|
fmt.Sprintf("IP=%s (10.0.0.0/24)", wg.WgIP), inspector.Critical))
|
|
} else if wg.WgIP != "" {
|
|
r = append(r, inspector.Warn("wg.correct_ip", "WG IP in expected range", wgSub, node,
|
|
fmt.Sprintf("IP=%s (not in 10.0.0.0/24)", wg.WgIP), inspector.High))
|
|
}
|
|
|
|
// 5.4 Listen port
|
|
if wg.ListenPort == 51820 {
|
|
r = append(r, inspector.Pass("wg.listen_port", "Listen port is 51820", wgSub, node,
|
|
"port=51820", inspector.Critical))
|
|
} else if wg.ListenPort > 0 {
|
|
r = append(r, inspector.Warn("wg.listen_port", "Listen port is 51820", wgSub, node,
|
|
fmt.Sprintf("port=%d (expected 51820)", wg.ListenPort), inspector.High))
|
|
}
|
|
|
|
// 5.7 Peer count
|
|
expectedNodes := countWGNodes(data)
|
|
expectedPeers := expectedNodes - 1
|
|
if expectedPeers < 0 {
|
|
expectedPeers = 0
|
|
}
|
|
if wg.PeerCount >= expectedPeers {
|
|
r = append(r, inspector.Pass("wg.peer_count", "Peer count matches expected", wgSub, node,
|
|
fmt.Sprintf("peers=%d (expected=%d)", wg.PeerCount, expectedPeers), inspector.High))
|
|
} else if wg.PeerCount > 0 {
|
|
r = append(r, inspector.Warn("wg.peer_count", "Peer count matches expected", wgSub, node,
|
|
fmt.Sprintf("peers=%d (expected=%d)", wg.PeerCount, expectedPeers), inspector.High))
|
|
} else {
|
|
r = append(r, inspector.Fail("wg.peer_count", "Peer count matches expected", wgSub, node,
|
|
fmt.Sprintf("peers=%d (isolated!)", wg.PeerCount), inspector.Critical))
|
|
}
|
|
|
|
// 5.29 MTU
|
|
if wg.MTU == 1420 {
|
|
r = append(r, inspector.Pass("wg.mtu", "MTU is 1420", wgSub, node,
|
|
"MTU=1420", inspector.High))
|
|
} else if wg.MTU > 0 {
|
|
r = append(r, inspector.Warn("wg.mtu", "MTU is 1420", wgSub, node,
|
|
fmt.Sprintf("MTU=%d (expected 1420)", wg.MTU), inspector.High))
|
|
}
|
|
|
|
// 5.35 Config file exists
|
|
if wg.ConfigExists {
|
|
r = append(r, inspector.Pass("wg.config_exists", "Config file exists", wgSub, node,
|
|
"/etc/wireguard/wg0.conf present", inspector.High))
|
|
} else {
|
|
r = append(r, inspector.Warn("wg.config_exists", "Config file exists", wgSub, node,
|
|
"/etc/wireguard/wg0.conf NOT found", inspector.High))
|
|
}
|
|
|
|
// 5.36 Config permissions
|
|
if wg.ConfigPerms == "600" {
|
|
r = append(r, inspector.Pass("wg.config_perms", "Config file permissions 600", wgSub, node,
|
|
"perms=600", inspector.Critical))
|
|
} else if wg.ConfigPerms != "" && wg.ConfigPerms != "000" {
|
|
r = append(r, inspector.Warn("wg.config_perms", "Config file permissions 600", wgSub, node,
|
|
fmt.Sprintf("perms=%s (expected 600)", wg.ConfigPerms), inspector.Critical))
|
|
}
|
|
|
|
// Per-peer checks
|
|
now := time.Now().Unix()
|
|
neverHandshaked := 0
|
|
staleHandshakes := 0
|
|
noTraffic := 0
|
|
|
|
for _, peer := range wg.Peers {
|
|
// 5.20 Each peer has exactly one /32 allowed IP
|
|
if !strings.Contains(peer.AllowedIPs, "/32") {
|
|
r = append(r, inspector.Warn("wg.peer_allowed_ip", "Peer has /32 allowed IP", wgSub, node,
|
|
fmt.Sprintf("peer %s...%s has allowed_ips=%s", peer.PublicKey[:8], peer.PublicKey[len(peer.PublicKey)-4:], peer.AllowedIPs), inspector.High))
|
|
}
|
|
|
|
// 5.23 No peer has 0.0.0.0/0
|
|
if strings.Contains(peer.AllowedIPs, "0.0.0.0/0") {
|
|
r = append(r, inspector.Fail("wg.peer_catch_all", "No catch-all route peer", wgSub, node,
|
|
fmt.Sprintf("peer %s...%s has 0.0.0.0/0 (route hijack!)", peer.PublicKey[:8], peer.PublicKey[len(peer.PublicKey)-4:]), inspector.Critical))
|
|
}
|
|
|
|
// 5.11-5.12 Handshake freshness
|
|
if peer.LatestHandshake == 0 {
|
|
neverHandshaked++
|
|
} else {
|
|
age := now - peer.LatestHandshake
|
|
if age > 300 {
|
|
staleHandshakes++
|
|
}
|
|
}
|
|
|
|
// 5.13 Transfer stats
|
|
if peer.TransferRx == 0 && peer.TransferTx == 0 {
|
|
noTraffic++
|
|
}
|
|
}
|
|
|
|
if len(wg.Peers) > 0 {
|
|
// 5.12 Never handshaked
|
|
if neverHandshaked == 0 {
|
|
r = append(r, inspector.Pass("wg.handshake_all", "All peers have handshaked", wgSub, node,
|
|
fmt.Sprintf("%d/%d peers handshaked", len(wg.Peers), len(wg.Peers)), inspector.Critical))
|
|
} else {
|
|
r = append(r, inspector.Fail("wg.handshake_all", "All peers have handshaked", wgSub, node,
|
|
fmt.Sprintf("%d/%d peers never handshaked", neverHandshaked, len(wg.Peers)), inspector.Critical))
|
|
}
|
|
|
|
// 5.11 Stale handshakes
|
|
if staleHandshakes == 0 {
|
|
r = append(r, inspector.Pass("wg.handshake_fresh", "All handshakes recent (<5m)", wgSub, node,
|
|
"all handshakes within 5 minutes", inspector.High))
|
|
} else {
|
|
r = append(r, inspector.Warn("wg.handshake_fresh", "All handshakes recent (<5m)", wgSub, node,
|
|
fmt.Sprintf("%d/%d peers with stale handshake (>5m)", staleHandshakes, len(wg.Peers)), inspector.High))
|
|
}
|
|
|
|
// 5.13 Transfer
|
|
if noTraffic == 0 {
|
|
r = append(r, inspector.Pass("wg.peer_traffic", "All peers have traffic", wgSub, node,
|
|
fmt.Sprintf("%d/%d peers with traffic", len(wg.Peers), len(wg.Peers)), inspector.High))
|
|
} else {
|
|
r = append(r, inspector.Warn("wg.peer_traffic", "All peers have traffic", wgSub, node,
|
|
fmt.Sprintf("%d/%d peers with zero traffic", noTraffic, len(wg.Peers)), inspector.High))
|
|
}
|
|
}
|
|
|
|
return r
|
|
}
|
|
|
|
func checkWGCrossNode(data *inspector.ClusterData) []inspector.CheckResult {
|
|
var r []inspector.CheckResult
|
|
|
|
type nodeInfo struct {
|
|
name string
|
|
wg *inspector.WireGuardData
|
|
}
|
|
var nodes []nodeInfo
|
|
for _, nd := range data.Nodes {
|
|
if nd.WireGuard != nil && nd.WireGuard.InterfaceUp {
|
|
nodes = append(nodes, nodeInfo{name: nd.Node.Name(), wg: nd.WireGuard})
|
|
}
|
|
}
|
|
|
|
if len(nodes) < 2 {
|
|
return r
|
|
}
|
|
|
|
// 5.8 Peer count consistent
|
|
counts := map[int]int{}
|
|
for _, n := range nodes {
|
|
counts[n.wg.PeerCount]++
|
|
}
|
|
if len(counts) == 1 {
|
|
for c := range counts {
|
|
r = append(r, inspector.Pass("wg.peer_count_consistent", "Peer count consistent across nodes", wgSub, "",
|
|
fmt.Sprintf("all nodes have %d peers", c), inspector.High))
|
|
}
|
|
} else {
|
|
var parts []string
|
|
for c, num := range counts {
|
|
parts = append(parts, fmt.Sprintf("%d nodes have %d peers", num, c))
|
|
}
|
|
r = append(r, inspector.Warn("wg.peer_count_consistent", "Peer count consistent across nodes", wgSub, "",
|
|
strings.Join(parts, "; "), inspector.High))
|
|
}
|
|
|
|
// 5.30 MTU consistent
|
|
mtus := map[int]int{}
|
|
for _, n := range nodes {
|
|
if n.wg.MTU > 0 {
|
|
mtus[n.wg.MTU]++
|
|
}
|
|
}
|
|
if len(mtus) == 1 {
|
|
for m := range mtus {
|
|
r = append(r, inspector.Pass("wg.mtu_consistent", "MTU consistent across nodes", wgSub, "",
|
|
fmt.Sprintf("all nodes MTU=%d", m), inspector.High))
|
|
}
|
|
} else if len(mtus) > 1 {
|
|
r = append(r, inspector.Warn("wg.mtu_consistent", "MTU consistent across nodes", wgSub, "",
|
|
fmt.Sprintf("%d different MTU values", len(mtus)), inspector.High))
|
|
}
|
|
|
|
// 5.50 Public key uniqueness
|
|
allKeys := map[string][]string{}
|
|
for _, n := range nodes {
|
|
for _, peer := range n.wg.Peers {
|
|
allKeys[peer.PublicKey] = append(allKeys[peer.PublicKey], n.name)
|
|
}
|
|
}
|
|
dupeKeys := 0
|
|
for _, names := range allKeys {
|
|
if len(names) > len(nodes)-1 {
|
|
dupeKeys++
|
|
}
|
|
}
|
|
// If all good, the same key should appear at most N-1 times (once per other node)
|
|
if dupeKeys == 0 {
|
|
r = append(r, inspector.Pass("wg.key_uniqueness", "Public keys unique across nodes", wgSub, "",
|
|
fmt.Sprintf("%d unique peer keys", len(allKeys)), inspector.Critical))
|
|
}
|
|
|
|
return r
|
|
}
|
|
|
|
func countWGNodes(data *inspector.ClusterData) int {
|
|
count := 0
|
|
for _, nd := range data.Nodes {
|
|
if nd.WireGuard != nil {
|
|
count++
|
|
}
|
|
}
|
|
return count
|
|
}
|