mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-07-01 08:26:08 +00:00
Compare commits
3 Commits
febf58fd27
...
f34dc633a8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f34dc633a8 | ||
| 1063c91815 | |||
| a5c07c3d50 |
|
|
@ -1,3 +1,55 @@
|
|||
secubox-toolbox (2.6.9-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* Phase 13.B (#522, parent #519) — DNS-guard : resolve blocklisted
|
||||
domains into the enforcement set + DoH/DoT detection.
|
||||
- secubox-blacklist-sync : Source 3 resolves threat_intel
|
||||
ioc_type='domain' to A/AAAA (getent, per-lookup timeout 2s,
|
||||
2000-domain cap) and pushes the IPs into blacklist_v4/v6 — so a
|
||||
device that resolves a known-bad domain via DoH / hardcoded IP
|
||||
is still dropped at the forward hook. Writes a state file
|
||||
/run/secubox/blacklist-sync.json (resolved count + doh_block).
|
||||
- nftables.d/secubox-blacklist.nft : doh_detect_v4/v6 sets + a
|
||||
count-only doh_watch chain (priority -11) that counters device
|
||||
egress to known DoH/DoT provider endpoints (Cloudflare/Google/
|
||||
Quad9/AdGuard/OpenDNS/CleanBrowsing/Control-D/NextDNS) on
|
||||
tcp 443/853. Detection only by default.
|
||||
- secubox-blacklist-sync : populates the DoH sets every cycle ;
|
||||
SECUBOX_DOH_BLOCK=1 ALSO adds them to the enforced blacklist
|
||||
(forces devices back through Vortex DNS). Default off — gated.
|
||||
- api.py /admin/blacklist : adds doh_detect counts, doh_hits
|
||||
counter, resolved_domains, doh_block flag.
|
||||
Detection-first ; DoH blocking opt-in only (intrusive, per #519 Q1).
|
||||
|
||||
-- Gerald KERMA <devel@cybermind.fr> Thu, 11 Jun 2026 09:30:00 +0200
|
||||
|
||||
secubox-toolbox (2.6.8-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* Phase 13.A (#521, parent #519) — unified blacklist enforcement spine.
|
||||
- nftables.d/secubox-blacklist.nft : table inet secubox_blacklist
|
||||
with blacklist_v4 / blacklist_v6 sets (flags interval,timeout)
|
||||
+ a single forward-hook chain (priority -10, policy accept) that
|
||||
counter-drops any routed packet whose saddr OR daddr is
|
||||
blacklisted. One rule set covers every device egress path
|
||||
(captive / R3 WG / br-lxc / LAN) — no per-interface logic.
|
||||
Loaded by nftables.service at boot ; survives reboot.
|
||||
- sbin/secubox-blacklist-sync : unions CrowdSec ban decisions
|
||||
(cscli) + threat-intel C2 IPs (threat_intel ip IOCs from
|
||||
toolbox.db) into the sets, per-element timeout 2h (auto-expire
|
||||
on stale feeds), 50k safety cap, idempotent add (no flush race
|
||||
with CrowdSec's own table).
|
||||
- systemd/secubox-blacklist-sync.{service,timer} : oneshot +
|
||||
every-5-min timer (OnBootSec 90s).
|
||||
- postinst : install drop-in to /etc/nftables.d/, reload nft,
|
||||
enable+start the timer, kick a first sync.
|
||||
- api.py : GET /admin/blacklist — element counts + drop counters
|
||||
from nft -j (read-only status).
|
||||
Doctrine preserved : policy accept only ADDS drops (a drop in any
|
||||
chain wins) — never a WAF/default-drop bypass. Detection sources are
|
||||
high-confidence only (CrowdSec bans + threat-intel C2) ; WAF-ban +
|
||||
Vortex-resolved-IP feeds land in 13.B/13.D.
|
||||
|
||||
-- Gerald KERMA <devel@cybermind.fr> Thu, 11 Jun 2026 09:00:00 +0200
|
||||
|
||||
secubox-toolbox (2.6.7-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* Phase 12.C (#518, parent #514) — operator-grade / state-adjacent
|
||||
|
|
|
|||
|
|
@ -151,6 +151,26 @@ fi
|
|||
fi
|
||||
fi
|
||||
|
||||
# Phase 13.A (#521) : unified blacklist enforcement spine.
|
||||
# nft drop-in (loaded by nftables.service at boot) + sync timer that
|
||||
# unions CrowdSec decisions + threat-intel C2 IPs into the sets.
|
||||
if [ -f /usr/share/secubox/toolbox/nftables.d/secubox-blacklist.nft ]; then
|
||||
install -d -m 0755 /etc/nftables.d
|
||||
install -m 0644 /usr/share/secubox/toolbox/nftables.d/secubox-blacklist.nft \
|
||||
/etc/nftables.d/secubox-blacklist.nft
|
||||
if systemctl is-active --quiet nftables.service 2>/dev/null; then
|
||||
systemctl reload nftables.service 2>/dev/null \
|
||||
|| /usr/sbin/nft -f /etc/nftables.d/secubox-blacklist.nft 2>/dev/null \
|
||||
|| true
|
||||
fi
|
||||
if [ -d /run/systemd/system ]; then
|
||||
systemctl enable secubox-blacklist-sync.timer 2>/dev/null || true
|
||||
systemctl start secubox-blacklist-sync.timer 2>/dev/null || true
|
||||
# Kick an immediate first sync (best-effort).
|
||||
systemctl start secubox-blacklist-sync.service 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
|
||||
# Phase 7 (#498) : install unbound listeners.
|
||||
# 99-secubox-wg.conf — WG peers (10.99.x). Without it WG peers' DNS
|
||||
# queries get ICMP port unreachable and the iPhone stops resolving.
|
||||
|
|
|
|||
|
|
@ -54,8 +54,14 @@ override_dh_installsystemd:
|
|||
override_dh_auto_test:
|
||||
@true
|
||||
|
||||
override_dh_strip:
|
||||
@true
|
||||
# NOTE: dh_strip is NOT invoked in the binary-indep sequence (this is an
|
||||
# Architecture: all package with no binaries to strip), so an
|
||||
# override_dh_strip target would silently never run. Everything that
|
||||
# used to live there is installed from execute_after_dh_auto_install
|
||||
# below — which DOES run — so nft drop-ins / unbound / nginx / perf
|
||||
# drop-ins / blacklist spine actually ship in the .deb (#521 caught the
|
||||
# stale-on-disk drift this caused).
|
||||
execute_after_dh_auto_install:
|
||||
# Phase 6.Q (#496) : DB tuning helper + uvicorn perf drop-in
|
||||
install -m 755 sbin/secubox-toolbox-db-tune \
|
||||
debian/secubox-toolbox/usr/sbin/
|
||||
|
|
@ -73,6 +79,15 @@ override_dh_strip:
|
|||
# by symlinking /etc/nftables.d/secubox-toolbox-wg.nft → this file.
|
||||
install -m 0644 nftables.d/secubox-toolbox-wg-fanout.nft \
|
||||
debian/secubox-toolbox/usr/share/secubox/toolbox/nftables.d/
|
||||
# Phase 13.A (#521) : unified blacklist enforcement spine.
|
||||
install -m 0644 nftables.d/secubox-blacklist.nft \
|
||||
debian/secubox-toolbox/usr/share/secubox/toolbox/nftables.d/
|
||||
install -m 0755 sbin/secubox-blacklist-sync \
|
||||
debian/secubox-toolbox/usr/sbin/
|
||||
install -m 0644 systemd/secubox-blacklist-sync.service \
|
||||
debian/secubox-toolbox/lib/systemd/system/
|
||||
install -m 0644 systemd/secubox-blacklist-sync.timer \
|
||||
debian/secubox-toolbox/lib/systemd/system/
|
||||
install -m 0755 sbin/secubox-toolbox-wg-restore \
|
||||
debian/secubox-toolbox/usr/sbin/
|
||||
install -m 0644 systemd/secubox-toolbox-wg-restore.service \
|
||||
|
|
|
|||
70
packages/secubox-toolbox/nftables.d/secubox-blacklist.nft
Normal file
70
packages/secubox-toolbox/nftables.d/secubox-blacklist.nft
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
# SPDX-License-Identifier: LicenseRef-CMSD-1.0
|
||||
# Copyright (c) 2026 CyberMind — Gérald Kerma <devel@cybermind.fr>
|
||||
#
|
||||
# Phase 13.A (#521) — unified blacklist enforcement spine.
|
||||
#
|
||||
# One authoritative pair of sets (v4/v6) fed by secubox-blacklist-sync
|
||||
# from ALL ban sources (CrowdSec decisions + threat-intel C2 IPs ;
|
||||
# WAF-ban + Vortex-resolved feeds land in 13.B/13.D). A single
|
||||
# forward-hook chain drops any ROUTED packet whose source OR destination
|
||||
# is blacklisted — so it covers every device egress path at once
|
||||
# (captive 10.99.0.0/24, R3 WG wg-toolbox 10.99.1.0/24, br-lxc, LAN)
|
||||
# with no per-interface logic.
|
||||
#
|
||||
# Doctrine : policy accept here only ADDS drops ; it never bypasses the
|
||||
# main inet filter default-drop (a drop in any chain wins regardless of
|
||||
# another chain's accept policy). Loaded by nftables.service at boot
|
||||
# via /etc/nftables.d/*.nft so it survives reboot (#498/#501 pattern).
|
||||
#
|
||||
# The sets are `interval` (CIDR support) + `timeout` (elements auto-expire
|
||||
# if the sync stops re-adding them — fail-open on stale data, never a
|
||||
# permanent black hole from a one-off bad feed).
|
||||
#
|
||||
# Idempotent load : the create-or-replace idiom below makes `nft -f` on
|
||||
# this file safe to run repeatedly (postinst reload, manual reload)
|
||||
# WITHOUT appending duplicate rules to the chains. The sets are wiped on
|
||||
# reload but secubox-blacklist-sync repopulates them immediately (postinst
|
||||
# kicks a sync ; the timer keeps them fresh every 5 min) — a brief empty
|
||||
# window is the fail-open direction anyway.
|
||||
table inet secubox_blacklist { }
|
||||
delete table inet secubox_blacklist
|
||||
|
||||
table inet secubox_blacklist {
|
||||
set blacklist_v4 {
|
||||
type ipv4_addr
|
||||
flags interval, timeout
|
||||
}
|
||||
set blacklist_v6 {
|
||||
type ipv6_addr
|
||||
flags interval, timeout
|
||||
}
|
||||
|
||||
chain enforce {
|
||||
type filter hook forward priority -10; policy accept;
|
||||
ip daddr @blacklist_v4 counter drop
|
||||
ip saddr @blacklist_v4 counter drop
|
||||
ip6 daddr @blacklist_v6 counter drop
|
||||
ip6 saddr @blacklist_v6 counter drop
|
||||
}
|
||||
|
||||
# Phase 13.B (#522) — DoH/DoT detection. COUNT-ONLY by default : a
|
||||
# device reaching a known DoH/DoT provider endpoint is counted (for
|
||||
# passive stats + the "this device bypasses Vortex DNS" signal) but
|
||||
# NOT blocked. Blocking is opt-in : secubox-blacklist-sync with
|
||||
# SECUBOX_DOH_BLOCK=1 ALSO adds these IPs to blacklist_v4/v6 above,
|
||||
# where the enforce chain drops them — forcing the device back through
|
||||
# Vortex DNS. Default off (intrusive ; gated per #519 open Q1).
|
||||
set doh_detect_v4 {
|
||||
type ipv4_addr
|
||||
flags interval, timeout
|
||||
}
|
||||
set doh_detect_v6 {
|
||||
type ipv6_addr
|
||||
flags interval, timeout
|
||||
}
|
||||
chain doh_watch {
|
||||
type filter hook forward priority -11; policy accept;
|
||||
ip daddr @doh_detect_v4 tcp dport { 443, 853 } counter
|
||||
ip6 daddr @doh_detect_v6 tcp dport { 443, 853 } counter
|
||||
}
|
||||
}
|
||||
147
packages/secubox-toolbox/sbin/secubox-blacklist-sync
Normal file
147
packages/secubox-toolbox/sbin/secubox-blacklist-sync
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
#!/usr/bin/env bash
|
||||
# SPDX-License-Identifier: LicenseRef-CMSD-1.0
|
||||
# Copyright (c) 2026 CyberMind — Gérald Kerma <devel@cybermind.fr>
|
||||
#
|
||||
# SecuBox-Deb :: secubox-blacklist-sync
|
||||
#
|
||||
# Phase 13.A (#521) — union all ban sources into the nft enforcement
|
||||
# sets (inet secubox_blacklist blacklist_v4 / blacklist_v6). Idempotent,
|
||||
# safe to run on a timer. Each element gets a timeout so stale entries
|
||||
# auto-expire if a source stops listing them.
|
||||
set -euo pipefail
|
||||
readonly MODULE="secubox-blacklist-sync"
|
||||
readonly VERSION="13.A"
|
||||
|
||||
NFT=/usr/sbin/nft
|
||||
TABLE="inet secubox_blacklist"
|
||||
TOOLBOX_DB=/var/lib/secubox/toolbox/toolbox.db
|
||||
ELEM_TIMEOUT="${SECUBOX_BL_TIMEOUT:-2h}" # auto-expiry per element
|
||||
# Safety cap : never load more than this many IPs (a runaway feed
|
||||
# shouldn't be able to black-hole the box).
|
||||
MAX_ELEMS="${SECUBOX_BL_MAX:-50000}"
|
||||
|
||||
log() { logger -t "$MODULE" -- "$*" 2>/dev/null || echo "[$MODULE] $*" >&2; }
|
||||
|
||||
# The nft table must already exist (loaded by nftables.service from the
|
||||
# drop-in). If it doesn't, load the drop-in once.
|
||||
if ! $NFT list table $TABLE >/dev/null 2>&1; then
|
||||
if [ -r /etc/nftables.d/secubox-blacklist.nft ]; then
|
||||
$NFT -f /etc/nftables.d/secubox-blacklist.nft || {
|
||||
log "ERROR: table missing and drop-in load failed"; exit 1; }
|
||||
else
|
||||
log "ERROR: table $TABLE missing and no drop-in to load"; exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── Collect IPs from all sources into temp files (v4 / v6) ──
|
||||
TMP4=$(mktemp); TMP6=$(mktemp)
|
||||
trap 'rm -f "$TMP4" "$TMP6"' EXIT
|
||||
|
||||
# Source 1 : threat-intel C2 IPs (feodo / threatfox / sslbl) from the
|
||||
# toolbox SQLite. ioc_type='ip'.
|
||||
if [ -r "$TOOLBOX_DB" ] && command -v sqlite3 >/dev/null 2>&1; then
|
||||
sqlite3 "$TOOLBOX_DB" \
|
||||
"SELECT DISTINCT ioc FROM threat_intel WHERE ioc_type='ip';" \
|
||||
2>/dev/null >> "$TMP4.raw" || true
|
||||
fi
|
||||
|
||||
# Source 2 : CrowdSec decisions (ban scope=Ip). JSON via cscli.
|
||||
if command -v cscli >/dev/null 2>&1; then
|
||||
cscli decisions list -o json 2>/dev/null \
|
||||
| { command -v jq >/dev/null 2>&1 \
|
||||
&& jq -r '.[] | select(.decisions != null) | .decisions[] | select(.type=="ban") | .value' 2>/dev/null \
|
||||
|| sed -n 's/.*"value":"\([0-9a-fA-F:.]*\)".*/\1/p'; } \
|
||||
>> "$TMP4.raw" || true
|
||||
fi
|
||||
|
||||
# Source 3 (Phase 13.B #522) : DNS-guard — resolve blocklisted DOMAINS
|
||||
# (threat_intel ioc_type='domain') to their A/AAAA records and add those
|
||||
# IPs. Closes the DoH / hardcoded-IP bypass : even if a device resolves a
|
||||
# known-bad domain out-of-band, the resulting IP is dropped at forward.
|
||||
# Bounded : cap on domains/cycle + per-lookup timeout so the sync never
|
||||
# hangs on a dead resolver.
|
||||
DOMAIN_CAP="${SECUBOX_BL_DOMAIN_CAP:-2000}"
|
||||
RESOLVE_TIMEOUT="${SECUBOX_BL_RESOLVE_TIMEOUT:-2}"
|
||||
resolved_domains=0
|
||||
if [ -r "$TOOLBOX_DB" ] && command -v sqlite3 >/dev/null 2>&1; then
|
||||
while IFS= read -r dom; do
|
||||
[ -n "$dom" ] || continue
|
||||
# getent ahosts returns both A + AAAA ; timeout guards a dead lookup.
|
||||
ips=$(timeout "$RESOLVE_TIMEOUT" getent ahosts "$dom" 2>/dev/null \
|
||||
| awk '{print $1}' | sort -u)
|
||||
if [ -n "$ips" ]; then
|
||||
printf '%s\n' "$ips" >> "$TMP4.raw"
|
||||
resolved_domains=$((resolved_domains + 1))
|
||||
fi
|
||||
done < <(sqlite3 "$TOOLBOX_DB" \
|
||||
"SELECT DISTINCT ioc FROM threat_intel WHERE ioc_type='domain' LIMIT $DOMAIN_CAP;" \
|
||||
2>/dev/null)
|
||||
fi
|
||||
|
||||
# Split v4 / v6, validate, dedup, cap.
|
||||
if [ -f "$TMP4.raw" ]; then
|
||||
# IPv6 = contains a colon ; IPv4 = dotted quad (optionally /CIDR).
|
||||
grep -E ':' "$TMP4.raw" 2>/dev/null | grep -vE '^\s*$' | sort -u > "$TMP6" || true
|
||||
grep -vE ':' "$TMP4.raw" 2>/dev/null \
|
||||
| grep -E '^([0-9]{1,3}\.){3}[0-9]{1,3}(/[0-9]{1,2})?$' \
|
||||
| sort -u > "$TMP4" || true
|
||||
rm -f "$TMP4.raw"
|
||||
fi
|
||||
|
||||
n4=$(wc -l < "$TMP4" 2>/dev/null || echo 0)
|
||||
n6=$(wc -l < "$TMP6" 2>/dev/null || echo 0)
|
||||
|
||||
if [ "$n4" -gt "$MAX_ELEMS" ]; then
|
||||
log "WARN: $n4 v4 IPs exceeds cap $MAX_ELEMS — truncating"
|
||||
head -n "$MAX_ELEMS" "$TMP4" > "$TMP4.cap" && mv "$TMP4.cap" "$TMP4"
|
||||
n4=$MAX_ELEMS
|
||||
fi
|
||||
|
||||
# ── Push into the nft sets (batched add-element with per-elem timeout) ──
|
||||
# We don't flush : timeouts handle expiry, and re-adding refreshes the
|
||||
# timeout on still-active entries. Batching keeps it to one nft call.
|
||||
add_batch() {
|
||||
local set="$1" file="$2" fam="$3"
|
||||
[ -s "$file" ] || return 0
|
||||
local elems
|
||||
elems=$(awk -v t="$ELEM_TIMEOUT" 'NF{printf "%s timeout %s, ", $1, t}' "$file")
|
||||
elems="${elems%, }"
|
||||
[ -n "$elems" ] || return 0
|
||||
# add (not flush+add) so concurrent CrowdSec table updates don't race.
|
||||
$NFT add element $TABLE "$set" "{ $elems }" 2>/dev/null \
|
||||
|| log "WARN: partial add to $set ($fam)"
|
||||
}
|
||||
|
||||
add_batch blacklist_v4 "$TMP4" v4
|
||||
add_batch blacklist_v6 "$TMP6" v6
|
||||
|
||||
# ── Phase 13.B (#522) : DoH/DoT detection-list population ──
|
||||
# Known DoH/DoT provider endpoints. Count-only by default (the doh_watch
|
||||
# chain just counters) ; SECUBOX_DOH_BLOCK=1 also adds them to the
|
||||
# enforced blacklist so devices are forced back through Vortex DNS.
|
||||
DOH_BLOCK="${SECUBOX_DOH_BLOCK:-0}"
|
||||
DOH_V4="1.1.1.1 1.0.0.1 8.8.8.8 8.8.4.4 9.9.9.9 149.112.112.112 \
|
||||
94.140.14.14 94.140.15.15 208.67.222.222 208.67.220.220 \
|
||||
185.228.168.9 76.76.2.0 76.76.10.0 45.90.28.0 45.90.30.0"
|
||||
DOH_V6="2606:4700:4700::1111 2606:4700:4700::1001 2001:4860:4860::8888 \
|
||||
2001:4860:4860::8844 2620:fe::fe 2620:fe::9"
|
||||
doh4_elems=$(printf '%s ' $DOH_V4 | awk -v t="$ELEM_TIMEOUT" '{for(i=1;i<=NF;i++)printf "%s timeout %s, ",$i,t}')
|
||||
doh6_elems=$(printf '%s ' $DOH_V6 | awk -v t="$ELEM_TIMEOUT" '{for(i=1;i<=NF;i++)printf "%s timeout %s, ",$i,t}')
|
||||
[ -n "$doh4_elems" ] && $NFT add element $TABLE doh_detect_v4 "{ ${doh4_elems%, } }" 2>/dev/null || true
|
||||
[ -n "$doh6_elems" ] && $NFT add element $TABLE doh_detect_v6 "{ ${doh6_elems%, } }" 2>/dev/null || true
|
||||
if [ "$DOH_BLOCK" = "1" ]; then
|
||||
# Opt-in : also enforce-drop DoH so devices fall back to Vortex.
|
||||
[ -n "$doh4_elems" ] && $NFT add element $TABLE blacklist_v4 "{ ${doh4_elems%, } }" 2>/dev/null || true
|
||||
[ -n "$doh6_elems" ] && $NFT add element $TABLE blacklist_v6 "{ ${doh6_elems%, } }" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# ── Report + state file (consumed by /admin/blacklist) ──
|
||||
live4=$($NFT list set $TABLE blacklist_v4 2>/dev/null | grep -c 'timeout' || echo 0)
|
||||
live6=$($NFT list set $TABLE blacklist_v6 2>/dev/null | grep -c 'timeout' || echo 0)
|
||||
STATE=/run/secubox/blacklist-sync.json
|
||||
mkdir -p /run/secubox 2>/dev/null || true
|
||||
printf '{"ts":%s,"v4_added":%s,"v6_added":%s,"resolved_domains":%s,"doh_block":%s}\n' \
|
||||
"$(date +%s)" "${n4:-0}" "${n6:-0}" "${resolved_domains:-0}" "${DOH_BLOCK}" \
|
||||
> "$STATE" 2>/dev/null || true
|
||||
log "synced: +${n4} v4 / +${n6} v6 (live ~${live4}/${live6}, resolved ${resolved_domains:-0} domains, doh_block=${DOH_BLOCK}, timeout ${ELEM_TIMEOUT})"
|
||||
exit 0
|
||||
|
|
@ -2085,6 +2085,74 @@ async def admin_social_aggregate(hours: int = 24) -> dict:
|
|||
return _s.aggregate(hours=hours)
|
||||
|
||||
|
||||
@router.get("/admin/blacklist")
|
||||
async def admin_blacklist() -> dict:
|
||||
"""Phase 13.A (#521) + 13.B (#522) — enforcement-spine status :
|
||||
element counts + drop counters of the unified nft blacklist sets,
|
||||
plus the DoH/DoT detection counters and the last-sync state.
|
||||
Read-only ; parses `nft -j list table inet secubox_blacklist`.
|
||||
"""
|
||||
import json as _json
|
||||
import subprocess as _sp
|
||||
from pathlib import Path as _P
|
||||
out: dict = {
|
||||
"active": False,
|
||||
"v4_count": 0,
|
||||
"v6_count": 0,
|
||||
"drops": 0,
|
||||
"doh_detect_v4": 0,
|
||||
"doh_detect_v6": 0,
|
||||
"doh_hits": 0,
|
||||
"resolved_domains": 0,
|
||||
"doh_block": False,
|
||||
"sources": ["crowdsec", "threat-intel", "dns-guard"],
|
||||
}
|
||||
try:
|
||||
r = _sp.run(
|
||||
["/usr/sbin/nft", "-j", "list", "table", "inet", "secubox_blacklist"],
|
||||
capture_output=True, text=True, timeout=5,
|
||||
)
|
||||
if r.returncode == 0:
|
||||
data = _json.loads(r.stdout or "{}")
|
||||
for item in data.get("nftables", []):
|
||||
if "set" in item:
|
||||
s = item["set"]
|
||||
n = len(s.get("elem", []) or [])
|
||||
name = s.get("name")
|
||||
if name == "blacklist_v4":
|
||||
out["v4_count"] = n
|
||||
elif name == "blacklist_v6":
|
||||
out["v6_count"] = n
|
||||
elif name == "doh_detect_v4":
|
||||
out["doh_detect_v4"] = n
|
||||
elif name == "doh_detect_v6":
|
||||
out["doh_detect_v6"] = n
|
||||
if "rule" in item:
|
||||
chain = item["rule"].get("chain", "")
|
||||
for ex in item["rule"].get("expr", []):
|
||||
c = ex.get("counter")
|
||||
if not c:
|
||||
continue
|
||||
pk = int(c.get("packets", 0) or 0)
|
||||
if chain == "doh_watch":
|
||||
out["doh_hits"] += pk
|
||||
else:
|
||||
out["drops"] += pk
|
||||
out["active"] = True
|
||||
except Exception as e: # noqa: BLE001
|
||||
log.warning("admin_blacklist nft parse failed: %s", e)
|
||||
# Last-sync state file (resolved-domain count + doh_block flag).
|
||||
try:
|
||||
st = _P("/run/secubox/blacklist-sync.json")
|
||||
if st.exists():
|
||||
j = _json.loads(st.read_text())
|
||||
out["resolved_domains"] = int(j.get("resolved_domains", 0) or 0)
|
||||
out["doh_block"] = str(j.get("doh_block", "0")) == "1"
|
||||
except Exception:
|
||||
pass
|
||||
return out
|
||||
|
||||
|
||||
@router.get("/social/report/{token}.pdf")
|
||||
async def social_report_pdf(token: str) -> Response:
|
||||
"""Phase 11.C (#508) — bilingual FR/EN evidence PDF for a peer.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,20 @@
|
|||
# SPDX-License-Identifier: LicenseRef-CMSD-1.0
|
||||
# Phase 13.A (#521) — union ban sources into the nft blacklist sets.
|
||||
[Unit]
|
||||
Description=SecuBox blacklist enforcement sync (CrowdSec + threat-intel -> nft sets)
|
||||
Documentation=https://github.com/CyberMind-FR/secubox-deb/issues/521
|
||||
After=nftables.service secubox-toolbox.service
|
||||
Wants=nftables.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/sbin/secubox-blacklist-sync
|
||||
# Needs CAP_NET_ADMIN for nft set writes ; root is simplest + the script
|
||||
# is read-only against the toolbox DB + cscli.
|
||||
User=root
|
||||
Nice=10
|
||||
IOSchedulingClass=idle
|
||||
TimeoutStartSec=120
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
# SPDX-License-Identifier: LicenseRef-CMSD-1.0
|
||||
# Phase 13.A (#521) — periodic blacklist sync.
|
||||
[Unit]
|
||||
Description=SecuBox blacklist sync timer (every 5 min + boot)
|
||||
Documentation=https://github.com/CyberMind-FR/secubox-deb/issues/521
|
||||
|
||||
[Timer]
|
||||
# Run shortly after boot, then every 5 minutes. The element timeout
|
||||
# (2h) is well above the period so active entries never lapse.
|
||||
OnBootSec=90s
|
||||
OnUnitActiveSec=5min
|
||||
AccuracySec=30s
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
Loading…
Reference in New Issue
Block a user