fix(client-guardian): Safe defaults + emergency clear + safety limits

BREAKING: Default policy changed from quarantine to open
- Disabled by default (was enabled)
- Default policy: open (was quarantine - blocked new devices!)
- Auto-zoning: disabled by default
- Auto-parking zone: lan_private (was guest)
- Night block schedule: disabled by default
- Threat auto-ban: disabled by default

Safety mechanisms added:
- MAX_BLOCKED_DEVICES limit (10) prevents mass blocking
- check_safety_limit() function validates before blocking
- clear_all_cg_rules() emergency function via RPCD
- safety_status RPCD method to check current state

UI improvements:
- Added warnings for restrictive policies
- Reordered options (safe options first)
- Clearer descriptions of consequences

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
CyberMind-FR 2026-01-28 10:59:07 +01:00
parent 477c2fa162
commit 56afd02d40
4 changed files with 89 additions and 28 deletions

View File

@ -27,17 +27,17 @@ return view.extend({
s = m.section(form.NamedSection, 'config', 'client-guardian', _('Auto-Zoning Settings'));
o = s.option(form.Flag, 'auto_zoning_enabled', _('Enable Auto-Zoning'),
_('Automatically assign clients to zones using matching rules'));
o.default = '1';
_('Automatically assign clients to zones using matching rules. WARNING: May restrict network access for new devices!'));
o.default = '0';
o.rmempty = false;
o = s.option(form.ListValue, 'auto_parking_zone', _('Auto-Parking Zone'),
_('Default zone for clients that don\'t match any rule'));
o.value('guest', _('Guest'));
o.value('quarantine', _('Quarantine'));
o.value('iot', _('IoT'));
o.value('lan_private', _('LAN Private'));
o.default = 'guest';
o.value('lan_private', _('LAN Private (Full Access)'));
o.value('guest', _('Guest (Internet Only)'));
o.value('iot', _('IoT (Isolated)'));
o.value('quarantine', _('Quarantine (No Access - Dangerous!)'));
o.default = 'lan_private';
o.depends('auto_zoning_enabled', '1');
o = s.option(form.Flag, 'auto_parking_approve', _('Auto-Approve Parked Clients'),

View File

@ -29,17 +29,17 @@ return view.extend({
s = m.section(form.NamedSection, 'config', 'client-guardian', _('General Settings'));
o = s.option(form.Flag, 'enabled', _('Enable Client Guardian'));
o.default = '1';
o.default = '0';
o.rmempty = false;
o.description = _('Enable or disable the Client Guardian access control system');
o.description = _('Enable or disable the Client Guardian access control system. WARNING: Enabling with restrictive policies may block network access!');
o = s.option(form.ListValue, 'default_policy', _('Default Policy'));
o.value('open', _('Open - Allow all clients'));
o.value('quarantine', _('Quarantine - Require approval'));
o.value('whitelist', _('Whitelist Only - Allow only approved clients'));
o.default = 'quarantine';
o.value('open', _('Open - Allow all clients (Recommended)'));
o.value('quarantine', _('Quarantine - Require approval (Restrictive)'));
o.value('whitelist', _('Whitelist Only - Block unknown clients (Very Restrictive)'));
o.default = 'open';
o.rmempty = false;
o.description = _('Default behavior for new/unknown clients');
o.description = _('Default behavior for new/unknown clients. WARNING: Quarantine and Whitelist modes will block new devices from accessing the network!');
o = s.option(form.Flag, 'auto_approve', _('Auto-Approve Known Devices'));
o.default = '0';

View File

@ -1,9 +1,9 @@
config client-guardian 'config'
option enabled '1'
option default_policy 'quarantine'
option enabled '0'
option default_policy 'open'
option quarantine_zone 'quarantine'
option scan_interval '30'
option auto_approve '0'
option auto_approve '1'
option log_level 'info'
# Dashboard Reactiveness
option auto_refresh '1'
@ -11,11 +11,11 @@ config client-guardian 'config'
# Debug Mode
option debug_enabled '0'
option debug_level 'INFO'
option enable_active_scan '1'
# Auto-Zoning / Auto-Parking
option auto_zoning_enabled '1'
option auto_parking_zone 'guest'
option auto_parking_approve '0'
option enable_active_scan '0'
# Auto-Zoning / Auto-Parking - DISABLED BY DEFAULT for safety
option auto_zoning_enabled '0'
option auto_parking_zone 'lan_private'
option auto_parking_approve '1'
# Alert Configuration
config alerts 'alerts'
@ -196,7 +196,7 @@ config schedule 'school_hours'
config schedule 'night_block'
option name 'Blocage Nocturne'
option enabled '1'
option enabled '0'
option action 'block'
option start_time '22:00'
option end_time '07:00'
@ -216,11 +216,11 @@ config schedule 'weekend_limit'
list days 'sat'
list days 'sun'
# Threat Intelligence Integration
# Threat Intelligence Integration - DISABLED by default for safety
config threat_policy 'threat_policy'
option enabled '1'
option auto_ban_threshold '80'
option auto_quarantine_threshold '60'
option enabled '0'
option auto_ban_threshold '95'
option auto_quarantine_threshold '90'
option threat_check_interval '60'
# Auto-Zoning Rules

View File

@ -11,6 +11,49 @@ LOG_FILE="/var/log/client-guardian.log"
CLIENTS_DB="/tmp/client-guardian-clients.json"
ALERTS_QUEUE="/tmp/client-guardian-alerts.json"
# SAFETY LIMITS - prevent accidental mass blocking
MAX_BLOCKED_DEVICES=10
SAFETY_BYPASS_FILE="/tmp/client-guardian-safety-bypass"
# Count currently blocked devices (CG_NOLAN, CG_BLOCK, CG_QUARANTINE rules)
count_blocked_devices() {
uci show firewall 2>/dev/null | grep -c "\.name='CG_NOLAN_\|CG_BLOCK_\|CG_QUARANTINE_'" 2>/dev/null || echo "0"
}
# Check if safety limit is reached
check_safety_limit() {
local force="$1"
# If safety bypass file exists (set by admin), skip check
[ -f "$SAFETY_BYPASS_FILE" ] && return 0
# If force flag is set, skip check
[ "$force" = "1" ] && return 0
local blocked_count=$(count_blocked_devices)
if [ "$blocked_count" -ge "$MAX_BLOCKED_DEVICES" ]; then
log_event "warn" "SAFETY LIMIT: Already $blocked_count devices blocked (max: $MAX_BLOCKED_DEVICES)"
return 1
fi
return 0
}
# Clear all CG firewall rules (emergency restore)
clear_all_cg_rules() {
log_event "warn" "EMERGENCY: Clearing all Client Guardian firewall rules"
# Find and delete all CG rules
local rules=$(uci show firewall 2>/dev/null | grep "\.name='CG_" | cut -d. -f2 | cut -d= -f1 | sort -ru)
for rule in $rules; do
uci delete firewall.$rule 2>/dev/null
done
uci commit firewall
/etc/init.d/firewall reload >/dev/null 2>&1
log_event "info" "All Client Guardian rules cleared"
echo '{"success":true,"message":"All CG rules cleared"}'
}
# Logging function with debug support
log_event() {
local level="$1"
@ -1051,9 +1094,18 @@ create_firewall_zone() {
apply_client_rules() {
local mac="$1"
local zone="$2"
local force="${3:-0}"
log_event "info" "Applying rules for MAC: $mac -> Zone: $zone"
# SAFETY CHECK: If zone would block/quarantine and safety limit reached, skip
if [ "$zone" = "blocked" ] || [ "$zone" = "quarantine" ]; then
if ! check_safety_limit "$force"; then
log_event "error" "SAFETY LIMIT REACHED: Refusing to block MAC $mac. Use force=1 or clear existing rules."
return 1
fi
fi
# Normalize MAC to uppercase for firewall rules
local mac_upper=$(echo "$mac" | tr 'a-f' 'A-F')
@ -1649,7 +1701,7 @@ get_client() {
# Main dispatcher
case "$1" in
list)
echo '{"status":{},"clients":{},"zones":{},"parental":{},"alerts":{},"logs":{"limit":"int","level":"str"},"approve_client":{"mac":"str","name":"str","zone":"str","notes":"str"},"ban_client":{"mac":"str","reason":"str"},"quarantine_client":{"mac":"str"},"update_client":{"section":"str","name":"str","zone":"str","notes":"str","daily_quota":"int","static_ip":"str"},"update_zone":{"id":"str","name":"str","bandwidth_limit":"int","content_filter":"str"},"send_test_alert":{"type":"str"},"get_policy":{},"set_policy":{"policy":"str","auto_approve":"bool","session_timeout":"int"},"get_client":{"mac":"str"},"sync_zones":{},"list_profiles":{},"apply_profile":{"profile_id":"str","auto_refresh":"str","refresh_interval":"str","threat_enabled":"str","auto_ban_threshold":"str","auto_quarantine_threshold":"str"}}'
echo '{"status":{},"clients":{},"zones":{},"parental":{},"alerts":{},"logs":{"limit":"int","level":"str"},"approve_client":{"mac":"str","name":"str","zone":"str","notes":"str"},"ban_client":{"mac":"str","reason":"str"},"quarantine_client":{"mac":"str"},"update_client":{"section":"str","name":"str","zone":"str","notes":"str","daily_quota":"int","static_ip":"str"},"update_zone":{"id":"str","name":"str","bandwidth_limit":"int","content_filter":"str"},"send_test_alert":{"type":"str"},"get_policy":{},"set_policy":{"policy":"str","auto_approve":"bool","session_timeout":"int"},"get_client":{"mac":"str"},"sync_zones":{},"list_profiles":{},"apply_profile":{"profile_id":"str","auto_refresh":"str","refresh_interval":"str","threat_enabled":"str","auto_ban_threshold":"str","auto_quarantine_threshold":"str"},"clear_rules":{},"safety_status":{}}'
;;
call)
case "$2" in
@ -1677,6 +1729,15 @@ case "$1" in
;;
list_profiles) list_profiles ;;
apply_profile) apply_profile ;;
clear_rules) clear_all_cg_rules ;;
safety_status)
json_init
local blocked=$(count_blocked_devices)
json_add_int "blocked_devices" "$blocked"
json_add_int "max_allowed" "$MAX_BLOCKED_DEVICES"
json_add_boolean "safety_limit_reached" $([ "$blocked" -ge "$MAX_BLOCKED_DEVICES" ] && echo 1 || echo 0)
json_dump
;;
*) echo '{"error": "Unknown method"}' ;;
esac
;;