feat(mac-guardian): Rename to secubox-app-mac-guardian and add LuCI interface
Rename package folder to follow secubox-app-* convention and add luci-app-mac-guardian with KISS dashboard: status cards, client table with trust/block actions, recent alerts, and configuration form. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
aeb4825b25
commit
373d77368e
29
package/secubox/luci-app-mac-guardian/Makefile
Normal file
29
package/secubox/luci-app-mac-guardian/Makefile
Normal file
@ -0,0 +1,29 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
LUCI_TITLE:=LuCI MAC Guardian - WiFi MAC Security Monitor
|
||||
LUCI_DEPENDS:=+secubox-app-mac-guardian
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
PKG_NAME:=luci-app-mac-guardian
|
||||
PKG_VERSION:=0.5.0
|
||||
PKG_RELEASE:=1
|
||||
PKG_MAINTAINER:=Gandalf <contact@cybermind.fr>
|
||||
PKG_LICENSE:=GPL-3.0-or-later
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
define Package/luci-app-mac-guardian/install
|
||||
$(INSTALL_DIR) $(1)/usr/share/luci/menu.d
|
||||
$(INSTALL_DATA) ./root/usr/share/luci/menu.d/luci-app-mac-guardian.json $(1)/usr/share/luci/menu.d/
|
||||
|
||||
$(INSTALL_DIR) $(1)/usr/share/rpcd/acl.d
|
||||
$(INSTALL_DATA) ./root/usr/share/rpcd/acl.d/luci-app-mac-guardian.json $(1)/usr/share/rpcd/acl.d/
|
||||
|
||||
$(INSTALL_DIR) $(1)/www/luci-static/resources/view/mac-guardian
|
||||
$(INSTALL_DATA) ./htdocs/luci-static/resources/view/mac-guardian/*.js $(1)/www/luci-static/resources/view/mac-guardian/
|
||||
|
||||
$(INSTALL_DIR) $(1)/usr/libexec/rpcd
|
||||
$(INSTALL_BIN) ./root/usr/libexec/rpcd/luci.mac-guardian $(1)/usr/libexec/rpcd/
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,luci-app-mac-guardian))
|
||||
@ -0,0 +1,350 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require form';
|
||||
'require uci';
|
||||
'require rpc';
|
||||
'require ui';
|
||||
|
||||
var callStatus = rpc.declare({
|
||||
object: 'luci.mac-guardian',
|
||||
method: 'status',
|
||||
expect: { '': {} }
|
||||
});
|
||||
|
||||
var callGetClients = rpc.declare({
|
||||
object: 'luci.mac-guardian',
|
||||
method: 'get_clients',
|
||||
expect: { '': {} }
|
||||
});
|
||||
|
||||
var callGetEvents = rpc.declare({
|
||||
object: 'luci.mac-guardian',
|
||||
method: 'get_events',
|
||||
params: ['count'],
|
||||
expect: { '': {} }
|
||||
});
|
||||
|
||||
var callScan = rpc.declare({
|
||||
object: 'luci.mac-guardian',
|
||||
method: 'scan'
|
||||
});
|
||||
|
||||
var callStart = rpc.declare({
|
||||
object: 'luci.mac-guardian',
|
||||
method: 'start'
|
||||
});
|
||||
|
||||
var callStop = rpc.declare({
|
||||
object: 'luci.mac-guardian',
|
||||
method: 'stop'
|
||||
});
|
||||
|
||||
var callRestart = rpc.declare({
|
||||
object: 'luci.mac-guardian',
|
||||
method: 'restart'
|
||||
});
|
||||
|
||||
var callTrust = rpc.declare({
|
||||
object: 'luci.mac-guardian',
|
||||
method: 'trust',
|
||||
params: ['mac']
|
||||
});
|
||||
|
||||
var callBlock = rpc.declare({
|
||||
object: 'luci.mac-guardian',
|
||||
method: 'block',
|
||||
params: ['mac']
|
||||
});
|
||||
|
||||
function formatDate(ts) {
|
||||
if (!ts || ts === 0) return '-';
|
||||
var d = new Date(ts * 1000);
|
||||
var pad = function(n) { return n < 10 ? '0' + n : n; };
|
||||
return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()) +
|
||||
' ' + pad(d.getHours()) + ':' + pad(d.getMinutes());
|
||||
}
|
||||
|
||||
function statusBadge(status) {
|
||||
var colors = {
|
||||
'trusted': '#080',
|
||||
'suspect': '#c60',
|
||||
'blocked': '#c00',
|
||||
'unknown': '#888'
|
||||
};
|
||||
var color = colors[status] || '#888';
|
||||
return '<span style="display:inline-block;padding:2px 8px;border-radius:3px;' +
|
||||
'background:' + color + ';color:#fff;font-size:12px;font-weight:bold;">' +
|
||||
status + '</span>';
|
||||
}
|
||||
|
||||
return view.extend({
|
||||
load: function() {
|
||||
return Promise.all([
|
||||
uci.load('mac-guardian'),
|
||||
callStatus(),
|
||||
callGetClients(),
|
||||
callGetEvents(10)
|
||||
]);
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
var status = data[1];
|
||||
var clientData = data[2];
|
||||
var eventData = data[3];
|
||||
var clients = (clientData && clientData.clients) ? clientData.clients : [];
|
||||
var events = (eventData && eventData.events) ? eventData.events : [];
|
||||
var m, s, o;
|
||||
|
||||
m = new form.Map('mac-guardian', _('MAC Guardian'),
|
||||
_('WiFi MAC address security monitor. Detects randomized MACs, spoofing, and MAC floods.'));
|
||||
|
||||
// ==========================================
|
||||
// Status Section
|
||||
// ==========================================
|
||||
s = m.section(form.NamedSection, 'main', 'mac-guardian', _('Status'));
|
||||
s.anonymous = true;
|
||||
|
||||
o = s.option(form.DummyValue, '_status');
|
||||
o.rawhtml = true;
|
||||
o.cfgvalue = function() {
|
||||
var svcColor = status.service_status === 'running' ? '#080' : '#c00';
|
||||
var svcLabel = status.service_status === 'running' ? 'Running' : 'Stopped';
|
||||
|
||||
var html = '<div style="display:flex;gap:25px;flex-wrap:wrap;">';
|
||||
|
||||
// Service card
|
||||
html += '<div style="min-width:160px;">';
|
||||
html += '<h4 style="margin:0 0 8px 0;border-bottom:1px solid #ddd;padding-bottom:4px;">Service</h4>';
|
||||
html += '<p><b>Status:</b> <span style="color:' + svcColor + ';font-weight:bold;">' + svcLabel + '</span></p>';
|
||||
html += '<p><b>Policy:</b> ' + (status.policy || 'alert') + '</p>';
|
||||
html += '<p><b>Interval:</b> ' + (status.scan_interval || 30) + 's</p>';
|
||||
html += '</div>';
|
||||
|
||||
// Clients card
|
||||
var cl = status.clients || {};
|
||||
html += '<div style="min-width:160px;">';
|
||||
html += '<h4 style="margin:0 0 8px 0;border-bottom:1px solid #ddd;padding-bottom:4px;">Clients</h4>';
|
||||
html += '<p><b>Total:</b> ' + (cl.total || 0) + '</p>';
|
||||
html += '<p><b>Trusted:</b> <span style="color:#080;">' + (cl.trusted || 0) + '</span></p>';
|
||||
html += '<p><b>Suspect:</b> <span style="color:#c60;">' + (cl.suspect || 0) + '</span></p>';
|
||||
html += '<p><b>Blocked:</b> <span style="color:#c00;">' + (cl.blocked || 0) + '</span></p>';
|
||||
html += '</div>';
|
||||
|
||||
// Interfaces card
|
||||
var ifaces = status.interfaces || [];
|
||||
html += '<div style="min-width:160px;">';
|
||||
html += '<h4 style="margin:0 0 8px 0;border-bottom:1px solid #ddd;padding-bottom:4px;">WiFi Interfaces</h4>';
|
||||
if (ifaces.length === 0) {
|
||||
html += '<p style="color:#888;">None detected</p>';
|
||||
} else {
|
||||
for (var i = 0; i < ifaces.length; i++) {
|
||||
html += '<p><b>' + ifaces[i].name + '</b> (' + ifaces[i].essid + ') - ' + ifaces[i].stations + ' STA</p>';
|
||||
}
|
||||
}
|
||||
html += '</div>';
|
||||
|
||||
html += '</div>';
|
||||
return html;
|
||||
};
|
||||
|
||||
// Control buttons
|
||||
o = s.option(form.Button, '_start', _('Start'));
|
||||
o.inputtitle = _('Start');
|
||||
o.inputstyle = 'apply';
|
||||
o.onclick = function() {
|
||||
return callStart().then(function() { window.location.reload(); });
|
||||
};
|
||||
|
||||
o = s.option(form.Button, '_stop', _('Stop'));
|
||||
o.inputtitle = _('Stop');
|
||||
o.inputstyle = 'remove';
|
||||
o.onclick = function() {
|
||||
return callStop().then(function() { window.location.reload(); });
|
||||
};
|
||||
|
||||
o = s.option(form.Button, '_scan', _('Scan Now'));
|
||||
o.inputtitle = _('Scan');
|
||||
o.inputstyle = 'reload';
|
||||
o.onclick = function() {
|
||||
ui.showModal(_('Scanning'), [
|
||||
E('p', { 'class': 'spinning' }, _('Scanning WiFi interfaces...'))
|
||||
]);
|
||||
return callScan().then(function() {
|
||||
ui.hideModal();
|
||||
window.location.reload();
|
||||
});
|
||||
};
|
||||
|
||||
// ==========================================
|
||||
// Clients Table
|
||||
// ==========================================
|
||||
s = m.section(form.NamedSection, 'main', 'mac-guardian', _('Known Clients'));
|
||||
s.anonymous = true;
|
||||
|
||||
o = s.option(form.DummyValue, '_clients');
|
||||
o.rawhtml = true;
|
||||
o.cfgvalue = function() {
|
||||
if (clients.length === 0) {
|
||||
return '<p style="color:#888;">No clients detected yet. Run a scan or wait for devices to connect.</p>';
|
||||
}
|
||||
|
||||
var html = '<div style="overflow-x:auto;">';
|
||||
html += '<table class="table" style="width:100%;border-collapse:collapse;">';
|
||||
html += '<tr class="tr table-titles">';
|
||||
html += '<th class="th">MAC</th>';
|
||||
html += '<th class="th">Vendor</th>';
|
||||
html += '<th class="th">Hostname</th>';
|
||||
html += '<th class="th">Interface</th>';
|
||||
html += '<th class="th">First Seen</th>';
|
||||
html += '<th class="th">Last Seen</th>';
|
||||
html += '<th class="th">Status</th>';
|
||||
html += '<th class="th">Actions</th>';
|
||||
html += '</tr>';
|
||||
|
||||
for (var i = 0; i < clients.length; i++) {
|
||||
var c = clients[i];
|
||||
var macDisplay = c.mac;
|
||||
if (c.randomized) {
|
||||
macDisplay += ' <span title="Randomized MAC" style="color:#c60;font-weight:bold;">R</span>';
|
||||
}
|
||||
|
||||
html += '<tr class="tr">';
|
||||
html += '<td class="td" style="font-family:monospace;">' + macDisplay + '</td>';
|
||||
html += '<td class="td">' + (c.vendor || '-') + '</td>';
|
||||
html += '<td class="td">' + (c.hostname || '-') + '</td>';
|
||||
html += '<td class="td">' + (c.iface || '-') + '</td>';
|
||||
html += '<td class="td">' + formatDate(c.first_seen) + '</td>';
|
||||
html += '<td class="td">' + formatDate(c.last_seen) + '</td>';
|
||||
html += '<td class="td">' + statusBadge(c.status) + '</td>';
|
||||
html += '<td class="td">';
|
||||
if (c.status !== 'trusted') {
|
||||
html += '<button class="cbi-button cbi-button-apply" data-mac="' + c.mac + '" data-action="trust" style="margin-right:4px;">Trust</button>';
|
||||
}
|
||||
if (c.status !== 'blocked') {
|
||||
html += '<button class="cbi-button cbi-button-remove" data-mac="' + c.mac + '" data-action="block">Block</button>';
|
||||
}
|
||||
html += '</td>';
|
||||
html += '</tr>';
|
||||
}
|
||||
|
||||
html += '</table>';
|
||||
html += '</div>';
|
||||
return html;
|
||||
};
|
||||
|
||||
// ==========================================
|
||||
// Recent Alerts
|
||||
// ==========================================
|
||||
s = m.section(form.NamedSection, 'main', 'mac-guardian', _('Recent Alerts'));
|
||||
s.anonymous = true;
|
||||
|
||||
o = s.option(form.DummyValue, '_events');
|
||||
o.rawhtml = true;
|
||||
o.cfgvalue = function() {
|
||||
if (events.length === 0) {
|
||||
return '<p style="color:#888;">No alerts recorded.</p>';
|
||||
}
|
||||
|
||||
var html = '<div style="overflow-x:auto;">';
|
||||
html += '<table class="table" style="width:100%;border-collapse:collapse;">';
|
||||
html += '<tr class="tr table-titles">';
|
||||
html += '<th class="th">Time</th>';
|
||||
html += '<th class="th">Event</th>';
|
||||
html += '<th class="th">MAC</th>';
|
||||
html += '<th class="th">Interface</th>';
|
||||
html += '<th class="th">Details</th>';
|
||||
html += '</tr>';
|
||||
|
||||
for (var i = events.length - 1; i >= 0; i--) {
|
||||
try {
|
||||
var ev = JSON.parse(events[i]);
|
||||
html += '<tr class="tr">';
|
||||
html += '<td class="td" style="white-space:nowrap;">' + (ev.ts || '-') + '</td>';
|
||||
html += '<td class="td"><b>' + (ev.event || '-') + '</b></td>';
|
||||
html += '<td class="td" style="font-family:monospace;">' + (ev.mac || '-') + '</td>';
|
||||
html += '<td class="td">' + (ev.iface || '-') + '</td>';
|
||||
html += '<td class="td" style="font-size:12px;">' + (ev.details || '-') + '</td>';
|
||||
html += '</tr>';
|
||||
} catch(e) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
html += '</table>';
|
||||
html += '</div>';
|
||||
return html;
|
||||
};
|
||||
|
||||
// ==========================================
|
||||
// Configuration
|
||||
// ==========================================
|
||||
s = m.section(form.NamedSection, 'main', 'mac-guardian', _('Configuration'));
|
||||
s.anonymous = true;
|
||||
|
||||
o = s.option(form.Flag, 'enabled', _('Enabled'),
|
||||
_('Enable MAC Guardian service'));
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.option(form.Value, 'scan_interval', _('Scan Interval'),
|
||||
_('Seconds between WiFi scans'));
|
||||
o.datatype = 'uinteger';
|
||||
o.default = '30';
|
||||
o.placeholder = '30';
|
||||
|
||||
s = m.section(form.NamedSection, 'detection', 'detection', _('Detection'));
|
||||
s.anonymous = true;
|
||||
|
||||
o = s.option(form.Flag, 'random_mac', _('Detect Randomized MACs'),
|
||||
_('Alert on locally-administered (randomized) MAC addresses'));
|
||||
o.default = '1';
|
||||
|
||||
o = s.option(form.Flag, 'spoof_detection', _('Detect Spoofing'),
|
||||
_('Alert when a MAC address appears on a different interface'));
|
||||
o.default = '1';
|
||||
|
||||
o = s.option(form.Flag, 'mac_flip', _('Detect MAC Floods'),
|
||||
_('Alert when many new MACs appear in a short window'));
|
||||
o.default = '1';
|
||||
|
||||
s = m.section(form.NamedSection, 'enforcement', 'enforcement', _('Enforcement'));
|
||||
s.anonymous = true;
|
||||
|
||||
o = s.option(form.ListValue, 'policy', _('Policy'),
|
||||
_('Action to take on detected threats'));
|
||||
o.value('alert', _('Alert only'));
|
||||
o.value('quarantine', _('Quarantine (drop traffic)'));
|
||||
o.value('deny', _('Deny (drop + deauthenticate)'));
|
||||
o.default = 'alert';
|
||||
|
||||
// ==========================================
|
||||
// Bind action buttons
|
||||
// ==========================================
|
||||
var rendered = m.render();
|
||||
|
||||
return rendered.then(function(node) {
|
||||
node.addEventListener('click', function(ev) {
|
||||
var btn = ev.target.closest('[data-action]');
|
||||
if (!btn) return;
|
||||
|
||||
var mac = btn.getAttribute('data-mac');
|
||||
var action = btn.getAttribute('data-action');
|
||||
|
||||
if (action === 'trust') {
|
||||
callTrust(mac).then(function() {
|
||||
ui.addNotification(null, E('p', _('MAC %s trusted').format(mac)), 'success');
|
||||
window.location.reload();
|
||||
});
|
||||
} else if (action === 'block') {
|
||||
if (confirm(_('Block and deauthenticate %s?').format(mac))) {
|
||||
callBlock(mac).then(function() {
|
||||
ui.addNotification(null, E('p', _('MAC %s blocked').format(mac)), 'success');
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return node;
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,190 @@
|
||||
#!/bin/sh
|
||||
|
||||
. /lib/functions.sh
|
||||
. /usr/share/libubox/jshn.sh
|
||||
|
||||
MG_DBFILE="/var/run/mac-guardian/known.db"
|
||||
MG_LOGFILE="/var/log/mac-guardian.log"
|
||||
|
||||
case "$1" in
|
||||
list)
|
||||
echo '{"status":{},"get_clients":{},"get_events":{"count":"int"},"scan":{},"start":{},"stop":{},"restart":{},"trust":{"mac":"str"},"block":{"mac":"str"}}'
|
||||
;;
|
||||
call)
|
||||
case "$2" in
|
||||
status)
|
||||
json_init
|
||||
|
||||
enabled=$(uci -q get mac-guardian.main.enabled)
|
||||
policy=$(uci -q get mac-guardian.enforcement.policy)
|
||||
scan_interval=$(uci -q get mac-guardian.main.scan_interval)
|
||||
detect_random=$(uci -q get mac-guardian.detection.random_mac)
|
||||
detect_spoof=$(uci -q get mac-guardian.detection.spoof_detection)
|
||||
|
||||
json_add_boolean "enabled" ${enabled:-0}
|
||||
json_add_string "policy" "${policy:-alert}"
|
||||
json_add_int "scan_interval" ${scan_interval:-30}
|
||||
json_add_boolean "detect_random" ${detect_random:-1}
|
||||
json_add_boolean "detect_spoof" ${detect_spoof:-1}
|
||||
|
||||
# Service running?
|
||||
if pgrep mac-guardian >/dev/null 2>&1; then
|
||||
json_add_string "service_status" "running"
|
||||
else
|
||||
json_add_string "service_status" "stopped"
|
||||
fi
|
||||
|
||||
# WiFi interfaces
|
||||
json_add_array "interfaces"
|
||||
if command -v iwinfo >/dev/null 2>&1; then
|
||||
iwinfo 2>/dev/null | grep "ESSID" | while read -r line; do
|
||||
iface=$(echo "$line" | awk '{print $1}')
|
||||
essid=$(echo "$line" | sed 's/.*ESSID: "\(.*\)"/\1/')
|
||||
sta_count=$(iwinfo "$iface" assoclist 2>/dev/null | grep -cE '[0-9A-Fa-f]{2}(:[0-9A-Fa-f]{2}){5}')
|
||||
json_add_object ""
|
||||
json_add_string "name" "$iface"
|
||||
json_add_string "essid" "$essid"
|
||||
json_add_int "stations" ${sta_count:-0}
|
||||
json_close_object
|
||||
done
|
||||
fi
|
||||
json_close_array
|
||||
|
||||
# DB stats
|
||||
total=0
|
||||
trusted=0
|
||||
suspect=0
|
||||
blocked=0
|
||||
unknown=0
|
||||
if [ -f "$MG_DBFILE" ] && [ -s "$MG_DBFILE" ]; then
|
||||
total=$(wc -l < "$MG_DBFILE")
|
||||
trusted=$(grep -c '|trusted$' "$MG_DBFILE" 2>/dev/null || echo 0)
|
||||
suspect=$(grep -c '|suspect$' "$MG_DBFILE" 2>/dev/null || echo 0)
|
||||
blocked=$(grep -c '|blocked$' "$MG_DBFILE" 2>/dev/null || echo 0)
|
||||
unknown=$(grep -c '|unknown$' "$MG_DBFILE" 2>/dev/null || echo 0)
|
||||
fi
|
||||
json_add_object "clients"
|
||||
json_add_int "total" $total
|
||||
json_add_int "trusted" $trusted
|
||||
json_add_int "suspect" $suspect
|
||||
json_add_int "blocked" $blocked
|
||||
json_add_int "unknown" $unknown
|
||||
json_close_object
|
||||
|
||||
json_dump
|
||||
;;
|
||||
|
||||
get_clients)
|
||||
json_init
|
||||
json_add_array "clients"
|
||||
|
||||
if [ -f "$MG_DBFILE" ] && [ -s "$MG_DBFILE" ]; then
|
||||
while IFS='|' read -r mac oui first_seen last_seen iface hostname status; do
|
||||
[ -z "$mac" ] && continue
|
||||
json_add_object ""
|
||||
json_add_string "mac" "$mac"
|
||||
json_add_string "oui" "$oui"
|
||||
json_add_int "first_seen" ${first_seen:-0}
|
||||
json_add_int "last_seen" ${last_seen:-0}
|
||||
json_add_string "iface" "$iface"
|
||||
json_add_string "hostname" "${hostname:--}"
|
||||
json_add_string "status" "$status"
|
||||
|
||||
# OUI vendor lookup
|
||||
vendor=""
|
||||
if [ -f /usr/lib/secubox/mac-guardian/oui.tsv ]; then
|
||||
oui_upper=$(echo "$oui" | tr 'a-f' 'A-F')
|
||||
vendor=$(grep -i "^${oui_upper} " /usr/lib/secubox/mac-guardian/oui.tsv 2>/dev/null | cut -f2 | head -1)
|
||||
fi
|
||||
json_add_string "vendor" "${vendor:--}"
|
||||
|
||||
# Randomized check
|
||||
first_octet=$(echo "$mac" | cut -d: -f1)
|
||||
is_rand=0
|
||||
[ $((0x$first_octet & 0x02)) -ne 0 ] && is_rand=1
|
||||
json_add_boolean "randomized" $is_rand
|
||||
|
||||
json_close_object
|
||||
done < "$MG_DBFILE"
|
||||
fi
|
||||
|
||||
json_close_array
|
||||
json_dump
|
||||
;;
|
||||
|
||||
get_events)
|
||||
read -r input
|
||||
count=$(echo "$input" | jsonfilter -e '@.count' 2>/dev/null)
|
||||
[ -z "$count" ] && count=20
|
||||
|
||||
json_init
|
||||
json_add_array "events"
|
||||
|
||||
if [ -f "$MG_LOGFILE" ] && [ -s "$MG_LOGFILE" ]; then
|
||||
tail -"$count" "$MG_LOGFILE" 2>/dev/null | while read -r line; do
|
||||
json_add_string "" "$line"
|
||||
done
|
||||
fi
|
||||
|
||||
json_close_array
|
||||
json_dump
|
||||
;;
|
||||
|
||||
scan)
|
||||
/usr/sbin/mac-guardian scan >/dev/null 2>&1
|
||||
json_init
|
||||
json_add_boolean "success" 1
|
||||
json_dump
|
||||
;;
|
||||
|
||||
start)
|
||||
/etc/init.d/mac-guardian start >/dev/null 2>&1
|
||||
json_init
|
||||
json_add_boolean "success" 1
|
||||
json_dump
|
||||
;;
|
||||
|
||||
stop)
|
||||
/etc/init.d/mac-guardian stop >/dev/null 2>&1
|
||||
json_init
|
||||
json_add_boolean "success" 1
|
||||
json_dump
|
||||
;;
|
||||
|
||||
restart)
|
||||
/etc/init.d/mac-guardian restart >/dev/null 2>&1
|
||||
json_init
|
||||
json_add_boolean "success" 1
|
||||
json_dump
|
||||
;;
|
||||
|
||||
trust)
|
||||
read -r input
|
||||
mac=$(echo "$input" | jsonfilter -e '@.mac' 2>/dev/null)
|
||||
if [ -n "$mac" ]; then
|
||||
/usr/sbin/mac-guardian trust "$mac" >/dev/null 2>&1
|
||||
json_init
|
||||
json_add_boolean "success" 1
|
||||
json_dump
|
||||
else
|
||||
echo '{"success":false,"error":"missing mac"}'
|
||||
fi
|
||||
;;
|
||||
|
||||
block)
|
||||
read -r input
|
||||
mac=$(echo "$input" | jsonfilter -e '@.mac' 2>/dev/null)
|
||||
if [ -n "$mac" ]; then
|
||||
/usr/sbin/mac-guardian block "$mac" >/dev/null 2>&1
|
||||
json_init
|
||||
json_add_boolean "success" 1
|
||||
json_dump
|
||||
else
|
||||
echo '{"success":false,"error":"missing mac"}'
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
@ -0,0 +1,14 @@
|
||||
{
|
||||
"admin/services/mac-guardian": {
|
||||
"title": "MAC Guardian",
|
||||
"order": 70,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "mac-guardian/dashboard"
|
||||
},
|
||||
"depends": {
|
||||
"acl": ["luci-app-mac-guardian"],
|
||||
"uci": {"mac-guardian": true}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
{
|
||||
"luci-app-mac-guardian": {
|
||||
"description": "Grant access to MAC Guardian WiFi security monitor",
|
||||
"read": {
|
||||
"file": {
|
||||
"/etc/config/mac-guardian": ["read"],
|
||||
"/var/run/mac-guardian/known.db": ["read"],
|
||||
"/var/log/mac-guardian.log": ["read"]
|
||||
},
|
||||
"ubus": {
|
||||
"file": ["read", "stat"],
|
||||
"luci.mac-guardian": ["*"]
|
||||
},
|
||||
"uci": ["mac-guardian"]
|
||||
},
|
||||
"write": {
|
||||
"file": {
|
||||
"/etc/config/mac-guardian": ["write"]
|
||||
},
|
||||
"ubus": {
|
||||
"luci.mac-guardian": ["*"]
|
||||
},
|
||||
"uci": ["mac-guardian"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=mac-guardian
|
||||
PKG_NAME:=secubox-app-mac-guardian
|
||||
PKG_VERSION:=0.5.0
|
||||
PKG_RELEASE:=1
|
||||
|
||||
@ -9,7 +9,7 @@ PKG_LICENSE:=GPL-3.0-or-later
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/mac-guardian
|
||||
define Package/secubox-app-mac-guardian
|
||||
SECTION:=net
|
||||
CATEGORY:=Network
|
||||
SUBMENU:=SecuBox
|
||||
@ -18,21 +18,21 @@ define Package/mac-guardian
|
||||
PKGARCH:=all
|
||||
endef
|
||||
|
||||
define Package/mac-guardian/description
|
||||
define Package/secubox-app-mac-guardian/description
|
||||
WiFi MAC address security monitor for SecuBox.
|
||||
Detects randomized MACs, OUI anomalies, MAC floods,
|
||||
and spoofing. Integrates with CrowdSec and provides
|
||||
real-time hostapd hotplug detection.
|
||||
endef
|
||||
|
||||
define Package/mac-guardian/conffiles
|
||||
define Package/secubox-app-mac-guardian/conffiles
|
||||
/etc/config/mac-guardian
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
endef
|
||||
|
||||
define Package/mac-guardian/install
|
||||
define Package/secubox-app-mac-guardian/install
|
||||
$(INSTALL_DIR) $(1)/usr/sbin
|
||||
$(INSTALL_BIN) ./files/usr/sbin/mac-guardian $(1)/usr/sbin/
|
||||
|
||||
@ -60,4 +60,4 @@ define Package/mac-guardian/install
|
||||
$(INSTALL_DATA) ./files/etc/crowdsec/scenarios/secubox-mac-spoof.yaml $(1)/etc/crowdsec/scenarios/
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,mac-guardian))
|
||||
$(eval $(call BuildPackage,secubox-app-mac-guardian))
|
||||
Loading…
Reference in New Issue
Block a user