feat(iot-guard): Add IoT device isolation and security monitoring
Backend (secubox-iot-guard): - OUI-based device classification with 100+ IoT vendor prefixes - 10 device classes: camera, thermostat, lighting, plug, assistant, etc. - Risk scoring (0-100) with auto-isolation threshold - Anomaly detection: bandwidth spikes, port scans, time anomalies - Integration with Client Guardian, MAC Guardian, Vortex Firewall - iot-guardctl CLI for status/list/scan/isolate/trust/block - SQLite database for devices, anomalies, cloud dependencies - Traffic baseline profiles for common device classes Frontend (luci-app-iot-guard): - KISS-style overview dashboard with security score - Device management with isolate/trust/block actions - Vendor classification rules editor - Settings form for UCI configuration - RPCD handler with 11 methods - Public ACL for unauthenticated dashboard access Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
0544adbee6
commit
8ef0c70d0f
@ -1089,3 +1089,36 @@ _Last updated: 2026-02-10_
|
||||
- Alert Timeline with severity-colored items.
|
||||
- 15-second live polling for health, alerts, IPs.
|
||||
- Full dark mode support.
|
||||
|
||||
58. **IoT Guard Implementation (2026-02-11)**
|
||||
- Created `secubox-iot-guard` package — IoT device isolation, classification, and security monitoring.
|
||||
- **Device Classification**:
|
||||
- OUI-based classification with 100+ IoT manufacturer prefixes
|
||||
- 10 device classes: camera, thermostat, lighting, plug, assistant, media, lock, sensor, diy, mixed
|
||||
- Traffic-based classification from cloud dependency tracking
|
||||
- Hostname-based classification fallback
|
||||
- **Risk Scoring**:
|
||||
- 0-100 risk score with vendor risk, anomaly penalty, cloud dependency penalty
|
||||
- Risk levels: low (20), medium (50), high (80)
|
||||
- Auto-isolation threshold configurable (default 80)
|
||||
- **Anomaly Detection**:
|
||||
- Bandwidth spike detection (Nx above baseline)
|
||||
- New destination tracking
|
||||
- Port scan behavior detection
|
||||
- Time-based anomaly (unusual activity hours)
|
||||
- **Integration Points**:
|
||||
- Client Guardian: Zone assignment (IoT zone)
|
||||
- MAC Guardian: L2 blocking/trust
|
||||
- Vortex Firewall: DNS filtering for IoT malware feeds
|
||||
- Bandwidth Manager: Rate limiting
|
||||
- **CLI** (`iot-guardctl`): status, list, show, scan, isolate, trust, block, anomalies, cloud-map, daemon
|
||||
- **UCI Configuration**: main settings, zone policy, vendor rules, allowlist, blocklist
|
||||
- **Baseline Profiles**: JSON profiles for camera, thermostat, plug, assistant device classes
|
||||
- Created `luci-app-iot-guard` — LuCI dashboard with KISS-style views.
|
||||
- **Dashboard Views**:
|
||||
- Overview: Security score, device counts, risk distribution, anomaly timeline
|
||||
- Devices: Filterable table with device details, isolate/trust/block actions
|
||||
- Policies: Vendor classification rules management
|
||||
- Settings: UCI form for configuration
|
||||
- **RPCD Handler**: 11 methods (status, get_devices, get_device, get_anomalies, scan, isolate/trust/block_device, get_vendor_rules, add/delete_vendor_rule, get_cloud_map)
|
||||
- **ACL**: Public access for status and device list via `unauthenticated` group
|
||||
|
||||
@ -335,6 +335,19 @@ _Last updated: 2026-02-11_
|
||||
- Blockchain: `peer_approved` blocks recorded correctly
|
||||
- Threat Intel: 288 local IOCs, 67 threat_ioc blocks in chain
|
||||
|
||||
### Just Completed (2026-02-11)
|
||||
|
||||
- **IoT Guard Implementation** — DONE (2026-02-11)
|
||||
- Created `secubox-iot-guard` package for IoT device isolation and security
|
||||
- OUI-based classification with 100+ IoT manufacturer prefixes
|
||||
- 10 device classes with risk scoring (0-100)
|
||||
- Anomaly detection: bandwidth spikes, new destinations, port scans, time anomalies
|
||||
- Integration: Client Guardian (zones), MAC Guardian (L2), Vortex Firewall (DNS), Bandwidth Manager (QoS)
|
||||
- CLI: `iot-guardctl` with status/list/show/scan/isolate/trust/block/anomalies/cloud-map
|
||||
- Created `luci-app-iot-guard` with KISS-style dashboard
|
||||
- 4 views: Overview, Devices, Policies, Settings
|
||||
- RPCD handler with 11 methods + public ACL for unauthenticated access
|
||||
|
||||
### Next Up — Couche 1
|
||||
|
||||
1. **Guacamole Pre-built Binaries**
|
||||
|
||||
16
package/secubox/luci-app-iot-guard/Makefile
Normal file
16
package/secubox/luci-app-iot-guard/Makefile
Normal file
@ -0,0 +1,16 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
LUCI_TITLE:=LuCI IoT Guard Dashboard
|
||||
LUCI_DESCRIPTION:=IoT device isolation and security monitoring interface
|
||||
LUCI_DEPENDS:=+secubox-iot-guard +luci-base
|
||||
LUCI_PKGARCH:=all
|
||||
|
||||
PKG_VERSION:=1.0.0
|
||||
PKG_RELEASE:=1
|
||||
PKG_MAINTAINER:=SecuBox Team
|
||||
PKG_LICENSE:=GPL-3.0
|
||||
|
||||
include $(TOPDIR)/feeds/luci/luci.mk
|
||||
|
||||
# call BuildPackage - OpenWrt buildance that translates the above into package rules
|
||||
$(eval $(call BuildPackage,luci-app-iot-guard))
|
||||
89
package/secubox/luci-app-iot-guard/README.md
Normal file
89
package/secubox/luci-app-iot-guard/README.md
Normal file
@ -0,0 +1,89 @@
|
||||
# LuCI IoT Guard
|
||||
|
||||
LuCI dashboard for IoT Guard device isolation and security monitoring.
|
||||
|
||||
## Features
|
||||
|
||||
- **Overview Dashboard** - Security score, device counts, risk distribution
|
||||
- **Device List** - Filterable table with device details
|
||||
- **Device Actions** - Isolate, trust, or block devices
|
||||
- **Cloud Mapping** - View cloud services each device contacts
|
||||
- **Anomaly Alerts** - Real-time anomaly notifications
|
||||
- **Policy Management** - Vendor classification rules
|
||||
- **Settings** - Configure auto-isolation, thresholds, zones
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
opkg install luci-app-iot-guard
|
||||
```
|
||||
|
||||
Requires `secubox-iot-guard` backend package.
|
||||
|
||||
## Menu Location
|
||||
|
||||
SecuBox > Services > IoT Guard
|
||||
|
||||
## Screens
|
||||
|
||||
### Overview (`/iot-guard/overview`)
|
||||
|
||||
Dashboard with:
|
||||
- Device count, isolated, blocked, high-risk stats
|
||||
- Security score (0-100%)
|
||||
- Device grid grouped by risk level
|
||||
- Recent anomaly events
|
||||
|
||||
### Devices (`/iot-guard/devices`)
|
||||
|
||||
Device management table:
|
||||
- MAC, IP, hostname, vendor, class, risk, score, zone, status
|
||||
- Click to view device detail modal with cloud deps and anomalies
|
||||
- Quick actions: Isolate, Trust, Block
|
||||
|
||||
### Policies (`/iot-guard/policies`)
|
||||
|
||||
Vendor classification rules:
|
||||
- View/add/delete vendor rules
|
||||
- Configure OUI prefix, pattern, class, risk level
|
||||
- Device class reference table
|
||||
|
||||
### Settings (`/iot-guard/settings`)
|
||||
|
||||
Configuration options:
|
||||
- Enable/disable service
|
||||
- Scan interval
|
||||
- Auto-isolation threshold
|
||||
- Anomaly detection sensitivity
|
||||
- Zone policy (block LAN, allow internet, bandwidth limit)
|
||||
- Allowlist/blocklist management
|
||||
|
||||
## RPCD Methods
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `status` | Dashboard stats |
|
||||
| `get_devices` | List devices (optional filter) |
|
||||
| `get_device` | Device detail with cloud map |
|
||||
| `get_anomalies` | Recent anomaly events |
|
||||
| `get_vendor_rules` | List classification rules |
|
||||
| `get_cloud_map` | Device cloud dependencies |
|
||||
| `scan` | Trigger network scan |
|
||||
| `isolate_device` | Move device to IoT zone |
|
||||
| `trust_device` | Add to allowlist |
|
||||
| `block_device` | Block device |
|
||||
| `add_vendor_rule` | Add classification rule |
|
||||
| `delete_vendor_rule` | Delete classification rule |
|
||||
|
||||
## Public Access
|
||||
|
||||
The overview and device list are available publicly via the `unauthenticated` ACL group.
|
||||
|
||||
## Dependencies
|
||||
|
||||
- secubox-iot-guard
|
||||
- luci-base
|
||||
|
||||
## License
|
||||
|
||||
GPL-3.0
|
||||
@ -0,0 +1,262 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require rpc';
|
||||
'require ui';
|
||||
|
||||
var callGetDevices = rpc.declare({
|
||||
object: 'luci.iot-guard',
|
||||
method: 'get_devices',
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callGetDevice = rpc.declare({
|
||||
object: 'luci.iot-guard',
|
||||
method: 'get_device',
|
||||
params: ['mac'],
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callIsolateDevice = rpc.declare({
|
||||
object: 'luci.iot-guard',
|
||||
method: 'isolate_device',
|
||||
params: ['mac'],
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callTrustDevice = rpc.declare({
|
||||
object: 'luci.iot-guard',
|
||||
method: 'trust_device',
|
||||
params: ['mac'],
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callBlockDevice = rpc.declare({
|
||||
object: 'luci.iot-guard',
|
||||
method: 'block_device',
|
||||
params: ['mac'],
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callScan = rpc.declare({
|
||||
object: 'luci.iot-guard',
|
||||
method: 'scan',
|
||||
expect: {}
|
||||
});
|
||||
|
||||
// Risk level colors
|
||||
var RISK_COLORS = {
|
||||
high: '#ff4444',
|
||||
medium: '#ffaa00',
|
||||
low: '#44cc44',
|
||||
unknown: '#888888'
|
||||
};
|
||||
|
||||
return view.extend({
|
||||
handleScan: function() {
|
||||
return callScan().then(function() {
|
||||
ui.addNotification(null, E('p', 'Network scan started'), 'info');
|
||||
window.setTimeout(function() { window.location.reload(); }, 3000);
|
||||
});
|
||||
},
|
||||
|
||||
handleIsolate: function(mac) {
|
||||
return callIsolateDevice(mac).then(function() {
|
||||
ui.addNotification(null, E('p', 'Device isolated: ' + mac), 'success');
|
||||
window.location.reload();
|
||||
});
|
||||
},
|
||||
|
||||
handleTrust: function(mac) {
|
||||
return callTrustDevice(mac).then(function() {
|
||||
ui.addNotification(null, E('p', 'Device trusted: ' + mac), 'success');
|
||||
window.location.reload();
|
||||
});
|
||||
},
|
||||
|
||||
handleBlock: function(mac) {
|
||||
if (!confirm('Block device ' + mac + '? This will prevent all network access.')) return;
|
||||
return callBlockDevice(mac).then(function() {
|
||||
ui.addNotification(null, E('p', 'Device blocked: ' + mac), 'success');
|
||||
window.location.reload();
|
||||
});
|
||||
},
|
||||
|
||||
handleShowDetail: function(mac) {
|
||||
var self = this;
|
||||
callGetDevice(mac).then(function(device) {
|
||||
self.showDeviceModal(device);
|
||||
});
|
||||
},
|
||||
|
||||
showDeviceModal: function(device) {
|
||||
var self = this;
|
||||
var riskColor = RISK_COLORS[device.risk_level] || RISK_COLORS.unknown;
|
||||
|
||||
var cloudList = (device.cloud_deps || []).map(function(c) {
|
||||
return E('li', { 'style': 'margin: 5px 0;' }, [
|
||||
E('span', {}, c.domain),
|
||||
E('span', { 'style': 'color: #888; margin-left: 10px;' }, '(' + c.query_count + ' queries)')
|
||||
]);
|
||||
});
|
||||
|
||||
var anomalyList = (device.anomalies || []).map(function(a) {
|
||||
var sevColor = a.severity === 'high' ? '#ff4444' : (a.severity === 'medium' ? '#ffaa00' : '#888');
|
||||
return E('li', { 'style': 'margin: 5px 0;' }, [
|
||||
E('span', { 'style': 'color: ' + sevColor + ';' }, '[' + a.severity + '] '),
|
||||
E('span', {}, a.type + ': ' + a.description)
|
||||
]);
|
||||
});
|
||||
|
||||
var content = E('div', { 'style': 'max-width: 600px;' }, [
|
||||
E('table', { 'style': 'width: 100%;' }, [
|
||||
E('tr', {}, [
|
||||
E('td', { 'style': 'padding: 5px; font-weight: bold;' }, 'MAC'),
|
||||
E('td', { 'style': 'padding: 5px;' }, device.mac)
|
||||
]),
|
||||
E('tr', {}, [
|
||||
E('td', { 'style': 'padding: 5px; font-weight: bold;' }, 'IP'),
|
||||
E('td', { 'style': 'padding: 5px;' }, device.ip || '-')
|
||||
]),
|
||||
E('tr', {}, [
|
||||
E('td', { 'style': 'padding: 5px; font-weight: bold;' }, 'Hostname'),
|
||||
E('td', { 'style': 'padding: 5px;' }, device.hostname || '-')
|
||||
]),
|
||||
E('tr', {}, [
|
||||
E('td', { 'style': 'padding: 5px; font-weight: bold;' }, 'Vendor'),
|
||||
E('td', { 'style': 'padding: 5px;' }, device.vendor || '-')
|
||||
]),
|
||||
E('tr', {}, [
|
||||
E('td', { 'style': 'padding: 5px; font-weight: bold;' }, 'Class'),
|
||||
E('td', { 'style': 'padding: 5px;' }, device.device_class)
|
||||
]),
|
||||
E('tr', {}, [
|
||||
E('td', { 'style': 'padding: 5px; font-weight: bold;' }, 'Risk Level'),
|
||||
E('td', { 'style': 'padding: 5px; color: ' + riskColor + ';' }, device.risk_level + ' (' + device.risk_score + ')')
|
||||
]),
|
||||
E('tr', {}, [
|
||||
E('td', { 'style': 'padding: 5px; font-weight: bold;' }, 'Zone'),
|
||||
E('td', { 'style': 'padding: 5px;' }, device.zone)
|
||||
]),
|
||||
E('tr', {}, [
|
||||
E('td', { 'style': 'padding: 5px; font-weight: bold;' }, 'Status'),
|
||||
E('td', { 'style': 'padding: 5px;' },
|
||||
device.blocked ? 'Blocked' :
|
||||
device.isolated ? 'Isolated' :
|
||||
device.trusted ? 'Trusted' : 'Active')
|
||||
]),
|
||||
E('tr', {}, [
|
||||
E('td', { 'style': 'padding: 5px; font-weight: bold;' }, 'First Seen'),
|
||||
E('td', { 'style': 'padding: 5px;' }, device.first_seen || '-')
|
||||
]),
|
||||
E('tr', {}, [
|
||||
E('td', { 'style': 'padding: 5px; font-weight: bold;' }, 'Last Seen'),
|
||||
E('td', { 'style': 'padding: 5px;' }, device.last_seen || '-')
|
||||
])
|
||||
]),
|
||||
|
||||
cloudList.length > 0 ? E('div', { 'style': 'margin-top: 20px;' }, [
|
||||
E('h4', {}, 'Cloud Services (' + cloudList.length + ')'),
|
||||
E('ul', { 'style': 'max-height: 150px; overflow-y: auto;' }, cloudList)
|
||||
]) : '',
|
||||
|
||||
anomalyList.length > 0 ? E('div', { 'style': 'margin-top: 20px;' }, [
|
||||
E('h4', {}, 'Recent Anomalies'),
|
||||
E('ul', {}, anomalyList)
|
||||
]) : '',
|
||||
|
||||
E('div', { 'style': 'margin-top: 20px; display: flex; gap: 10px;' }, [
|
||||
!device.isolated && !device.blocked ?
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-action',
|
||||
'click': function() { ui.hideModal(); self.handleIsolate(device.mac); }
|
||||
}, 'Isolate') : '',
|
||||
!device.trusted && !device.blocked ?
|
||||
E('button', {
|
||||
'class': 'cbi-button',
|
||||
'click': function() { ui.hideModal(); self.handleTrust(device.mac); }
|
||||
}, 'Trust') : '',
|
||||
!device.blocked ?
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-negative',
|
||||
'click': function() { ui.hideModal(); self.handleBlock(device.mac); }
|
||||
}, 'Block') : ''
|
||||
])
|
||||
]);
|
||||
|
||||
ui.showModal('Device: ' + (device.hostname || device.mac), [
|
||||
content,
|
||||
E('div', { 'class': 'right', 'style': 'margin-top: 20px;' }, [
|
||||
E('button', { 'class': 'cbi-button', 'click': ui.hideModal }, 'Close')
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
load: function() {
|
||||
return callGetDevices();
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
var devices = (data && data.devices) || [];
|
||||
var self = this;
|
||||
|
||||
var rows = devices.map(function(d) {
|
||||
var riskColor = RISK_COLORS[d.risk_level] || RISK_COLORS.unknown;
|
||||
var status = d.blocked ? 'Blocked' : (d.isolated ? 'Isolated' : (d.trusted ? 'Trusted' : 'Active'));
|
||||
var statusColor = d.blocked ? '#ff4444' : (d.isolated ? '#ffaa00' : (d.trusted ? '#44cc44' : '#888'));
|
||||
|
||||
return E('tr', { 'style': 'cursor: pointer;', 'click': function() { self.handleShowDetail(d.mac); } }, [
|
||||
E('td', { 'style': 'padding: 10px; border-bottom: 1px solid #333;' }, d.mac),
|
||||
E('td', { 'style': 'padding: 10px; border-bottom: 1px solid #333;' }, d.ip || '-'),
|
||||
E('td', { 'style': 'padding: 10px; border-bottom: 1px solid #333;' }, d.hostname || '-'),
|
||||
E('td', { 'style': 'padding: 10px; border-bottom: 1px solid #333;' }, d.vendor ? (d.vendor.length > 25 ? d.vendor.substring(0, 22) + '...' : d.vendor) : '-'),
|
||||
E('td', { 'style': 'padding: 10px; border-bottom: 1px solid #333;' }, d.device_class),
|
||||
E('td', { 'style': 'padding: 10px; border-bottom: 1px solid #333; color: ' + riskColor + ';' }, d.risk_level),
|
||||
E('td', { 'style': 'padding: 10px; border-bottom: 1px solid #333; text-align: center;' }, d.risk_score),
|
||||
E('td', { 'style': 'padding: 10px; border-bottom: 1px solid #333;' }, d.zone),
|
||||
E('td', { 'style': 'padding: 10px; border-bottom: 1px solid #333; color: ' + statusColor + ';' }, status),
|
||||
E('td', { 'style': 'padding: 10px; border-bottom: 1px solid #333;' }, [
|
||||
!d.isolated && !d.blocked ? E('button', {
|
||||
'class': 'cbi-button cbi-button-action btn-sm',
|
||||
'style': 'padding: 2px 8px; font-size: 12px; margin-right: 5px;',
|
||||
'click': function(ev) { ev.stopPropagation(); self.handleIsolate(d.mac); }
|
||||
}, 'Isolate') : '',
|
||||
!d.blocked ? E('button', {
|
||||
'class': 'cbi-button cbi-button-negative btn-sm',
|
||||
'style': 'padding: 2px 8px; font-size: 12px;',
|
||||
'click': function(ev) { ev.stopPropagation(); self.handleBlock(d.mac); }
|
||||
}, 'Block') : ''
|
||||
])
|
||||
]);
|
||||
});
|
||||
|
||||
return E('div', { 'class': 'cbi-map', 'style': 'padding: 20px;' }, [
|
||||
E('h2', {}, 'IoT Devices'),
|
||||
E('div', { 'style': 'margin-bottom: 20px;' }, [
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-action',
|
||||
'click': L.bind(this.handleScan, this)
|
||||
}, 'Scan Network'),
|
||||
E('span', { 'style': 'margin-left: 15px; color: #888;' }, devices.length + ' devices')
|
||||
]),
|
||||
|
||||
devices.length === 0 ?
|
||||
E('div', { 'style': 'padding: 30px; text-align: center; color: #888;' },
|
||||
'No devices found. Click "Scan Network" to discover IoT devices.') :
|
||||
E('table', { 'style': 'width: 100%; border-collapse: collapse;' }, [
|
||||
E('thead', {}, E('tr', { 'style': 'background: #222;' }, [
|
||||
E('th', { 'style': 'padding: 10px; text-align: left;' }, 'MAC'),
|
||||
E('th', { 'style': 'padding: 10px; text-align: left;' }, 'IP'),
|
||||
E('th', { 'style': 'padding: 10px; text-align: left;' }, 'Hostname'),
|
||||
E('th', { 'style': 'padding: 10px; text-align: left;' }, 'Vendor'),
|
||||
E('th', { 'style': 'padding: 10px; text-align: left;' }, 'Class'),
|
||||
E('th', { 'style': 'padding: 10px; text-align: left;' }, 'Risk'),
|
||||
E('th', { 'style': 'padding: 10px; text-align: center;' }, 'Score'),
|
||||
E('th', { 'style': 'padding: 10px; text-align: left;' }, 'Zone'),
|
||||
E('th', { 'style': 'padding: 10px; text-align: left;' }, 'Status'),
|
||||
E('th', { 'style': 'padding: 10px; text-align: left;' }, 'Actions')
|
||||
])),
|
||||
E('tbody', {}, rows)
|
||||
])
|
||||
]);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,257 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require rpc';
|
||||
'require poll';
|
||||
|
||||
var callStatus = rpc.declare({
|
||||
object: 'luci.iot-guard',
|
||||
method: 'status',
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callGetDevices = rpc.declare({
|
||||
object: 'luci.iot-guard',
|
||||
method: 'get_devices',
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callGetAnomalies = rpc.declare({
|
||||
object: 'luci.iot-guard',
|
||||
method: 'get_anomalies',
|
||||
params: ['limit'],
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callScan = rpc.declare({
|
||||
object: 'luci.iot-guard',
|
||||
method: 'scan',
|
||||
expect: {}
|
||||
});
|
||||
|
||||
// Device class icons (emoji-free)
|
||||
var CLASS_ICONS = {
|
||||
camera: '[CAM]',
|
||||
thermostat: '[TMP]',
|
||||
lighting: '[LGT]',
|
||||
plug: '[PLG]',
|
||||
assistant: '[AST]',
|
||||
media: '[MED]',
|
||||
lock: '[LCK]',
|
||||
sensor: '[SNS]',
|
||||
diy: '[DIY]',
|
||||
mixed: '[MIX]',
|
||||
unknown: '[???]'
|
||||
};
|
||||
|
||||
// Risk colors
|
||||
var RISK_COLORS = {
|
||||
high: '#ff4444',
|
||||
medium: '#ffaa00',
|
||||
low: '#44cc44',
|
||||
unknown: '#888888'
|
||||
};
|
||||
|
||||
return view.extend({
|
||||
handleScan: function() {
|
||||
return callScan().then(function() {
|
||||
window.location.reload();
|
||||
});
|
||||
},
|
||||
|
||||
load: function() {
|
||||
return Promise.all([
|
||||
callStatus(),
|
||||
callGetDevices(),
|
||||
callGetAnomalies(5)
|
||||
]);
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
var status = data[0] || {};
|
||||
var devices = (data[1] && data[1].devices) || [];
|
||||
var anomalies = (data[2] && data[2].anomalies) || [];
|
||||
|
||||
var view = E('div', { 'class': 'cbi-map', 'style': 'padding: 20px;' }, [
|
||||
// Header
|
||||
E('h2', { 'style': 'margin-bottom: 5px;' }, 'IoT Guard'),
|
||||
E('div', { 'style': 'color: #666; margin-bottom: 20px;' }, 'Device Isolation & Security Monitoring'),
|
||||
|
||||
// Status Cards Row
|
||||
E('div', { 'style': 'display: flex; flex-wrap: wrap; gap: 15px; margin-bottom: 25px;' }, [
|
||||
this.renderStatCard('Devices', status.total_devices || 0, '#4a9eff'),
|
||||
this.renderStatCard('Isolated', status.isolated || 0, '#ffaa00'),
|
||||
this.renderStatCard('Blocked', status.blocked || 0, '#ff4444'),
|
||||
this.renderStatCard('High Risk', status.high_risk || 0, '#ff4444'),
|
||||
this.renderStatCard('Anomalies', status.anomalies || 0, '#ff6600'),
|
||||
this.renderScoreCard('Security Score', status.security_score || 0)
|
||||
]),
|
||||
|
||||
// Action buttons
|
||||
E('div', { 'style': 'margin-bottom: 25px;' }, [
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-action',
|
||||
'click': L.bind(this.handleScan, this)
|
||||
}, 'Scan Network'),
|
||||
E('a', {
|
||||
'href': L.url('admin/secubox/services/iot-guard/devices'),
|
||||
'class': 'cbi-button',
|
||||
'style': 'margin-left: 10px;'
|
||||
}, 'View All Devices')
|
||||
]),
|
||||
|
||||
// Device Grid by Risk
|
||||
E('h3', { 'style': 'margin-top: 25px; margin-bottom: 15px;' }, 'Devices by Risk Level'),
|
||||
this.renderDeviceGrid(devices),
|
||||
|
||||
// Recent Anomalies
|
||||
E('h3', { 'style': 'margin-top: 30px; margin-bottom: 15px;' }, 'Recent Anomalies'),
|
||||
this.renderAnomaliesTable(anomalies)
|
||||
]);
|
||||
|
||||
// Poll for updates
|
||||
poll.add(L.bind(function() {
|
||||
return callStatus().then(L.bind(function(status) {
|
||||
this.updateStats(status);
|
||||
}, this));
|
||||
}, this), 10);
|
||||
|
||||
return view;
|
||||
},
|
||||
|
||||
renderStatCard: function(label, value, color) {
|
||||
return E('div', {
|
||||
'style': 'background: linear-gradient(135deg, ' + color + '22, ' + color + '11); ' +
|
||||
'border: 1px solid ' + color + '44; border-radius: 8px; padding: 15px 20px; ' +
|
||||
'min-width: 120px; text-align: center;'
|
||||
}, [
|
||||
E('div', {
|
||||
'style': 'font-size: 28px; font-weight: bold; color: ' + color + ';',
|
||||
'data-stat': label.toLowerCase().replace(' ', '-')
|
||||
}, String(value)),
|
||||
E('div', { 'style': 'color: #666; font-size: 12px; margin-top: 5px;' }, label)
|
||||
]);
|
||||
},
|
||||
|
||||
renderScoreCard: function(label, score) {
|
||||
var color = score >= 70 ? '#44cc44' : (score >= 40 ? '#ffaa00' : '#ff4444');
|
||||
return E('div', {
|
||||
'style': 'background: linear-gradient(135deg, ' + color + '22, ' + color + '11); ' +
|
||||
'border: 1px solid ' + color + '44; border-radius: 8px; padding: 15px 20px; ' +
|
||||
'min-width: 140px; text-align: center;'
|
||||
}, [
|
||||
E('div', {
|
||||
'style': 'font-size: 28px; font-weight: bold; color: ' + color + ';',
|
||||
'data-stat': 'security-score'
|
||||
}, score + '%'),
|
||||
E('div', { 'style': 'color: #666; font-size: 12px; margin-top: 5px;' }, label)
|
||||
]);
|
||||
},
|
||||
|
||||
renderDeviceGrid: function(devices) {
|
||||
var groups = { high: [], medium: [], low: [], unknown: [] };
|
||||
|
||||
devices.forEach(function(d) {
|
||||
var risk = d.risk_level || 'unknown';
|
||||
if (groups[risk]) {
|
||||
groups[risk].push(d);
|
||||
} else {
|
||||
groups.unknown.push(d);
|
||||
}
|
||||
});
|
||||
|
||||
var rows = [];
|
||||
|
||||
['high', 'medium', 'low'].forEach(function(risk) {
|
||||
if (groups[risk].length === 0) return;
|
||||
|
||||
rows.push(E('div', { 'style': 'margin-bottom: 15px;' }, [
|
||||
E('div', {
|
||||
'style': 'color: ' + RISK_COLORS[risk] + '; font-weight: bold; margin-bottom: 8px; text-transform: uppercase;'
|
||||
}, risk + ' Risk (' + groups[risk].length + ')'),
|
||||
E('div', { 'style': 'display: flex; flex-wrap: wrap; gap: 10px;' },
|
||||
groups[risk].slice(0, 12).map(function(d) {
|
||||
return this.renderDeviceChip(d);
|
||||
}, this)
|
||||
)
|
||||
]));
|
||||
}, this);
|
||||
|
||||
if (rows.length === 0) {
|
||||
return E('div', { 'style': 'color: #888; padding: 20px; text-align: center;' },
|
||||
'No IoT devices detected. Click "Scan Network" to discover devices.');
|
||||
}
|
||||
|
||||
return E('div', {}, rows);
|
||||
},
|
||||
|
||||
renderDeviceChip: function(device) {
|
||||
var icon = CLASS_ICONS[device.device_class] || CLASS_ICONS.unknown;
|
||||
var color = RISK_COLORS[device.risk_level] || RISK_COLORS.unknown;
|
||||
|
||||
var statusBadge = '';
|
||||
if (device.isolated) statusBadge = ' [ISO]';
|
||||
else if (device.blocked) statusBadge = ' [BLK]';
|
||||
else if (device.trusted) statusBadge = ' [OK]';
|
||||
|
||||
var label = device.hostname || device.ip || device.mac.substring(9);
|
||||
|
||||
return E('a', {
|
||||
'href': L.url('admin/secubox/services/iot-guard/devices') + '?mac=' + encodeURIComponent(device.mac),
|
||||
'style': 'background: #1a1a2e; border: 1px solid ' + color + '66; border-radius: 6px; ' +
|
||||
'padding: 8px 12px; color: #eee; text-decoration: none; display: inline-flex; ' +
|
||||
'align-items: center; gap: 8px; font-size: 13px;',
|
||||
'title': device.vendor + ' - ' + device.mac
|
||||
}, [
|
||||
E('span', { 'style': 'color: ' + color + '; font-weight: bold;' }, icon),
|
||||
E('span', {}, label.length > 15 ? label.substring(0, 12) + '...' : label),
|
||||
statusBadge ? E('span', { 'style': 'color: #888; font-size: 11px;' }, statusBadge) : ''
|
||||
]);
|
||||
},
|
||||
|
||||
renderAnomaliesTable: function(anomalies) {
|
||||
if (!anomalies || anomalies.length === 0) {
|
||||
return E('div', {
|
||||
'style': 'background: #0a2a0a; border: 1px solid #2a4a2a; border-radius: 8px; ' +
|
||||
'padding: 20px; text-align: center; color: #4a8a4a;'
|
||||
}, 'No recent anomalies detected');
|
||||
}
|
||||
|
||||
var rows = anomalies.map(function(a) {
|
||||
var sevColor = a.severity === 'high' ? '#ff4444' : (a.severity === 'medium' ? '#ffaa00' : '#888');
|
||||
return E('tr', {}, [
|
||||
E('td', { 'style': 'padding: 8px; border-bottom: 1px solid #333;' }, a.timestamp ? a.timestamp.substring(11, 16) : '-'),
|
||||
E('td', { 'style': 'padding: 8px; border-bottom: 1px solid #333;' }, a.hostname || a.mac),
|
||||
E('td', { 'style': 'padding: 8px; border-bottom: 1px solid #333;' }, a.type),
|
||||
E('td', { 'style': 'padding: 8px; border-bottom: 1px solid #333; color: ' + sevColor + ';' }, a.severity),
|
||||
E('td', { 'style': 'padding: 8px; border-bottom: 1px solid #333; color: #888;' }, a.description)
|
||||
]);
|
||||
});
|
||||
|
||||
return E('table', { 'style': 'width: 100%; border-collapse: collapse;' }, [
|
||||
E('thead', {}, E('tr', { 'style': 'background: #222;' }, [
|
||||
E('th', { 'style': 'padding: 10px; text-align: left;' }, 'Time'),
|
||||
E('th', { 'style': 'padding: 10px; text-align: left;' }, 'Device'),
|
||||
E('th', { 'style': 'padding: 10px; text-align: left;' }, 'Type'),
|
||||
E('th', { 'style': 'padding: 10px; text-align: left;' }, 'Severity'),
|
||||
E('th', { 'style': 'padding: 10px; text-align: left;' }, 'Description')
|
||||
])),
|
||||
E('tbody', {}, rows)
|
||||
]);
|
||||
},
|
||||
|
||||
updateStats: function(status) {
|
||||
var updates = {
|
||||
'devices': status.total_devices,
|
||||
'isolated': status.isolated,
|
||||
'blocked': status.blocked,
|
||||
'high-risk': status.high_risk,
|
||||
'anomalies': status.anomalies,
|
||||
'security-score': status.security_score + '%'
|
||||
};
|
||||
|
||||
Object.keys(updates).forEach(function(key) {
|
||||
var el = document.querySelector('[data-stat="' + key + '"]');
|
||||
if (el) el.textContent = String(updates[key]);
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,199 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require rpc';
|
||||
'require ui';
|
||||
|
||||
var callGetVendorRules = rpc.declare({
|
||||
object: 'luci.iot-guard',
|
||||
method: 'get_vendor_rules',
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callAddVendorRule = rpc.declare({
|
||||
object: 'luci.iot-guard',
|
||||
method: 'add_vendor_rule',
|
||||
params: ['name', 'pattern', 'oui', 'class', 'risk', 'auto_isolate'],
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var callDeleteVendorRule = rpc.declare({
|
||||
object: 'luci.iot-guard',
|
||||
method: 'delete_vendor_rule',
|
||||
params: ['name'],
|
||||
expect: {}
|
||||
});
|
||||
|
||||
var RISK_COLORS = {
|
||||
high: '#ff4444',
|
||||
medium: '#ffaa00',
|
||||
low: '#44cc44'
|
||||
};
|
||||
|
||||
var DEVICE_CLASSES = ['camera', 'thermostat', 'lighting', 'plug', 'assistant', 'media', 'lock', 'sensor', 'diy', 'mixed'];
|
||||
var RISK_LEVELS = ['low', 'medium', 'high'];
|
||||
|
||||
return view.extend({
|
||||
handleAddRule: function() {
|
||||
var self = this;
|
||||
|
||||
var form = E('div', { 'style': 'min-width: 400px;' }, [
|
||||
E('div', { 'style': 'margin-bottom: 15px;' }, [
|
||||
E('label', { 'style': 'display: block; margin-bottom: 5px;' }, 'Rule Name'),
|
||||
E('input', { 'type': 'text', 'id': 'rule-name', 'style': 'width: 100%; padding: 8px;' })
|
||||
]),
|
||||
E('div', { 'style': 'margin-bottom: 15px;' }, [
|
||||
E('label', { 'style': 'display: block; margin-bottom: 5px;' }, 'Vendor Pattern (regex)'),
|
||||
E('input', { 'type': 'text', 'id': 'rule-pattern', 'style': 'width: 100%; padding: 8px;', 'placeholder': 'Ring|Amazon Ring' })
|
||||
]),
|
||||
E('div', { 'style': 'margin-bottom: 15px;' }, [
|
||||
E('label', { 'style': 'display: block; margin-bottom: 5px;' }, 'OUI Prefix'),
|
||||
E('input', { 'type': 'text', 'id': 'rule-oui', 'style': 'width: 100%; padding: 8px;', 'placeholder': '40:B4:CD' })
|
||||
]),
|
||||
E('div', { 'style': 'margin-bottom: 15px;' }, [
|
||||
E('label', { 'style': 'display: block; margin-bottom: 5px;' }, 'Device Class'),
|
||||
E('select', { 'id': 'rule-class', 'style': 'width: 100%; padding: 8px;' },
|
||||
DEVICE_CLASSES.map(function(c) { return E('option', { 'value': c }, c); })
|
||||
)
|
||||
]),
|
||||
E('div', { 'style': 'margin-bottom: 15px;' }, [
|
||||
E('label', { 'style': 'display: block; margin-bottom: 5px;' }, 'Risk Level'),
|
||||
E('select', { 'id': 'rule-risk', 'style': 'width: 100%; padding: 8px;' },
|
||||
RISK_LEVELS.map(function(r) { return E('option', { 'value': r }, r); })
|
||||
)
|
||||
]),
|
||||
E('div', { 'style': 'margin-bottom: 15px;' }, [
|
||||
E('label', {}, [
|
||||
E('input', { 'type': 'checkbox', 'id': 'rule-auto-isolate', 'style': 'margin-right: 8px;' }),
|
||||
'Auto-isolate matching devices'
|
||||
])
|
||||
])
|
||||
]);
|
||||
|
||||
ui.showModal('Add Vendor Rule', [
|
||||
form,
|
||||
E('div', { 'class': 'right', 'style': 'margin-top: 20px;' }, [
|
||||
E('button', { 'class': 'cbi-button', 'click': ui.hideModal }, 'Cancel'),
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-action',
|
||||
'style': 'margin-left: 10px;',
|
||||
'click': function() {
|
||||
var name = document.getElementById('rule-name').value.trim().replace(/[^a-z0-9_]/gi, '_');
|
||||
var pattern = document.getElementById('rule-pattern').value.trim();
|
||||
var oui = document.getElementById('rule-oui').value.trim();
|
||||
var deviceClass = document.getElementById('rule-class').value;
|
||||
var risk = document.getElementById('rule-risk').value;
|
||||
var autoIsolate = document.getElementById('rule-auto-isolate').checked ? '1' : '0';
|
||||
|
||||
if (!name) {
|
||||
ui.addNotification(null, E('p', 'Rule name is required'), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
ui.hideModal();
|
||||
callAddVendorRule(name, pattern, oui, deviceClass, risk, autoIsolate).then(function() {
|
||||
ui.addNotification(null, E('p', 'Rule added: ' + name), 'success');
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
}, 'Add Rule')
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
handleDeleteRule: function(name) {
|
||||
if (!confirm('Delete rule "' + name + '"?')) return;
|
||||
callDeleteVendorRule(name).then(function() {
|
||||
ui.addNotification(null, E('p', 'Rule deleted'), 'success');
|
||||
window.location.reload();
|
||||
});
|
||||
},
|
||||
|
||||
load: function() {
|
||||
return callGetVendorRules();
|
||||
},
|
||||
|
||||
render: function(data) {
|
||||
var rules = (data && data.rules) || [];
|
||||
var self = this;
|
||||
|
||||
var rows = rules.map(function(r) {
|
||||
var riskColor = RISK_COLORS[r.risk_level] || '#888';
|
||||
return E('tr', {}, [
|
||||
E('td', { 'style': 'padding: 10px; border-bottom: 1px solid #333;' }, r.name),
|
||||
E('td', { 'style': 'padding: 10px; border-bottom: 1px solid #333; font-family: monospace;' }, r.pattern || '-'),
|
||||
E('td', { 'style': 'padding: 10px; border-bottom: 1px solid #333; font-family: monospace;' }, r.oui_prefix || '-'),
|
||||
E('td', { 'style': 'padding: 10px; border-bottom: 1px solid #333;' }, r.device_class),
|
||||
E('td', { 'style': 'padding: 10px; border-bottom: 1px solid #333; color: ' + riskColor + ';' }, r.risk_level),
|
||||
E('td', { 'style': 'padding: 10px; border-bottom: 1px solid #333;' }, r.auto_isolate ? 'Yes' : 'No'),
|
||||
E('td', { 'style': 'padding: 10px; border-bottom: 1px solid #333;' }, [
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-negative btn-sm',
|
||||
'style': 'padding: 2px 8px; font-size: 12px;',
|
||||
'click': function() { self.handleDeleteRule(r.name); }
|
||||
}, 'Delete')
|
||||
])
|
||||
]);
|
||||
});
|
||||
|
||||
return E('div', { 'class': 'cbi-map', 'style': 'padding: 20px;' }, [
|
||||
E('h2', {}, 'IoT Guard Policies'),
|
||||
E('p', { 'style': 'color: #888; margin-bottom: 20px;' },
|
||||
'Vendor classification rules determine how devices are identified and their default risk level.'),
|
||||
|
||||
E('div', { 'style': 'margin-bottom: 20px;' }, [
|
||||
E('button', {
|
||||
'class': 'cbi-button cbi-button-action',
|
||||
'click': L.bind(this.handleAddRule, this)
|
||||
}, 'Add Vendor Rule')
|
||||
]),
|
||||
|
||||
E('h3', { 'style': 'margin-top: 25px;' }, 'Vendor Classification Rules'),
|
||||
|
||||
rules.length === 0 ?
|
||||
E('div', { 'style': 'padding: 20px; color: #888; text-align: center;' },
|
||||
'No custom vendor rules defined. Using built-in classification.') :
|
||||
E('table', { 'style': 'width: 100%; border-collapse: collapse;' }, [
|
||||
E('thead', {}, E('tr', { 'style': 'background: #222;' }, [
|
||||
E('th', { 'style': 'padding: 10px; text-align: left;' }, 'Name'),
|
||||
E('th', { 'style': 'padding: 10px; text-align: left;' }, 'Vendor Pattern'),
|
||||
E('th', { 'style': 'padding: 10px; text-align: left;' }, 'OUI Prefix'),
|
||||
E('th', { 'style': 'padding: 10px; text-align: left;' }, 'Class'),
|
||||
E('th', { 'style': 'padding: 10px; text-align: left;' }, 'Risk'),
|
||||
E('th', { 'style': 'padding: 10px; text-align: left;' }, 'Auto-Isolate'),
|
||||
E('th', { 'style': 'padding: 10px; text-align: left;' }, 'Actions')
|
||||
])),
|
||||
E('tbody', {}, rows)
|
||||
]),
|
||||
|
||||
E('h3', { 'style': 'margin-top: 30px;' }, 'Device Classes'),
|
||||
E('table', { 'style': 'width: 100%; border-collapse: collapse; margin-top: 10px;' }, [
|
||||
E('thead', {}, E('tr', { 'style': 'background: #222;' }, [
|
||||
E('th', { 'style': 'padding: 8px; text-align: left;' }, 'Class'),
|
||||
E('th', { 'style': 'padding: 8px; text-align: left;' }, 'Description'),
|
||||
E('th', { 'style': 'padding: 8px; text-align: left;' }, 'Default Risk')
|
||||
])),
|
||||
E('tbody', {}, [
|
||||
this.renderClassRow('camera', 'IP cameras, video doorbells', 'medium'),
|
||||
this.renderClassRow('thermostat', 'Smart thermostats, HVAC', 'low'),
|
||||
this.renderClassRow('lighting', 'Smart bulbs, LED strips', 'low'),
|
||||
this.renderClassRow('plug', 'Smart plugs, outlets', 'medium'),
|
||||
this.renderClassRow('assistant', 'Voice assistants', 'medium'),
|
||||
this.renderClassRow('media', 'TVs, streaming devices', 'medium'),
|
||||
this.renderClassRow('lock', 'Smart locks', 'high'),
|
||||
this.renderClassRow('sensor', 'Motion, door sensors', 'low'),
|
||||
this.renderClassRow('diy', 'ESP32, Raspberry Pi', 'high'),
|
||||
this.renderClassRow('mixed', 'Multi-function devices', 'high')
|
||||
])
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
renderClassRow: function(name, desc, risk) {
|
||||
var color = RISK_COLORS[risk] || '#888';
|
||||
return E('tr', {}, [
|
||||
E('td', { 'style': 'padding: 8px; border-bottom: 1px solid #333;' }, name),
|
||||
E('td', { 'style': 'padding: 8px; border-bottom: 1px solid #333; color: #888;' }, desc),
|
||||
E('td', { 'style': 'padding: 8px; border-bottom: 1px solid #333; color: ' + color + ';' }, risk)
|
||||
]);
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,102 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require form';
|
||||
'require uci';
|
||||
|
||||
return view.extend({
|
||||
load: function() {
|
||||
return uci.load('iot-guard');
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var m, s, o;
|
||||
|
||||
m = new form.Map('iot-guard', 'IoT Guard Settings',
|
||||
'Configure IoT device isolation and security monitoring settings.');
|
||||
|
||||
// Main settings
|
||||
s = m.section(form.TypedSection, 'iot-guard', 'General Settings');
|
||||
s.anonymous = true;
|
||||
|
||||
o = s.option(form.Flag, 'enabled', 'Enable IoT Guard');
|
||||
o.default = '1';
|
||||
o.rmempty = false;
|
||||
|
||||
o = s.option(form.Value, 'scan_interval', 'Scan Interval (seconds)');
|
||||
o.datatype = 'uinteger';
|
||||
o.default = '300';
|
||||
o.placeholder = '300';
|
||||
|
||||
o = s.option(form.Flag, 'auto_isolate', 'Auto-Isolate High-Risk Devices',
|
||||
'Automatically move high-risk IoT devices to the isolated zone.');
|
||||
o.default = '1';
|
||||
|
||||
o = s.option(form.Value, 'auto_isolate_threshold', 'Auto-Isolate Threshold',
|
||||
'Risk score threshold (0-100) for automatic isolation.');
|
||||
o.datatype = 'range(0,100)';
|
||||
o.default = '80';
|
||||
o.placeholder = '80';
|
||||
o.depends('auto_isolate', '1');
|
||||
|
||||
o = s.option(form.Flag, 'anomaly_detection', 'Enable Anomaly Detection',
|
||||
'Monitor traffic patterns and detect behavioral anomalies.');
|
||||
o.default = '1';
|
||||
|
||||
o = s.option(form.ListValue, 'anomaly_sensitivity', 'Anomaly Sensitivity');
|
||||
o.value('low', 'Low (fewer alerts)');
|
||||
o.value('medium', 'Medium');
|
||||
o.value('high', 'High (more alerts)');
|
||||
o.default = 'medium';
|
||||
o.depends('anomaly_detection', '1');
|
||||
|
||||
o = s.option(form.ListValue, 'log_level', 'Log Level');
|
||||
o.value('debug', 'Debug');
|
||||
o.value('info', 'Info');
|
||||
o.value('warn', 'Warning');
|
||||
o.value('error', 'Error');
|
||||
o.default = 'info';
|
||||
|
||||
// Zone policy
|
||||
s = m.section(form.TypedSection, 'zone_policy', 'IoT Zone Policy');
|
||||
s.anonymous = true;
|
||||
|
||||
o = s.option(form.Value, 'target_zone', 'Target Zone',
|
||||
'Network zone for isolated IoT devices.');
|
||||
o.default = 'iot';
|
||||
o.placeholder = 'iot';
|
||||
|
||||
o = s.option(form.Flag, 'block_lan', 'Block LAN Access',
|
||||
'Prevent isolated devices from accessing other LAN devices.');
|
||||
o.default = '1';
|
||||
|
||||
o = s.option(form.Flag, 'allow_internet', 'Allow Internet Access',
|
||||
'Allow isolated devices to access the internet.');
|
||||
o.default = '1';
|
||||
|
||||
o = s.option(form.Value, 'bandwidth_limit', 'Bandwidth Limit (Mbps)',
|
||||
'Rate limit for isolated IoT devices. 0 = unlimited.');
|
||||
o.datatype = 'uinteger';
|
||||
o.default = '10';
|
||||
o.placeholder = '10';
|
||||
|
||||
// Allowlist
|
||||
s = m.section(form.TypedSection, 'allowlist', 'Trusted Devices (Allowlist)');
|
||||
s.anonymous = true;
|
||||
|
||||
o = s.option(form.DynamicList, 'mac', 'Trusted MAC Addresses',
|
||||
'Devices in this list will never be auto-isolated.');
|
||||
o.datatype = 'macaddr';
|
||||
o.placeholder = 'AA:BB:CC:DD:EE:FF';
|
||||
|
||||
// Blocklist
|
||||
s = m.section(form.TypedSection, 'blocklist', 'Blocked Devices');
|
||||
s.anonymous = true;
|
||||
|
||||
o = s.option(form.DynamicList, 'mac', 'Blocked MAC Addresses',
|
||||
'Devices in this list are completely blocked from network access.');
|
||||
o.datatype = 'macaddr';
|
||||
o.placeholder = 'AA:BB:CC:DD:EE:FF';
|
||||
|
||||
return m.render();
|
||||
}
|
||||
});
|
||||
@ -0,0 +1,435 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# RPCD handler for IoT Guard
|
||||
#
|
||||
# Provides ubus methods for LuCI frontend.
|
||||
#
|
||||
|
||||
. /lib/functions.sh
|
||||
. /usr/share/libubox/jshn.sh
|
||||
|
||||
DB_FILE="/var/lib/iot-guard/iot-guard.db"
|
||||
|
||||
# ============================================================================
|
||||
# Helper Functions
|
||||
# ============================================================================
|
||||
|
||||
init_db() {
|
||||
[ -d "/var/lib/iot-guard" ] || mkdir -p /var/lib/iot-guard
|
||||
[ -f "$DB_FILE" ] || /usr/sbin/iot-guardctl scan >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# RPC Methods
|
||||
# ============================================================================
|
||||
|
||||
method_status() {
|
||||
init_db
|
||||
|
||||
local total=$(sqlite3 "$DB_FILE" "SELECT COUNT(*) FROM devices;" 2>/dev/null || echo 0)
|
||||
local isolated=$(sqlite3 "$DB_FILE" "SELECT COUNT(*) FROM devices WHERE isolated=1;" 2>/dev/null || echo 0)
|
||||
local trusted=$(sqlite3 "$DB_FILE" "SELECT COUNT(*) FROM devices WHERE trusted=1;" 2>/dev/null || echo 0)
|
||||
local blocked=$(sqlite3 "$DB_FILE" "SELECT COUNT(*) FROM devices WHERE blocked=1;" 2>/dev/null || echo 0)
|
||||
local high_risk=$(sqlite3 "$DB_FILE" "SELECT COUNT(*) FROM devices WHERE risk_level='high';" 2>/dev/null || echo 0)
|
||||
local medium_risk=$(sqlite3 "$DB_FILE" "SELECT COUNT(*) FROM devices WHERE risk_level='medium';" 2>/dev/null || echo 0)
|
||||
local low_risk=$(sqlite3 "$DB_FILE" "SELECT COUNT(*) FROM devices WHERE risk_level='low';" 2>/dev/null || echo 0)
|
||||
local anomalies=$(sqlite3 "$DB_FILE" "SELECT COUNT(*) FROM anomalies WHERE resolved=0;" 2>/dev/null || echo 0)
|
||||
local avg_risk=$(sqlite3 "$DB_FILE" "SELECT COALESCE(CAST(AVG(risk_score) AS INTEGER), 0) FROM devices;" 2>/dev/null || echo 0)
|
||||
local security_score=$((100 - avg_risk))
|
||||
[ "$security_score" -lt 0 ] && security_score=0
|
||||
|
||||
# Get service status
|
||||
local enabled
|
||||
config_load iot-guard
|
||||
config_get_bool enabled main enabled 0
|
||||
|
||||
json_init
|
||||
json_add_int "total_devices" "$total"
|
||||
json_add_int "isolated" "$isolated"
|
||||
json_add_int "trusted" "$trusted"
|
||||
json_add_int "blocked" "$blocked"
|
||||
json_add_int "high_risk" "$high_risk"
|
||||
json_add_int "medium_risk" "$medium_risk"
|
||||
json_add_int "low_risk" "$low_risk"
|
||||
json_add_int "anomalies" "$anomalies"
|
||||
json_add_int "security_score" "$security_score"
|
||||
json_add_boolean "enabled" "$enabled"
|
||||
json_add_string "version" "1.0.0"
|
||||
json_dump
|
||||
}
|
||||
|
||||
method_get_devices() {
|
||||
init_db
|
||||
|
||||
local filter="${1:-}"
|
||||
|
||||
local where_clause=""
|
||||
case "$filter" in
|
||||
isolated) where_clause="WHERE isolated=1" ;;
|
||||
trusted) where_clause="WHERE trusted=1" ;;
|
||||
blocked) where_clause="WHERE blocked=1" ;;
|
||||
high) where_clause="WHERE risk_level='high'" ;;
|
||||
medium) where_clause="WHERE risk_level='medium'" ;;
|
||||
low) where_clause="WHERE risk_level='low'" ;;
|
||||
esac
|
||||
|
||||
json_init
|
||||
json_add_array "devices"
|
||||
|
||||
sqlite3 -separator '|' "$DB_FILE" \
|
||||
"SELECT mac, ip, hostname, vendor, device_class, risk_level, risk_score, zone, isolated, trusted, blocked, last_seen
|
||||
FROM devices $where_clause ORDER BY risk_score DESC;" 2>/dev/null | \
|
||||
while IFS='|' read -r mac ip hostname vendor class risk score zone isolated trusted blocked last_seen; do
|
||||
json_add_object ""
|
||||
json_add_string "mac" "$mac"
|
||||
json_add_string "ip" "$ip"
|
||||
json_add_string "hostname" "$hostname"
|
||||
json_add_string "vendor" "$vendor"
|
||||
json_add_string "device_class" "$class"
|
||||
json_add_string "risk_level" "$risk"
|
||||
json_add_int "risk_score" "$score"
|
||||
json_add_string "zone" "$zone"
|
||||
json_add_boolean "isolated" "$isolated"
|
||||
json_add_boolean "trusted" "$trusted"
|
||||
json_add_boolean "blocked" "$blocked"
|
||||
json_add_string "last_seen" "$last_seen"
|
||||
json_close_object
|
||||
done
|
||||
|
||||
json_close_array
|
||||
json_dump
|
||||
}
|
||||
|
||||
method_get_device() {
|
||||
local mac="$1"
|
||||
[ -z "$mac" ] && { echo '{"error":"MAC address required"}'; return; }
|
||||
|
||||
init_db
|
||||
mac=$(echo "$mac" | tr '[:lower:]' '[:upper:]')
|
||||
|
||||
local device=$(sqlite3 -separator '|' "$DB_FILE" \
|
||||
"SELECT mac, ip, hostname, vendor, device_class, risk_level, risk_score, zone, isolated, trusted, blocked, first_seen, last_seen
|
||||
FROM devices WHERE mac='$mac';" 2>/dev/null)
|
||||
|
||||
if [ -z "$device" ]; then
|
||||
echo '{"error":"Device not found"}'
|
||||
return
|
||||
fi
|
||||
|
||||
IFS='|' read -r d_mac d_ip d_hostname d_vendor d_class d_risk d_score d_zone d_isolated d_trusted d_blocked d_first d_last <<EOF
|
||||
$device
|
||||
EOF
|
||||
|
||||
json_init
|
||||
json_add_string "mac" "$d_mac"
|
||||
json_add_string "ip" "$d_ip"
|
||||
json_add_string "hostname" "$d_hostname"
|
||||
json_add_string "vendor" "$d_vendor"
|
||||
json_add_string "device_class" "$d_class"
|
||||
json_add_string "risk_level" "$d_risk"
|
||||
json_add_int "risk_score" "$d_score"
|
||||
json_add_string "zone" "$d_zone"
|
||||
json_add_boolean "isolated" "$d_isolated"
|
||||
json_add_boolean "trusted" "$d_trusted"
|
||||
json_add_boolean "blocked" "$d_blocked"
|
||||
json_add_string "first_seen" "$d_first"
|
||||
json_add_string "last_seen" "$d_last"
|
||||
|
||||
# Add cloud dependencies
|
||||
json_add_array "cloud_deps"
|
||||
sqlite3 -separator '|' "$DB_FILE" \
|
||||
"SELECT domain, query_count, last_seen FROM cloud_deps WHERE mac='$mac' ORDER BY query_count DESC LIMIT 20;" 2>/dev/null | \
|
||||
while IFS='|' read -r domain count last; do
|
||||
json_add_object ""
|
||||
json_add_string "domain" "$domain"
|
||||
json_add_int "query_count" "$count"
|
||||
json_add_string "last_seen" "$last"
|
||||
json_close_object
|
||||
done
|
||||
json_close_array
|
||||
|
||||
# Add recent anomalies
|
||||
json_add_array "anomalies"
|
||||
sqlite3 -separator '|' "$DB_FILE" \
|
||||
"SELECT timestamp, anomaly_type, severity, description FROM anomalies WHERE mac='$mac' ORDER BY timestamp DESC LIMIT 10;" 2>/dev/null | \
|
||||
while IFS='|' read -r ts atype sev desc; do
|
||||
json_add_object ""
|
||||
json_add_string "timestamp" "$ts"
|
||||
json_add_string "type" "$atype"
|
||||
json_add_string "severity" "$sev"
|
||||
json_add_string "description" "$desc"
|
||||
json_close_object
|
||||
done
|
||||
json_close_array
|
||||
|
||||
json_dump
|
||||
}
|
||||
|
||||
method_get_anomalies() {
|
||||
init_db
|
||||
|
||||
local limit="${1:-20}"
|
||||
|
||||
json_init
|
||||
json_add_array "anomalies"
|
||||
|
||||
sqlite3 -separator '|' "$DB_FILE" \
|
||||
"SELECT a.id, a.mac, d.hostname, d.device_class, a.timestamp, a.anomaly_type, a.severity, a.description, a.resolved
|
||||
FROM anomalies a
|
||||
LEFT JOIN devices d ON a.mac = d.mac
|
||||
ORDER BY a.timestamp DESC
|
||||
LIMIT $limit;" 2>/dev/null | \
|
||||
while IFS='|' read -r id mac hostname class ts atype sev desc resolved; do
|
||||
json_add_object ""
|
||||
json_add_int "id" "$id"
|
||||
json_add_string "mac" "$mac"
|
||||
json_add_string "hostname" "$hostname"
|
||||
json_add_string "device_class" "$class"
|
||||
json_add_string "timestamp" "$ts"
|
||||
json_add_string "type" "$atype"
|
||||
json_add_string "severity" "$sev"
|
||||
json_add_string "description" "$desc"
|
||||
json_add_boolean "resolved" "$resolved"
|
||||
json_close_object
|
||||
done
|
||||
|
||||
json_close_array
|
||||
json_dump
|
||||
}
|
||||
|
||||
method_get_vendor_rules() {
|
||||
json_init
|
||||
json_add_array "rules"
|
||||
|
||||
config_load iot-guard
|
||||
config_foreach _output_vendor_rule vendor_rule
|
||||
|
||||
json_close_array
|
||||
json_dump
|
||||
}
|
||||
|
||||
_output_vendor_rule() {
|
||||
local section="$1"
|
||||
local pattern oui_prefix device_class risk_level auto_isolate
|
||||
|
||||
config_get pattern "$section" vendor_pattern
|
||||
config_get oui_prefix "$section" oui_prefix
|
||||
config_get device_class "$section" device_class
|
||||
config_get risk_level "$section" risk_level
|
||||
config_get_bool auto_isolate "$section" auto_isolate 0
|
||||
|
||||
json_add_object ""
|
||||
json_add_string "name" "$section"
|
||||
json_add_string "pattern" "$pattern"
|
||||
json_add_string "oui_prefix" "$oui_prefix"
|
||||
json_add_string "device_class" "$device_class"
|
||||
json_add_string "risk_level" "$risk_level"
|
||||
json_add_boolean "auto_isolate" "$auto_isolate"
|
||||
json_close_object
|
||||
}
|
||||
|
||||
method_get_cloud_map() {
|
||||
local mac="$1"
|
||||
[ -z "$mac" ] && { echo '{"error":"MAC address required"}'; return; }
|
||||
|
||||
init_db
|
||||
mac=$(echo "$mac" | tr '[:lower:]' '[:upper:]')
|
||||
|
||||
json_init
|
||||
json_add_string "mac" "$mac"
|
||||
json_add_array "cloud_services"
|
||||
|
||||
sqlite3 -separator '|' "$DB_FILE" \
|
||||
"SELECT domain, query_count, first_seen, last_seen FROM cloud_deps WHERE mac='$mac' ORDER BY query_count DESC;" 2>/dev/null | \
|
||||
while IFS='|' read -r domain count first last; do
|
||||
json_add_object ""
|
||||
json_add_string "domain" "$domain"
|
||||
json_add_int "query_count" "$count"
|
||||
json_add_string "first_seen" "$first"
|
||||
json_add_string "last_seen" "$last"
|
||||
json_close_object
|
||||
done
|
||||
|
||||
json_close_array
|
||||
|
||||
local total=$(sqlite3 "$DB_FILE" "SELECT COUNT(DISTINCT domain) FROM cloud_deps WHERE mac='$mac';" 2>/dev/null || echo 0)
|
||||
json_add_int "total_services" "$total"
|
||||
|
||||
json_dump
|
||||
}
|
||||
|
||||
method_scan() {
|
||||
/usr/sbin/iot-guardctl scan >/dev/null 2>&1 &
|
||||
|
||||
json_init
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "Network scan started"
|
||||
json_dump
|
||||
}
|
||||
|
||||
method_isolate_device() {
|
||||
local mac="$1"
|
||||
[ -z "$mac" ] && { echo '{"error":"MAC address required"}'; return; }
|
||||
|
||||
mac=$(echo "$mac" | tr '[:lower:]' '[:upper:]')
|
||||
/usr/sbin/iot-guardctl isolate "$mac" >/dev/null 2>&1
|
||||
|
||||
json_init
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "Device isolated: $mac"
|
||||
json_dump
|
||||
}
|
||||
|
||||
method_trust_device() {
|
||||
local mac="$1"
|
||||
[ -z "$mac" ] && { echo '{"error":"MAC address required"}'; return; }
|
||||
|
||||
mac=$(echo "$mac" | tr '[:lower:]' '[:upper:]')
|
||||
/usr/sbin/iot-guardctl trust "$mac" >/dev/null 2>&1
|
||||
|
||||
json_init
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "Device trusted: $mac"
|
||||
json_dump
|
||||
}
|
||||
|
||||
method_block_device() {
|
||||
local mac="$1"
|
||||
[ -z "$mac" ] && { echo '{"error":"MAC address required"}'; return; }
|
||||
|
||||
mac=$(echo "$mac" | tr '[:lower:]' '[:upper:]')
|
||||
/usr/sbin/iot-guardctl block "$mac" >/dev/null 2>&1
|
||||
|
||||
json_init
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "Device blocked: $mac"
|
||||
json_dump
|
||||
}
|
||||
|
||||
method_add_vendor_rule() {
|
||||
local name="$1"
|
||||
local pattern="$2"
|
||||
local oui="$3"
|
||||
local class="$4"
|
||||
local risk="$5"
|
||||
local auto_isolate="$6"
|
||||
|
||||
[ -z "$name" ] && { echo '{"error":"Rule name required"}'; return; }
|
||||
|
||||
uci set "iot-guard.$name=vendor_rule"
|
||||
[ -n "$pattern" ] && uci set "iot-guard.$name.vendor_pattern=$pattern"
|
||||
[ -n "$oui" ] && uci set "iot-guard.$name.oui_prefix=$oui"
|
||||
[ -n "$class" ] && uci set "iot-guard.$name.device_class=$class"
|
||||
[ -n "$risk" ] && uci set "iot-guard.$name.risk_level=$risk"
|
||||
[ -n "$auto_isolate" ] && uci set "iot-guard.$name.auto_isolate=$auto_isolate"
|
||||
uci commit iot-guard
|
||||
|
||||
json_init
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "Vendor rule added: $name"
|
||||
json_dump
|
||||
}
|
||||
|
||||
method_delete_vendor_rule() {
|
||||
local name="$1"
|
||||
[ -z "$name" ] && { echo '{"error":"Rule name required"}'; return; }
|
||||
|
||||
uci delete "iot-guard.$name" 2>/dev/null
|
||||
uci commit iot-guard
|
||||
|
||||
json_init
|
||||
json_add_boolean "success" 1
|
||||
json_add_string "message" "Vendor rule deleted: $name"
|
||||
json_dump
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Main Entry Point
|
||||
# ============================================================================
|
||||
|
||||
case "$1" in
|
||||
list)
|
||||
cat <<'EOF'
|
||||
{
|
||||
"status": {},
|
||||
"get_devices": {"filter": "string"},
|
||||
"get_device": {"mac": "string"},
|
||||
"get_anomalies": {"limit": "int"},
|
||||
"get_vendor_rules": {},
|
||||
"get_cloud_map": {"mac": "string"},
|
||||
"scan": {},
|
||||
"isolate_device": {"mac": "string"},
|
||||
"trust_device": {"mac": "string"},
|
||||
"block_device": {"mac": "string"},
|
||||
"add_vendor_rule": {"name": "string", "pattern": "string", "oui": "string", "class": "string", "risk": "string", "auto_isolate": "bool"},
|
||||
"delete_vendor_rule": {"name": "string"}
|
||||
}
|
||||
EOF
|
||||
;;
|
||||
call)
|
||||
case "$2" in
|
||||
status)
|
||||
method_status
|
||||
;;
|
||||
get_devices)
|
||||
read -r input
|
||||
filter=$(echo "$input" | jsonfilter -e '@.filter' 2>/dev/null)
|
||||
method_get_devices "$filter"
|
||||
;;
|
||||
get_device)
|
||||
read -r input
|
||||
mac=$(echo "$input" | jsonfilter -e '@.mac' 2>/dev/null)
|
||||
method_get_device "$mac"
|
||||
;;
|
||||
get_anomalies)
|
||||
read -r input
|
||||
limit=$(echo "$input" | jsonfilter -e '@.limit' 2>/dev/null)
|
||||
method_get_anomalies "${limit:-20}"
|
||||
;;
|
||||
get_vendor_rules)
|
||||
method_get_vendor_rules
|
||||
;;
|
||||
get_cloud_map)
|
||||
read -r input
|
||||
mac=$(echo "$input" | jsonfilter -e '@.mac' 2>/dev/null)
|
||||
method_get_cloud_map "$mac"
|
||||
;;
|
||||
scan)
|
||||
method_scan
|
||||
;;
|
||||
isolate_device)
|
||||
read -r input
|
||||
mac=$(echo "$input" | jsonfilter -e '@.mac' 2>/dev/null)
|
||||
method_isolate_device "$mac"
|
||||
;;
|
||||
trust_device)
|
||||
read -r input
|
||||
mac=$(echo "$input" | jsonfilter -e '@.mac' 2>/dev/null)
|
||||
method_trust_device "$mac"
|
||||
;;
|
||||
block_device)
|
||||
read -r input
|
||||
mac=$(echo "$input" | jsonfilter -e '@.mac' 2>/dev/null)
|
||||
method_block_device "$mac"
|
||||
;;
|
||||
add_vendor_rule)
|
||||
read -r input
|
||||
name=$(echo "$input" | jsonfilter -e '@.name' 2>/dev/null)
|
||||
pattern=$(echo "$input" | jsonfilter -e '@.pattern' 2>/dev/null)
|
||||
oui=$(echo "$input" | jsonfilter -e '@.oui' 2>/dev/null)
|
||||
class=$(echo "$input" | jsonfilter -e '@.class' 2>/dev/null)
|
||||
risk=$(echo "$input" | jsonfilter -e '@.risk' 2>/dev/null)
|
||||
auto_isolate=$(echo "$input" | jsonfilter -e '@.auto_isolate' 2>/dev/null)
|
||||
method_add_vendor_rule "$name" "$pattern" "$oui" "$class" "$risk" "$auto_isolate"
|
||||
;;
|
||||
delete_vendor_rule)
|
||||
read -r input
|
||||
name=$(echo "$input" | jsonfilter -e '@.name' 2>/dev/null)
|
||||
method_delete_vendor_rule "$name"
|
||||
;;
|
||||
*)
|
||||
echo '{"error":"Unknown method"}'
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
@ -0,0 +1,46 @@
|
||||
{
|
||||
"admin/secubox/services/iot-guard": {
|
||||
"title": "IoT Guard",
|
||||
"order": 30,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "iot-guard/overview"
|
||||
},
|
||||
"depends": {
|
||||
"acl": ["luci-app-iot-guard"]
|
||||
}
|
||||
},
|
||||
"admin/secubox/services/iot-guard/devices": {
|
||||
"title": "Devices",
|
||||
"order": 10,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "iot-guard/devices"
|
||||
},
|
||||
"depends": {
|
||||
"acl": ["luci-app-iot-guard"]
|
||||
}
|
||||
},
|
||||
"admin/secubox/services/iot-guard/policies": {
|
||||
"title": "Policies",
|
||||
"order": 20,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "iot-guard/policies"
|
||||
},
|
||||
"depends": {
|
||||
"acl": ["luci-app-iot-guard"]
|
||||
}
|
||||
},
|
||||
"admin/secubox/services/iot-guard/settings": {
|
||||
"title": "Settings",
|
||||
"order": 30,
|
||||
"action": {
|
||||
"type": "view",
|
||||
"path": "iot-guard/settings"
|
||||
},
|
||||
"depends": {
|
||||
"acl": ["luci-app-iot-guard"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
{
|
||||
"luci-app-iot-guard": {
|
||||
"description": "IoT Guard - Device Isolation & Security",
|
||||
"read": {
|
||||
"ubus": {
|
||||
"luci.iot-guard": [
|
||||
"status",
|
||||
"get_devices",
|
||||
"get_device",
|
||||
"get_anomalies",
|
||||
"get_vendor_rules",
|
||||
"get_cloud_map"
|
||||
]
|
||||
},
|
||||
"uci": ["iot-guard"]
|
||||
},
|
||||
"write": {
|
||||
"ubus": {
|
||||
"luci.iot-guard": [
|
||||
"scan",
|
||||
"isolate_device",
|
||||
"trust_device",
|
||||
"block_device",
|
||||
"add_vendor_rule",
|
||||
"delete_vendor_rule"
|
||||
]
|
||||
},
|
||||
"uci": ["iot-guard"]
|
||||
}
|
||||
},
|
||||
"unauthenticated": {
|
||||
"description": "Public access for IoT Guard dashboard",
|
||||
"read": {
|
||||
"ubus": {
|
||||
"luci.iot-guard": [
|
||||
"status",
|
||||
"get_devices"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
63
package/secubox/secubox-iot-guard/Makefile
Normal file
63
package/secubox/secubox-iot-guard/Makefile
Normal file
@ -0,0 +1,63 @@
|
||||
include $(TOPDIR)/rules.mk
|
||||
|
||||
PKG_NAME:=secubox-iot-guard
|
||||
PKG_VERSION:=1.0.0
|
||||
PKG_RELEASE:=1
|
||||
PKG_MAINTAINER:=SecuBox Team
|
||||
PKG_LICENSE:=GPL-3.0
|
||||
|
||||
include $(INCLUDE_DIR)/package.mk
|
||||
|
||||
define Package/secubox-iot-guard
|
||||
SECTION:=secubox
|
||||
CATEGORY:=SecuBox
|
||||
TITLE:=IoT Guard - Device Isolation & Security
|
||||
DEPENDS:=+secubox-core +sqlite3-cli +jsonfilter
|
||||
PKGARCH:=all
|
||||
endef
|
||||
|
||||
define Package/secubox-iot-guard/description
|
||||
IoT device isolation, classification, and security monitoring.
|
||||
Auto-classifies IoT devices by vendor OUI, enforces isolation
|
||||
policies, detects behavioral anomalies, and provides security
|
||||
risk scoring. Orchestrates Client Guardian, MAC Guardian,
|
||||
Vortex Firewall, and Bandwidth Manager for IoT protection.
|
||||
endef
|
||||
|
||||
define Package/secubox-iot-guard/conffiles
|
||||
/etc/config/iot-guard
|
||||
endef
|
||||
|
||||
define Build/Compile
|
||||
endef
|
||||
|
||||
define Package/secubox-iot-guard/install
|
||||
$(INSTALL_DIR) $(1)/usr/sbin
|
||||
$(INSTALL_BIN) ./root/usr/sbin/iot-guardctl $(1)/usr/sbin/
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/init.d
|
||||
$(INSTALL_BIN) ./root/etc/init.d/iot-guard $(1)/etc/init.d/
|
||||
|
||||
$(INSTALL_DIR) $(1)/etc/config
|
||||
$(INSTALL_CONF) ./files/config/iot-guard $(1)/etc/config/
|
||||
|
||||
$(INSTALL_DIR) $(1)/usr/lib/secubox/iot-guard
|
||||
$(INSTALL_DATA) ./root/usr/lib/secubox/iot-guard/functions.sh $(1)/usr/lib/secubox/iot-guard/
|
||||
$(INSTALL_DATA) ./root/usr/lib/secubox/iot-guard/classify.sh $(1)/usr/lib/secubox/iot-guard/
|
||||
$(INSTALL_DATA) ./root/usr/lib/secubox/iot-guard/anomaly.sh $(1)/usr/lib/secubox/iot-guard/
|
||||
$(INSTALL_DATA) ./root/usr/lib/secubox/iot-guard/iot-oui.tsv $(1)/usr/lib/secubox/iot-guard/
|
||||
|
||||
$(INSTALL_DIR) $(1)/usr/share/iot-guard/baseline-profiles
|
||||
$(INSTALL_DATA) ./root/usr/share/iot-guard/baseline-profiles/*.json $(1)/usr/share/iot-guard/baseline-profiles/
|
||||
endef
|
||||
|
||||
define Package/secubox-iot-guard/postinst
|
||||
#!/bin/sh
|
||||
[ -n "$${IPKG_INSTROOT}" ] || {
|
||||
/etc/init.d/iot-guard enable
|
||||
/etc/init.d/iot-guard start
|
||||
}
|
||||
exit 0
|
||||
endef
|
||||
|
||||
$(eval $(call BuildPackage,secubox-iot-guard))
|
||||
167
package/secubox/secubox-iot-guard/README.md
Normal file
167
package/secubox/secubox-iot-guard/README.md
Normal file
@ -0,0 +1,167 @@
|
||||
# SecuBox IoT Guard
|
||||
|
||||
IoT device isolation, classification, and security monitoring for OpenWrt.
|
||||
|
||||
## Overview
|
||||
|
||||
IoT Guard provides automated IoT device management:
|
||||
|
||||
- **Auto-Classification** - Identifies IoT devices by vendor OUI and behavior
|
||||
- **Risk Scoring** - Calculates security risk per device (0-100 scale)
|
||||
- **Auto-Isolation** - Automatically quarantines high-risk devices
|
||||
- **Anomaly Detection** - Monitors traffic patterns for behavioral anomalies
|
||||
- **Cloud Mapping** - Tracks cloud services each device contacts
|
||||
|
||||
IoT Guard **orchestrates existing SecuBox modules** rather than reimplementing:
|
||||
|
||||
| Module | Integration |
|
||||
|--------|-------------|
|
||||
| Client Guardian | Zone assignment (IoT zone) |
|
||||
| MAC Guardian | L2 blocking/trust |
|
||||
| Vortex Firewall | DNS filtering (IoT malware feeds) |
|
||||
| Bandwidth Manager | Rate limiting |
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
opkg install secubox-iot-guard luci-app-iot-guard
|
||||
```
|
||||
|
||||
## CLI Usage
|
||||
|
||||
```bash
|
||||
# Overview status
|
||||
iot-guardctl status
|
||||
|
||||
# Scan network for IoT devices
|
||||
iot-guardctl scan
|
||||
|
||||
# List all devices
|
||||
iot-guardctl list
|
||||
iot-guardctl list --json
|
||||
|
||||
# Device details
|
||||
iot-guardctl show AA:BB:CC:DD:EE:FF
|
||||
|
||||
# Isolate to IoT zone
|
||||
iot-guardctl isolate AA:BB:CC:DD:EE:FF
|
||||
|
||||
# Trust device (add to allowlist)
|
||||
iot-guardctl trust AA:BB:CC:DD:EE:FF
|
||||
|
||||
# Block device
|
||||
iot-guardctl block AA:BB:CC:DD:EE:FF
|
||||
|
||||
# View anomalies
|
||||
iot-guardctl anomalies
|
||||
|
||||
# Cloud dependency map
|
||||
iot-guardctl cloud-map AA:BB:CC:DD:EE:FF
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
Edit `/etc/config/iot-guard`:
|
||||
|
||||
```
|
||||
config iot-guard 'main'
|
||||
option enabled '1'
|
||||
option scan_interval '300' # Network scan interval (seconds)
|
||||
option auto_isolate '1' # Auto-isolate high-risk devices
|
||||
option auto_isolate_threshold '80' # Risk score threshold for auto-isolation
|
||||
option anomaly_detection '1' # Enable anomaly detection
|
||||
option anomaly_sensitivity 'medium' # low/medium/high
|
||||
|
||||
config zone_policy 'isolation'
|
||||
option target_zone 'iot' # Target zone for isolated devices
|
||||
option block_lan '1' # Block LAN access
|
||||
option allow_internet '1' # Allow internet access
|
||||
option bandwidth_limit '10' # Rate limit (Mbps)
|
||||
|
||||
config vendor_rule 'ring'
|
||||
option vendor_pattern 'Ring|Amazon Ring'
|
||||
option oui_prefix '40:B4:CD'
|
||||
option device_class 'camera'
|
||||
option risk_level 'medium'
|
||||
option auto_isolate '1'
|
||||
|
||||
config allowlist 'trusted'
|
||||
list mac 'AA:BB:CC:DD:EE:FF'
|
||||
|
||||
config blocklist 'banned'
|
||||
list mac 'AA:BB:CC:DD:EE:FF'
|
||||
```
|
||||
|
||||
## Device Classes
|
||||
|
||||
| Class | Description | Default Risk |
|
||||
|-------|-------------|--------------|
|
||||
| camera | IP cameras, video doorbells | medium |
|
||||
| thermostat | Smart thermostats, HVAC | low |
|
||||
| lighting | Smart bulbs, LED strips | low |
|
||||
| plug | Smart plugs, outlets | medium |
|
||||
| assistant | Voice assistants | medium |
|
||||
| media | TVs, streaming devices | medium |
|
||||
| lock | Smart locks | high |
|
||||
| sensor | Motion, door/window sensors | low |
|
||||
| diy | ESP32, Raspberry Pi | high |
|
||||
| mixed | Multi-function devices | high |
|
||||
|
||||
## Risk Scoring
|
||||
|
||||
Risk score is calculated as:
|
||||
|
||||
```
|
||||
score = base_risk + anomaly_penalty + cloud_penalty
|
||||
```
|
||||
|
||||
- **base_risk**: 20 (low), 50 (medium), 80 (high) based on vendor/class
|
||||
- **anomaly_penalty**: +10 per unresolved anomaly
|
||||
- **cloud_penalty**: +10 if >10 cloud deps, +20 if >20 cloud deps
|
||||
|
||||
## Anomaly Types
|
||||
|
||||
| Type | Severity | Description |
|
||||
|------|----------|-------------|
|
||||
| bandwidth_spike | high | Traffic Nx above baseline |
|
||||
| new_destination | low | First connection to domain |
|
||||
| port_scan | high | Contacted many ports quickly |
|
||||
| time_anomaly | medium | Activity at unusual hours |
|
||||
| protocol_anomaly | medium | Unexpected protocol usage |
|
||||
|
||||
## OUI Database
|
||||
|
||||
IoT Guard includes an OUI database for ~100 common IoT manufacturers:
|
||||
|
||||
- Ring, Nest, Wyze, Eufy (cameras)
|
||||
- Philips Hue, Lifx, Wiz (lighting)
|
||||
- TP-Link Kasa/Tapo, Tuya (plugs)
|
||||
- Amazon Echo, Google Home (assistants)
|
||||
- Espressif, Raspberry Pi (DIY)
|
||||
- Samsung, LG, Roku (media)
|
||||
|
||||
Add custom OUIs to `/usr/lib/secubox/iot-guard/iot-oui.tsv`:
|
||||
|
||||
```
|
||||
AA:BB:CC MyVendor camera medium
|
||||
```
|
||||
|
||||
## Files
|
||||
|
||||
```
|
||||
/etc/config/iot-guard # Configuration
|
||||
/usr/sbin/iot-guardctl # CLI controller
|
||||
/usr/lib/secubox/iot-guard/ # Library scripts
|
||||
/usr/share/iot-guard/baseline-profiles/ # Traffic baselines
|
||||
/var/lib/iot-guard/iot-guard.db # SQLite database
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
- secubox-core
|
||||
- sqlite3-cli
|
||||
- jsonfilter
|
||||
|
||||
## License
|
||||
|
||||
GPL-3.0
|
||||
96
package/secubox/secubox-iot-guard/files/config/iot-guard
Normal file
96
package/secubox/secubox-iot-guard/files/config/iot-guard
Normal file
@ -0,0 +1,96 @@
|
||||
# IoT Guard - Device Isolation & Security Configuration
|
||||
|
||||
config iot-guard 'main'
|
||||
option enabled '1'
|
||||
option scan_interval '300'
|
||||
option auto_isolate '1'
|
||||
option auto_isolate_threshold '80'
|
||||
option anomaly_detection '1'
|
||||
option anomaly_sensitivity 'medium'
|
||||
option log_level 'info'
|
||||
|
||||
# Zone isolation policy
|
||||
config zone_policy 'isolation'
|
||||
option target_zone 'iot'
|
||||
option block_lan '1'
|
||||
option allow_internet '1'
|
||||
option bandwidth_limit '10'
|
||||
|
||||
# Vendor classification rules
|
||||
config vendor_rule 'ring'
|
||||
option vendor_pattern 'Ring|Amazon Ring'
|
||||
option oui_prefix '40:B4:CD'
|
||||
option device_class 'camera'
|
||||
option risk_level 'medium'
|
||||
option auto_isolate '1'
|
||||
|
||||
config vendor_rule 'nest'
|
||||
option vendor_pattern 'Nest|Google Nest'
|
||||
option oui_prefix '18:B4:30'
|
||||
option device_class 'thermostat'
|
||||
option risk_level 'low'
|
||||
option auto_isolate '1'
|
||||
|
||||
config vendor_rule 'philips_hue'
|
||||
option vendor_pattern 'Philips Hue|Signify'
|
||||
option oui_prefix '00:17:88'
|
||||
option device_class 'bridge'
|
||||
option risk_level 'low'
|
||||
option auto_isolate '0'
|
||||
|
||||
config vendor_rule 'xiaomi'
|
||||
option vendor_pattern 'Xiaomi|Mijia'
|
||||
option oui_prefix '28:6C:07'
|
||||
option device_class 'mixed'
|
||||
option risk_level 'high'
|
||||
option auto_isolate '1'
|
||||
|
||||
config vendor_rule 'tuya'
|
||||
option vendor_pattern 'Tuya|Smart Life'
|
||||
option oui_prefix 'DC:4F:22'
|
||||
option device_class 'mixed'
|
||||
option risk_level 'high'
|
||||
option auto_isolate '1'
|
||||
|
||||
config vendor_rule 'tplink_kasa'
|
||||
option vendor_pattern 'TP-Link Kasa|Kasa Smart'
|
||||
option oui_prefix '50:C7:BF'
|
||||
option device_class 'plug'
|
||||
option risk_level 'medium'
|
||||
option auto_isolate '1'
|
||||
|
||||
config vendor_rule 'wyze'
|
||||
option vendor_pattern 'Wyze'
|
||||
option oui_prefix '2C:AA:8E'
|
||||
option device_class 'camera'
|
||||
option risk_level 'medium'
|
||||
option auto_isolate '1'
|
||||
|
||||
config vendor_rule 'espressif'
|
||||
option vendor_pattern 'Espressif|ESP32|ESP8266'
|
||||
option oui_prefix '60:01:94'
|
||||
option device_class 'diy'
|
||||
option risk_level 'high'
|
||||
option auto_isolate '1'
|
||||
|
||||
config vendor_rule 'amazon_echo'
|
||||
option vendor_pattern 'Amazon Echo|Alexa'
|
||||
option oui_prefix 'F0:27:2D'
|
||||
option device_class 'assistant'
|
||||
option risk_level 'medium'
|
||||
option auto_isolate '1'
|
||||
|
||||
config vendor_rule 'google_home'
|
||||
option vendor_pattern 'Google Home'
|
||||
option oui_prefix '30:FD:38'
|
||||
option device_class 'assistant'
|
||||
option risk_level 'medium'
|
||||
option auto_isolate '1'
|
||||
|
||||
# Allowlist - trusted IoT devices
|
||||
config allowlist 'trusted'
|
||||
# list mac 'AA:BB:CC:DD:EE:FF'
|
||||
|
||||
# Blocklist - banned IoT devices
|
||||
config blocklist 'banned'
|
||||
# list mac 'AA:BB:CC:DD:EE:FF'
|
||||
46
package/secubox/secubox-iot-guard/root/etc/init.d/iot-guard
Normal file
46
package/secubox/secubox-iot-guard/root/etc/init.d/iot-guard
Normal file
@ -0,0 +1,46 @@
|
||||
#!/bin/sh /etc/rc.common
|
||||
#
|
||||
# IoT Guard - Device Isolation & Security Service
|
||||
#
|
||||
|
||||
START=95
|
||||
STOP=10
|
||||
USE_PROCD=1
|
||||
|
||||
NAME="iot-guard"
|
||||
PROG="/usr/sbin/iot-guardctl"
|
||||
|
||||
start_service() {
|
||||
local enabled
|
||||
config_load iot-guard
|
||||
config_get_bool enabled main enabled 0
|
||||
|
||||
[ "$enabled" -eq 0 ] && {
|
||||
logger -t "$NAME" "Service disabled"
|
||||
return 0
|
||||
}
|
||||
|
||||
logger -t "$NAME" "Starting IoT Guard..."
|
||||
|
||||
procd_open_instance
|
||||
procd_set_param command "$PROG" daemon
|
||||
procd_set_param respawn 3600 5 5
|
||||
procd_set_param stdout 1
|
||||
procd_set_param stderr 1
|
||||
procd_close_instance
|
||||
|
||||
logger -t "$NAME" "IoT Guard started"
|
||||
}
|
||||
|
||||
stop_service() {
|
||||
logger -t "$NAME" "Stopping IoT Guard..."
|
||||
}
|
||||
|
||||
reload_service() {
|
||||
stop
|
||||
start
|
||||
}
|
||||
|
||||
service_triggers() {
|
||||
procd_add_reload_trigger "iot-guard"
|
||||
}
|
||||
@ -0,0 +1,198 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# IoT Guard - Anomaly Detection Module
|
||||
#
|
||||
# Detects behavioral anomalies in IoT device traffic patterns.
|
||||
#
|
||||
|
||||
# Sensitivity levels
|
||||
SENSITIVITY_LOW=3
|
||||
SENSITIVITY_MEDIUM=2
|
||||
SENSITIVITY_HIGH=1.5
|
||||
|
||||
# ============================================================================
|
||||
# Baseline Management
|
||||
# ============================================================================
|
||||
|
||||
get_device_baseline() {
|
||||
local mac="$1"
|
||||
local db="${2:-/var/lib/iot-guard/iot-guard.db}"
|
||||
|
||||
sqlite3 -json "$db" \
|
||||
"SELECT avg_bps_in, avg_bps_out, peak_bps_in, peak_bps_out, common_ports
|
||||
FROM traffic_baseline WHERE mac='$mac';" 2>/dev/null
|
||||
}
|
||||
|
||||
update_device_baseline() {
|
||||
local mac="$1"
|
||||
local bps_in="$2"
|
||||
local bps_out="$3"
|
||||
local ports="$4"
|
||||
local db="${5:-/var/lib/iot-guard/iot-guard.db}"
|
||||
|
||||
local now=$(date -Iseconds)
|
||||
|
||||
# Get current baseline
|
||||
local current=$(sqlite3 "$db" \
|
||||
"SELECT avg_bps_in, avg_bps_out, sample_count FROM traffic_baseline WHERE mac='$mac';" 2>/dev/null)
|
||||
|
||||
if [ -z "$current" ]; then
|
||||
# Create new baseline
|
||||
sqlite3 "$db" "INSERT INTO traffic_baseline
|
||||
(mac, avg_bps_in, avg_bps_out, peak_bps_in, peak_bps_out, common_ports, sample_count, last_update)
|
||||
VALUES ('$mac', $bps_in, $bps_out, $bps_in, $bps_out, '$ports', 1, '$now');"
|
||||
else
|
||||
# Update with exponential moving average
|
||||
local old_in=$(echo "$current" | cut -d'|' -f1)
|
||||
local old_out=$(echo "$current" | cut -d'|' -f2)
|
||||
local count=$(echo "$current" | cut -d'|' -f3)
|
||||
|
||||
# EMA with alpha = 0.1
|
||||
local new_in=$(awk "BEGIN {printf \"%.2f\", $old_in * 0.9 + $bps_in * 0.1}")
|
||||
local new_out=$(awk "BEGIN {printf \"%.2f\", $old_out * 0.9 + $bps_out * 0.1}")
|
||||
|
||||
sqlite3 "$db" "UPDATE traffic_baseline SET
|
||||
avg_bps_in = $new_in,
|
||||
avg_bps_out = $new_out,
|
||||
peak_bps_in = MAX(peak_bps_in, $bps_in),
|
||||
peak_bps_out = MAX(peak_bps_out, $bps_out),
|
||||
sample_count = sample_count + 1,
|
||||
last_update = '$now'
|
||||
WHERE mac = '$mac';"
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Anomaly Detection
|
||||
# ============================================================================
|
||||
|
||||
check_bandwidth_anomaly() {
|
||||
local mac="$1"
|
||||
local current_bps="$2"
|
||||
local direction="$3"
|
||||
local db="${4:-/var/lib/iot-guard/iot-guard.db}"
|
||||
|
||||
local sensitivity
|
||||
config_load iot-guard
|
||||
config_get sensitivity main anomaly_sensitivity "medium"
|
||||
|
||||
local threshold
|
||||
case "$sensitivity" in
|
||||
low) threshold=$SENSITIVITY_LOW ;;
|
||||
high) threshold=$SENSITIVITY_HIGH ;;
|
||||
*) threshold=$SENSITIVITY_MEDIUM ;;
|
||||
esac
|
||||
|
||||
# Get baseline average
|
||||
local avg_field="avg_bps_in"
|
||||
[ "$direction" = "out" ] && avg_field="avg_bps_out"
|
||||
|
||||
local baseline=$(sqlite3 "$db" "SELECT $avg_field FROM traffic_baseline WHERE mac='$mac';" 2>/dev/null)
|
||||
[ -z "$baseline" ] && return 1
|
||||
|
||||
# Check if current is threshold times the baseline
|
||||
local ratio=$(awk "BEGIN {printf \"%.2f\", $current_bps / ($baseline + 0.01)}")
|
||||
local is_anomaly=$(awk "BEGIN {print ($ratio > $threshold) ? 1 : 0}")
|
||||
|
||||
if [ "$is_anomaly" = "1" ]; then
|
||||
echo "bandwidth_spike|high|Traffic ${direction} ${ratio}x above baseline"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
check_new_destination() {
|
||||
local mac="$1"
|
||||
local domain="$2"
|
||||
local db="${3:-/var/lib/iot-guard/iot-guard.db}"
|
||||
|
||||
# Check if this is a new destination for this device
|
||||
local exists=$(sqlite3 "$db" \
|
||||
"SELECT COUNT(*) FROM cloud_deps WHERE mac='$mac' AND domain='$domain';" 2>/dev/null)
|
||||
|
||||
if [ "$exists" = "0" ]; then
|
||||
echo "new_destination|low|First connection to $domain"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
check_port_scan() {
|
||||
local mac="$1"
|
||||
local port_count="$2"
|
||||
local time_window="$3"
|
||||
|
||||
# If device contacts many ports in short time, flag as port scan
|
||||
if [ "$port_count" -gt 10 ]; then
|
||||
echo "port_scan|high|Contacted $port_count ports in ${time_window}s"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
check_time_anomaly() {
|
||||
local mac="$1"
|
||||
local db="${2:-/var/lib/iot-guard/iot-guard.db}"
|
||||
|
||||
# Get device class to determine expected active hours
|
||||
local device_class=$(sqlite3 "$db" "SELECT device_class FROM devices WHERE mac='$mac';" 2>/dev/null)
|
||||
|
||||
local hour=$(date +%H)
|
||||
|
||||
# Cameras/doorbells should be active 24/7
|
||||
# Thermostats should have some activity
|
||||
# Lights/plugs typically quiet at night
|
||||
|
||||
case "$device_class" in
|
||||
lighting|plug)
|
||||
if [ "$hour" -ge 2 ] && [ "$hour" -lt 6 ]; then
|
||||
echo "time_anomaly|medium|Unusual activity at night (${hour}:00)"
|
||||
return 0
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Main Anomaly Check
|
||||
# ============================================================================
|
||||
|
||||
run_anomaly_detection() {
|
||||
local db="${1:-/var/lib/iot-guard/iot-guard.db}"
|
||||
|
||||
# Get all active devices
|
||||
sqlite3 "$db" "SELECT mac FROM devices WHERE status='active';" 2>/dev/null | while read -r mac; do
|
||||
[ -z "$mac" ] && continue
|
||||
|
||||
# Check various anomaly types
|
||||
# These would normally be called with real-time data from traffic monitoring
|
||||
|
||||
# Time-based anomaly
|
||||
local time_result=$(check_time_anomaly "$mac" "$db")
|
||||
if [ -n "$time_result" ]; then
|
||||
local atype=$(echo "$time_result" | cut -d'|' -f1)
|
||||
local severity=$(echo "$time_result" | cut -d'|' -f2)
|
||||
local desc=$(echo "$time_result" | cut -d'|' -f3)
|
||||
record_anomaly "$mac" "$atype" "$severity" "$desc"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
record_anomaly() {
|
||||
local mac="$1"
|
||||
local atype="$2"
|
||||
local severity="$3"
|
||||
local desc="$4"
|
||||
local db="${5:-/var/lib/iot-guard/iot-guard.db}"
|
||||
|
||||
local now=$(date -Iseconds)
|
||||
sqlite3 "$db" "INSERT INTO anomalies (mac, timestamp, anomaly_type, severity, description)
|
||||
VALUES ('$mac', '$now', '$atype', '$severity', '$desc');"
|
||||
|
||||
logger -t "iot-guard" "Anomaly detected: $mac - $atype ($severity): $desc"
|
||||
}
|
||||
@ -0,0 +1,176 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# IoT Guard - Device Classification Module
|
||||
#
|
||||
# Classifies IoT devices by vendor OUI, traffic patterns, and heuristics.
|
||||
#
|
||||
|
||||
# ============================================================================
|
||||
# OUI-Based Classification
|
||||
# ============================================================================
|
||||
|
||||
# Known IoT manufacturer OUIs
|
||||
# Format: OUI_PREFIX|Vendor|Class|Risk
|
||||
IOT_OUIS="
|
||||
40:B4:CD|Ring|camera|medium
|
||||
18:B4:30|Nest Labs|thermostat|low
|
||||
00:17:88|Philips Hue|lighting|low
|
||||
28:6C:07|Xiaomi|mixed|high
|
||||
DC:4F:22|Tuya|mixed|high
|
||||
50:C7:BF|TP-Link|plug|medium
|
||||
2C:AA:8E|Wyze|camera|medium
|
||||
60:01:94|Espressif|diy|high
|
||||
F0:27:2D|Amazon|assistant|medium
|
||||
30:FD:38|Google|assistant|medium
|
||||
78:8A:20|Ubiquiti|networking|low
|
||||
B4:FB:E4|Ubiquiti|networking|low
|
||||
74:D4:35|Gree|hvac|medium
|
||||
E4:5E:1B|Orvibo|plug|high
|
||||
D0:73:D5|Lifx|lighting|low
|
||||
00:0E:58|Sonos|media|low
|
||||
B8:27:EB|Raspberry Pi|diy|high
|
||||
DC:A6:32|Raspberry Pi|diy|high
|
||||
E4:5F:01|Raspberry Pi|diy|high
|
||||
AC:BC:32|Samsung TV|media|medium
|
||||
78:BD:BC|Samsung|media|medium
|
||||
CC:2D:21|Tenda|networking|medium
|
||||
7C:A9:6B|Synology|nas|low
|
||||
00:11:32|Synology|nas|low
|
||||
B0:BE:76|TP-Link Tapo|plug|medium
|
||||
54:AF:97|TP-Link Tapo|camera|medium
|
||||
68:FF:7B|TP-Link Kasa|plug|medium
|
||||
"
|
||||
|
||||
classify_by_oui() {
|
||||
local mac="$1"
|
||||
local oui=$(echo "$mac" | cut -d':' -f1-3 | tr '[:lower:]' '[:upper:]')
|
||||
|
||||
echo "$IOT_OUIS" | while IFS='|' read -r prefix vendor class risk; do
|
||||
[ -z "$prefix" ] && continue
|
||||
if [ "$oui" = "$prefix" ]; then
|
||||
echo "$vendor|$class|$risk"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Traffic-Based Classification
|
||||
# ============================================================================
|
||||
|
||||
classify_by_traffic() {
|
||||
local mac="$1"
|
||||
local db="${2:-/var/lib/iot-guard/iot-guard.db}"
|
||||
|
||||
# Get cloud dependencies
|
||||
local domains=$(sqlite3 "$db" "SELECT domain FROM cloud_deps WHERE mac='$mac';" 2>/dev/null)
|
||||
|
||||
# Match cloud services to device types
|
||||
echo "$domains" | while read -r domain; do
|
||||
case "$domain" in
|
||||
*ring.com*|*nest.com*|*wyze.com*)
|
||||
echo "camera"
|
||||
return
|
||||
;;
|
||||
*alexa.amazon.com*|*google.home*)
|
||||
echo "assistant"
|
||||
return
|
||||
;;
|
||||
*tuya*|*smart-life*|*tuyacloud*)
|
||||
echo "mixed"
|
||||
return
|
||||
;;
|
||||
*hue*|*lifx*|*wiz*)
|
||||
echo "lighting"
|
||||
return
|
||||
;;
|
||||
*sonos*|*spotify*|*pandora*)
|
||||
echo "media"
|
||||
return
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "unknown"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Hostname-Based Classification
|
||||
# ============================================================================
|
||||
|
||||
classify_by_hostname() {
|
||||
local hostname="$1"
|
||||
hostname=$(echo "$hostname" | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
case "$hostname" in
|
||||
*cam*|*camera*|*ipcam*|*nvr*|*dvr*)
|
||||
echo "camera"
|
||||
;;
|
||||
*thermostat*|*nest*|*ecobee*|*hvac*)
|
||||
echo "thermostat"
|
||||
;;
|
||||
*light*|*hue*|*bulb*|*lamp*)
|
||||
echo "lighting"
|
||||
;;
|
||||
*plug*|*outlet*|*switch*|*socket*)
|
||||
echo "plug"
|
||||
;;
|
||||
*echo*|*alexa*|*google-home*|*homepod*)
|
||||
echo "assistant"
|
||||
;;
|
||||
*tv*|*roku*|*firestick*|*chromecast*|*sonos*)
|
||||
echo "media"
|
||||
;;
|
||||
*lock*|*doorbell*|*ring*)
|
||||
echo "lock"
|
||||
;;
|
||||
*sensor*|*motion*|*door-sensor*)
|
||||
echo "sensor"
|
||||
;;
|
||||
*esp*|*raspberry*|*arduino*)
|
||||
echo "diy"
|
||||
;;
|
||||
*)
|
||||
echo "unknown"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Combined Classification
|
||||
# ============================================================================
|
||||
|
||||
classify_device_full() {
|
||||
local mac="$1"
|
||||
local hostname="$2"
|
||||
local vendor="$3"
|
||||
local db="$4"
|
||||
|
||||
# Priority 1: OUI-based classification
|
||||
local oui_result=$(classify_by_oui "$mac")
|
||||
if [ -n "$oui_result" ]; then
|
||||
echo "$oui_result"
|
||||
return
|
||||
fi
|
||||
|
||||
# Priority 2: Hostname-based classification
|
||||
if [ -n "$hostname" ] && [ "$hostname" != "unknown" ]; then
|
||||
local host_class=$(classify_by_hostname "$hostname")
|
||||
if [ "$host_class" != "unknown" ]; then
|
||||
echo "$vendor|$host_class|medium"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
|
||||
# Priority 3: Traffic-based classification
|
||||
if [ -n "$db" ] && [ -f "$db" ]; then
|
||||
local traffic_class=$(classify_by_traffic "$mac" "$db")
|
||||
if [ "$traffic_class" != "unknown" ]; then
|
||||
echo "$vendor|$traffic_class|medium"
|
||||
return
|
||||
fi
|
||||
fi
|
||||
|
||||
# Fallback
|
||||
echo "${vendor:-Unknown}|unknown|unknown"
|
||||
}
|
||||
@ -0,0 +1,202 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# IoT Guard - Core Functions Library
|
||||
#
|
||||
# Common functions used by IoT Guard components.
|
||||
#
|
||||
|
||||
# Load UCI functions
|
||||
. /lib/functions.sh
|
||||
|
||||
# ============================================================================
|
||||
# Configuration Helpers
|
||||
# ============================================================================
|
||||
|
||||
iot_guard_enabled() {
|
||||
local enabled
|
||||
config_load iot-guard
|
||||
config_get_bool enabled main enabled 0
|
||||
return $((1 - enabled))
|
||||
}
|
||||
|
||||
get_config_value() {
|
||||
local section="$1"
|
||||
local option="$2"
|
||||
local default="$3"
|
||||
|
||||
local value
|
||||
config_load iot-guard
|
||||
config_get value "$section" "$option" "$default"
|
||||
echo "$value"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# MAC Address Utilities
|
||||
# ============================================================================
|
||||
|
||||
normalize_mac() {
|
||||
echo "$1" | tr '[:lower:]' '[:upper:]' | tr -d ' -'
|
||||
}
|
||||
|
||||
get_oui_prefix() {
|
||||
local mac="$1"
|
||||
echo "$mac" | cut -d':' -f1-3
|
||||
}
|
||||
|
||||
mac_to_key() {
|
||||
# Convert MAC to database-safe key (replace : with _)
|
||||
echo "$1" | tr ':' '_'
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Device Lookup
|
||||
# ============================================================================
|
||||
|
||||
get_device_ip() {
|
||||
local mac="$1"
|
||||
mac=$(normalize_mac "$mac")
|
||||
|
||||
# Try ARP table
|
||||
arp -n 2>/dev/null | grep -i "$mac" | awk '{print $1}' | head -1
|
||||
}
|
||||
|
||||
get_device_hostname() {
|
||||
local mac="$1"
|
||||
mac=$(normalize_mac "$mac")
|
||||
|
||||
# Try DHCP leases
|
||||
if [ -f /tmp/dhcp.leases ]; then
|
||||
grep -i "$mac" /tmp/dhcp.leases | awk '{print $4}' | head -1
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Zone Management
|
||||
# ============================================================================
|
||||
|
||||
get_device_zone() {
|
||||
local mac="$1"
|
||||
|
||||
# Check Client Guardian if available
|
||||
if [ -x /usr/sbin/client-guardian ]; then
|
||||
/usr/sbin/client-guardian get-zone "$mac" 2>/dev/null
|
||||
return
|
||||
fi
|
||||
|
||||
echo "lan"
|
||||
}
|
||||
|
||||
set_device_zone() {
|
||||
local mac="$1"
|
||||
local zone="$2"
|
||||
|
||||
if [ -x /usr/sbin/client-guardian ]; then
|
||||
/usr/sbin/client-guardian set-zone "$mac" "$zone" 2>/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Risk Assessment
|
||||
# ============================================================================
|
||||
|
||||
risk_level_to_score() {
|
||||
case "$1" in
|
||||
critical) echo 100 ;;
|
||||
high) echo 80 ;;
|
||||
medium) echo 50 ;;
|
||||
low) echo 20 ;;
|
||||
*) echo 40 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
score_to_risk_level() {
|
||||
local score="$1"
|
||||
|
||||
if [ "$score" -ge 80 ]; then
|
||||
echo "high"
|
||||
elif [ "$score" -ge 50 ]; then
|
||||
echo "medium"
|
||||
elif [ "$score" -ge 20 ]; then
|
||||
echo "low"
|
||||
else
|
||||
echo "unknown"
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Integration Helpers
|
||||
# ============================================================================
|
||||
|
||||
call_mac_guardian() {
|
||||
local action="$1"
|
||||
local mac="$2"
|
||||
|
||||
[ -x /usr/sbin/mac-guardian ] || return 1
|
||||
|
||||
case "$action" in
|
||||
trust)
|
||||
/usr/sbin/mac-guardian trust "$mac" 2>/dev/null
|
||||
;;
|
||||
block)
|
||||
/usr/sbin/mac-guardian block "$mac" 2>/dev/null
|
||||
;;
|
||||
status)
|
||||
/usr/sbin/mac-guardian status "$mac" 2>/dev/null
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
call_bandwidth_manager() {
|
||||
local action="$1"
|
||||
local mac="$2"
|
||||
local profile="${3:-iot_limited}"
|
||||
|
||||
[ -x /usr/sbin/bandwidth-manager ] || return 1
|
||||
|
||||
case "$action" in
|
||||
set-profile)
|
||||
/usr/sbin/bandwidth-manager set-profile "$mac" "$profile" 2>/dev/null
|
||||
;;
|
||||
get-profile)
|
||||
/usr/sbin/bandwidth-manager get-profile "$mac" 2>/dev/null
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
call_vortex_firewall() {
|
||||
local action="$1"
|
||||
local domain="$2"
|
||||
|
||||
[ -x /usr/sbin/vortex-firewall ] || return 1
|
||||
|
||||
case "$action" in
|
||||
block)
|
||||
/usr/sbin/vortex-firewall intel add "$domain" "iot_malware" 2>/dev/null
|
||||
;;
|
||||
check)
|
||||
/usr/sbin/vortex-firewall intel search "$domain" 2>/dev/null
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Logging
|
||||
# ============================================================================
|
||||
|
||||
iot_log() {
|
||||
local level="$1"
|
||||
shift
|
||||
logger -t "iot-guard" -p "daemon.$level" "$*"
|
||||
}
|
||||
|
||||
iot_log_info() {
|
||||
iot_log "info" "$*"
|
||||
}
|
||||
|
||||
iot_log_warn() {
|
||||
iot_log "warning" "$*"
|
||||
}
|
||||
|
||||
iot_log_error() {
|
||||
iot_log "err" "$*"
|
||||
}
|
||||
@ -0,0 +1,100 @@
|
||||
# IoT Guard - OUI Database
|
||||
# Format: OUI_PREFIX Vendor Class Risk
|
||||
#
|
||||
# OUI prefixes for common IoT device manufacturers
|
||||
# Used for auto-classification of devices on the network
|
||||
#
|
||||
40:B4:CD Ring camera medium
|
||||
44:73:D6 Ring camera medium
|
||||
18:B4:30 Nest Labs thermostat low
|
||||
64:16:66 Nest Labs thermostat low
|
||||
F4:F5:D8 Google Nest thermostat low
|
||||
00:17:88 Philips Hue bridge low
|
||||
EC:B5:FA Philips Hue bridge low
|
||||
28:6C:07 Xiaomi mixed high
|
||||
78:11:DC Xiaomi mixed high
|
||||
64:CC:2E Xiaomi mixed high
|
||||
DC:4F:22 Tuya mixed high
|
||||
10:D5:61 Tuya mixed high
|
||||
50:C7:BF TP-Link Kasa plug medium
|
||||
68:FF:7B TP-Link Kasa plug medium
|
||||
B0:BE:76 TP-Link Tapo plug medium
|
||||
54:AF:97 TP-Link Tapo camera medium
|
||||
2C:AA:8E Wyze camera medium
|
||||
D0:3F:27 Wyze camera medium
|
||||
60:01:94 Espressif diy high
|
||||
24:0A:C4 Espressif diy high
|
||||
84:CC:A8 Espressif diy high
|
||||
A4:CF:12 Espressif diy high
|
||||
EC:FA:BC Espressif diy high
|
||||
30:AE:A4 Espressif diy high
|
||||
F0:27:2D Amazon Echo assistant medium
|
||||
FC:65:DE Amazon Echo assistant medium
|
||||
68:37:E9 Amazon Echo assistant medium
|
||||
4C:EF:C0 Amazon Echo assistant medium
|
||||
74:C2:46 Amazon Echo assistant medium
|
||||
30:FD:38 Google Home assistant medium
|
||||
1C:F2:9A Google Home assistant medium
|
||||
48:D6:D5 Google Home assistant medium
|
||||
F4:F5:E8 Google Home assistant medium
|
||||
00:0E:58 Sonos media low
|
||||
78:28:CA Sonos media low
|
||||
94:9F:3E Sonos media low
|
||||
B8:E9:37 Sonos media low
|
||||
B8:27:EB Raspberry Pi diy high
|
||||
DC:A6:32 Raspberry Pi diy high
|
||||
E4:5F:01 Raspberry Pi diy high
|
||||
D8:3A:DD Raspberry Pi diy high
|
||||
2C:CF:67 Raspberry Pi diy high
|
||||
AC:BC:32 Samsung SmartThings mixed medium
|
||||
24:FB:65 Samsung SmartThings mixed medium
|
||||
D0:03:4B Samsung TV media medium
|
||||
78:BD:BC Samsung TV media medium
|
||||
38:B1:DB Samsung Appliance appliance medium
|
||||
CC:2D:21 Tenda networking medium
|
||||
7C:A9:6B Synology nas low
|
||||
00:11:32 Synology nas low
|
||||
78:8A:20 Ubiquiti networking low
|
||||
B4:FB:E4 Ubiquiti networking low
|
||||
80:2A:A8 Ubiquiti networking low
|
||||
E8:AB:FA Shenzhen mixed high
|
||||
98:DA:C4 TP-Link networking medium
|
||||
6C:5A:B0 TP-Link networking medium
|
||||
D8:6C:63 TP-Link networking medium
|
||||
44:A5:6E Murata (Sony) gaming medium
|
||||
BC:D0:74 Apple HomeKit mixed low
|
||||
7C:50:49 Apple HomeKit mixed low
|
||||
00:16:6C Samsung media medium
|
||||
00:1E:75 LG media medium
|
||||
A8:23:FE LG media medium
|
||||
3C:BD:D8 LG media medium
|
||||
00:1D:D8 Microsoft Xbox gaming medium
|
||||
28:18:78 Microsoft Xbox gaming medium
|
||||
7C:ED:8D Microsoft Xbox gaming medium
|
||||
00:04:4B Nvidia Shield media medium
|
||||
04:03:D6 Nintendo gaming medium
|
||||
98:B6:E9 Nintendo gaming medium
|
||||
7C:BB:8A Nintendo gaming medium
|
||||
78:2B:CB Dell Wyse thin_client low
|
||||
74:D4:35 Gree Electric hvac medium
|
||||
E4:5E:1B Orvibo plug high
|
||||
D0:73:D5 Lifx lighting low
|
||||
D0:73:D5 Lifx lighting low
|
||||
1C:3E:84 Apple TV media low
|
||||
40:98:AD Apple TV media low
|
||||
B0:34:95 Apple TV media low
|
||||
EC:B5:06 Apple TV media low
|
||||
38:42:0B Panasonic appliance medium
|
||||
00:E0:4C Realtek networking medium
|
||||
D4:01:29 Roku media medium
|
||||
AC:3A:7A Roku media medium
|
||||
B0:A7:37 Roku media medium
|
||||
84:EA:ED Roku media medium
|
||||
D4:E8:80 Netgear Arlo camera medium
|
||||
94:E9:EE Eufy camera medium
|
||||
18:FE:34 Espressif diy high
|
||||
5C:CF:7F Espressif diy high
|
||||
CC:50:E3 Espressif diy high
|
||||
48:3F:DA Espressif diy high
|
||||
F0:08:D1 Espressif diy high
|
||||
84:F3:EB Espressif diy high
|
||||
|
Can't render this file because it has a wrong number of fields in line 2.
|
783
package/secubox/secubox-iot-guard/root/usr/sbin/iot-guardctl
Normal file
783
package/secubox/secubox-iot-guard/root/usr/sbin/iot-guardctl
Normal file
@ -0,0 +1,783 @@
|
||||
#!/bin/sh
|
||||
#
|
||||
# iot-guardctl - IoT Guard Controller
|
||||
#
|
||||
# IoT device isolation, classification, and security monitoring.
|
||||
# Orchestrates existing SecuBox modules for IoT protection.
|
||||
#
|
||||
# Usage:
|
||||
# iot-guardctl status Overview status
|
||||
# iot-guardctl list [--json] List IoT devices
|
||||
# iot-guardctl show <mac> Device detail
|
||||
# iot-guardctl scan Network scan
|
||||
# iot-guardctl isolate <mac> Move to IoT zone
|
||||
# iot-guardctl trust <mac> Add to allowlist
|
||||
# iot-guardctl block <mac> Block device
|
||||
# iot-guardctl anomalies Show anomalies
|
||||
# iot-guardctl cloud-map <mac> Show cloud dependencies
|
||||
#
|
||||
|
||||
VERSION="1.0.0"
|
||||
NAME="iot-guard"
|
||||
|
||||
# Directories
|
||||
VAR_DIR="/var/lib/iot-guard"
|
||||
CACHE_DIR="/tmp/iot-guard"
|
||||
DB_FILE="$VAR_DIR/iot-guard.db"
|
||||
OUI_FILE="/usr/lib/secubox/iot-guard/iot-oui.tsv"
|
||||
BASELINE_DIR="/usr/share/iot-guard/baseline-profiles"
|
||||
|
||||
# Load libraries
|
||||
. /usr/lib/secubox/iot-guard/functions.sh
|
||||
[ -f /usr/lib/secubox/iot-guard/classify.sh ] && . /usr/lib/secubox/iot-guard/classify.sh
|
||||
[ -f /usr/lib/secubox/iot-guard/anomaly.sh ] && . /usr/lib/secubox/iot-guard/anomaly.sh
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
CYAN='\033[0;36m'
|
||||
MAGENTA='\033[0;35m'
|
||||
BOLD='\033[1m'
|
||||
NC='\033[0m'
|
||||
|
||||
log() { echo -e "${GREEN}[IOT-GUARD]${NC} $1"; }
|
||||
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||
error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
info() { echo -e "${CYAN}[INFO]${NC} $1"; }
|
||||
|
||||
# ============================================================================
|
||||
# Initialization
|
||||
# ============================================================================
|
||||
|
||||
init_dirs() {
|
||||
mkdir -p "$VAR_DIR" "$CACHE_DIR"
|
||||
}
|
||||
|
||||
init_db() {
|
||||
if [ ! -f "$DB_FILE" ]; then
|
||||
log "Initializing IoT Guard database..."
|
||||
sqlite3 "$DB_FILE" <<'EOF'
|
||||
CREATE TABLE IF NOT EXISTS devices (
|
||||
mac TEXT PRIMARY KEY,
|
||||
ip TEXT,
|
||||
hostname TEXT,
|
||||
vendor TEXT,
|
||||
device_class TEXT DEFAULT 'unknown',
|
||||
risk_level TEXT DEFAULT 'unknown',
|
||||
risk_score INTEGER DEFAULT 0,
|
||||
zone TEXT DEFAULT 'lan',
|
||||
status TEXT DEFAULT 'active',
|
||||
first_seen TEXT,
|
||||
last_seen TEXT,
|
||||
isolated INTEGER DEFAULT 0,
|
||||
trusted INTEGER DEFAULT 0,
|
||||
blocked INTEGER DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS anomalies (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
mac TEXT,
|
||||
timestamp TEXT,
|
||||
anomaly_type TEXT,
|
||||
severity TEXT,
|
||||
description TEXT,
|
||||
resolved INTEGER DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS cloud_deps (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
mac TEXT,
|
||||
domain TEXT,
|
||||
first_seen TEXT,
|
||||
last_seen TEXT,
|
||||
query_count INTEGER DEFAULT 1
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS traffic_baseline (
|
||||
mac TEXT PRIMARY KEY,
|
||||
avg_bps_in REAL DEFAULT 0,
|
||||
avg_bps_out REAL DEFAULT 0,
|
||||
peak_bps_in REAL DEFAULT 0,
|
||||
peak_bps_out REAL DEFAULT 0,
|
||||
common_ports TEXT,
|
||||
sample_count INTEGER DEFAULT 0,
|
||||
last_update TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_devices_class ON devices(device_class);
|
||||
CREATE INDEX IF NOT EXISTS idx_devices_risk ON devices(risk_level);
|
||||
CREATE INDEX IF NOT EXISTS idx_anomalies_mac ON anomalies(mac);
|
||||
CREATE INDEX IF NOT EXISTS idx_cloud_mac ON cloud_deps(mac);
|
||||
EOF
|
||||
log "Database initialized: $DB_FILE"
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Network Scanning
|
||||
# ============================================================================
|
||||
|
||||
scan_network() {
|
||||
init_dirs
|
||||
init_db
|
||||
|
||||
log "Scanning network for IoT devices..."
|
||||
|
||||
local now=$(date -Iseconds)
|
||||
local found=0
|
||||
local classified=0
|
||||
|
||||
# Get devices from ARP table
|
||||
arp -n 2>/dev/null | grep -v "incomplete" | tail -n +2 | while read -r line; do
|
||||
local ip=$(echo "$line" | awk '{print $1}')
|
||||
local mac=$(echo "$line" | awk '{print $3}' | tr '[:lower:]' '[:upper:]')
|
||||
|
||||
[ -z "$mac" ] && continue
|
||||
[ "$mac" = "<INCOMPLETE>" ] && continue
|
||||
|
||||
# Get hostname from DHCP leases
|
||||
local hostname=""
|
||||
if [ -f /tmp/dhcp.leases ]; then
|
||||
hostname=$(grep -i "$mac" /tmp/dhcp.leases 2>/dev/null | awk '{print $4}' | head -1)
|
||||
fi
|
||||
[ -z "$hostname" ] && hostname="unknown"
|
||||
|
||||
# Classify device
|
||||
local oui=$(echo "$mac" | cut -d':' -f1-3)
|
||||
local vendor=$(lookup_vendor "$oui")
|
||||
local class=$(classify_device "$mac" "$vendor")
|
||||
local risk=$(get_risk_level "$vendor" "$class")
|
||||
local score=$(calculate_risk_score "$mac" "$vendor" "$class")
|
||||
|
||||
# Insert or update device
|
||||
sqlite3 "$DB_FILE" "INSERT OR REPLACE INTO devices
|
||||
(mac, ip, hostname, vendor, device_class, risk_level, risk_score, first_seen, last_seen)
|
||||
VALUES ('$mac', '$ip', '$hostname', '$vendor', '$class', '$risk', $score,
|
||||
COALESCE((SELECT first_seen FROM devices WHERE mac='$mac'), '$now'), '$now');"
|
||||
|
||||
found=$((found + 1))
|
||||
[ "$class" != "unknown" ] && classified=$((classified + 1))
|
||||
|
||||
# Check for auto-isolation
|
||||
check_auto_isolate "$mac" "$vendor" "$class" "$score"
|
||||
done
|
||||
|
||||
log "Scan complete: $found devices found"
|
||||
}
|
||||
|
||||
lookup_vendor() {
|
||||
local oui="$1"
|
||||
|
||||
# Try IoT-specific OUI database first
|
||||
if [ -f "$OUI_FILE" ]; then
|
||||
local result=$(grep -i "^$oui" "$OUI_FILE" 2>/dev/null | cut -f2)
|
||||
[ -n "$result" ] && { echo "$result"; return; }
|
||||
fi
|
||||
|
||||
# Fallback to system OUI database
|
||||
if [ -f /usr/share/misc/oui.txt ]; then
|
||||
local result=$(grep -i "^$oui" /usr/share/misc/oui.txt 2>/dev/null | cut -d' ' -f2)
|
||||
[ -n "$result" ] && { echo "$result"; return; }
|
||||
fi
|
||||
|
||||
echo "Unknown"
|
||||
}
|
||||
|
||||
classify_device() {
|
||||
local mac="$1"
|
||||
local vendor="$2"
|
||||
|
||||
# Check UCI vendor rules
|
||||
local class=""
|
||||
config_load iot-guard
|
||||
|
||||
config_foreach _classify_by_rule vendor_rule "$mac" "$vendor"
|
||||
|
||||
[ -n "$IOT_DEVICE_CLASS" ] && { echo "$IOT_DEVICE_CLASS"; return; }
|
||||
|
||||
# Keyword-based classification
|
||||
case "$vendor" in
|
||||
*Ring*|*Nest*Cam*|*Wyze*|*Eufy*|*Arlo*|*Reolink*)
|
||||
echo "camera" ;;
|
||||
*Nest*|*Ecobee*|*Honeywell*|*Tado*)
|
||||
echo "thermostat" ;;
|
||||
*Hue*|*LIFX*|*Wiz*|*Sengled*)
|
||||
echo "lighting" ;;
|
||||
*Kasa*|*Wemo*|*Gosund*|*Teckin*)
|
||||
echo "plug" ;;
|
||||
*Echo*|*Alexa*|*Google*Home*|*Sonos*)
|
||||
echo "assistant" ;;
|
||||
*Sonos*|*Bose*|*Samsung*TV*|*LG*TV*|*Roku*|*Chromecast*)
|
||||
echo "media" ;;
|
||||
*August*|*Yale*|*Schlage*)
|
||||
echo "lock" ;;
|
||||
*Ring*Doorbell*|*Nest*Doorbell*)
|
||||
echo "doorbell" ;;
|
||||
*Xiaomi*|*Tuya*|*Espressif*|*ESP*)
|
||||
echo "mixed" ;;
|
||||
*)
|
||||
echo "unknown" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
_classify_by_rule() {
|
||||
local section="$1"
|
||||
local mac="$2"
|
||||
local vendor="$3"
|
||||
|
||||
local pattern oui_prefix device_class
|
||||
config_get pattern "$section" vendor_pattern
|
||||
config_get oui_prefix "$section" oui_prefix
|
||||
config_get device_class "$section" device_class
|
||||
|
||||
# Match by OUI prefix
|
||||
if [ -n "$oui_prefix" ]; then
|
||||
local mac_prefix=$(echo "$mac" | cut -d':' -f1-3 | tr '[:lower:]' '[:upper:]')
|
||||
local check_prefix=$(echo "$oui_prefix" | tr '[:lower:]' '[:upper:]')
|
||||
if [ "$mac_prefix" = "$check_prefix" ]; then
|
||||
IOT_DEVICE_CLASS="$device_class"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Match by vendor pattern
|
||||
if [ -n "$pattern" ]; then
|
||||
if echo "$vendor" | grep -qiE "$pattern"; then
|
||||
IOT_DEVICE_CLASS="$device_class"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
get_risk_level() {
|
||||
local vendor="$1"
|
||||
local class="$2"
|
||||
|
||||
# Check UCI vendor rules for risk level
|
||||
config_load iot-guard
|
||||
|
||||
local risk_level=""
|
||||
config_foreach _get_risk_by_rule vendor_rule "$vendor"
|
||||
|
||||
[ -n "$IOT_RISK_LEVEL" ] && { echo "$IOT_RISK_LEVEL"; return; }
|
||||
|
||||
# Default risk by class
|
||||
case "$class" in
|
||||
camera|doorbell)
|
||||
echo "medium" ;;
|
||||
thermostat|lighting)
|
||||
echo "low" ;;
|
||||
plug|assistant)
|
||||
echo "medium" ;;
|
||||
lock)
|
||||
echo "high" ;;
|
||||
mixed|diy)
|
||||
echo "high" ;;
|
||||
*)
|
||||
echo "unknown" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
_get_risk_by_rule() {
|
||||
local section="$1"
|
||||
local vendor="$2"
|
||||
|
||||
local pattern risk_level
|
||||
config_get pattern "$section" vendor_pattern
|
||||
config_get risk_level "$section" risk_level
|
||||
|
||||
if [ -n "$pattern" ] && echo "$vendor" | grep -qiE "$pattern"; then
|
||||
IOT_RISK_LEVEL="$risk_level"
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
calculate_risk_score() {
|
||||
local mac="$1"
|
||||
local vendor="$2"
|
||||
local class="$3"
|
||||
|
||||
local score=0
|
||||
|
||||
# Base score by risk level
|
||||
case "$(get_risk_level "$vendor" "$class")" in
|
||||
low) score=20 ;;
|
||||
medium) score=50 ;;
|
||||
high) score=80 ;;
|
||||
*) score=40 ;;
|
||||
esac
|
||||
|
||||
# Add anomaly penalty
|
||||
local anomaly_count=$(sqlite3 "$DB_FILE" "SELECT COUNT(*) FROM anomalies WHERE mac='$mac' AND resolved=0;" 2>/dev/null || echo 0)
|
||||
score=$((score + anomaly_count * 10))
|
||||
|
||||
# Add cloud dependency penalty (many cloud deps = higher risk)
|
||||
local cloud_count=$(sqlite3 "$DB_FILE" "SELECT COUNT(DISTINCT domain) FROM cloud_deps WHERE mac='$mac';" 2>/dev/null || echo 0)
|
||||
[ "$cloud_count" -gt 10 ] && score=$((score + 10))
|
||||
[ "$cloud_count" -gt 20 ] && score=$((score + 10))
|
||||
|
||||
# Cap at 100
|
||||
[ "$score" -gt 100 ] && score=100
|
||||
|
||||
echo "$score"
|
||||
}
|
||||
|
||||
check_auto_isolate() {
|
||||
local mac="$1"
|
||||
local vendor="$2"
|
||||
local class="$3"
|
||||
local score="$4"
|
||||
|
||||
local auto_isolate threshold
|
||||
config_get_bool auto_isolate main auto_isolate 0
|
||||
config_get threshold main auto_isolate_threshold 80
|
||||
|
||||
[ "$auto_isolate" -eq 0 ] && return
|
||||
|
||||
# Check if already isolated
|
||||
local is_isolated=$(sqlite3 "$DB_FILE" "SELECT isolated FROM devices WHERE mac='$mac';" 2>/dev/null || echo 0)
|
||||
[ "$is_isolated" = "1" ] && return
|
||||
|
||||
# Check if trusted
|
||||
local is_trusted=$(sqlite3 "$DB_FILE" "SELECT trusted FROM devices WHERE mac='$mac';" 2>/dev/null || echo 0)
|
||||
[ "$is_trusted" = "1" ] && return
|
||||
|
||||
# Check score threshold
|
||||
if [ "$score" -ge "$threshold" ]; then
|
||||
log "Auto-isolating high-risk device: $mac (score: $score)"
|
||||
isolate_device "$mac"
|
||||
fi
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Device Management
|
||||
# ============================================================================
|
||||
|
||||
list_devices() {
|
||||
local json_mode="$1"
|
||||
|
||||
init_db
|
||||
|
||||
if [ "$json_mode" = "--json" ]; then
|
||||
# JSON output
|
||||
echo '{"devices":['
|
||||
local first=1
|
||||
sqlite3 -json "$DB_FILE" "SELECT mac, ip, hostname, vendor, device_class, risk_level, risk_score, zone, isolated, trusted, blocked, last_seen FROM devices ORDER BY risk_score DESC;" 2>/dev/null || echo '[]'
|
||||
echo ']}'
|
||||
else
|
||||
# Human-readable output
|
||||
echo ""
|
||||
echo -e "${BOLD}IoT Guard - Device List${NC}"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
printf "%-18s %-16s %-12s %-12s %-8s %-6s %s\n" "MAC" "IP" "Class" "Risk" "Score" "Zone" "Vendor"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
sqlite3 -separator '|' "$DB_FILE" "SELECT mac, ip, device_class, risk_level, risk_score, zone, vendor FROM devices ORDER BY risk_score DESC;" 2>/dev/null | while IFS='|' read -r mac ip class risk score zone vendor; do
|
||||
# Color by risk
|
||||
local risk_color="$NC"
|
||||
case "$risk" in
|
||||
high) risk_color="$RED" ;;
|
||||
medium) risk_color="$YELLOW" ;;
|
||||
low) risk_color="$GREEN" ;;
|
||||
esac
|
||||
|
||||
# Truncate vendor
|
||||
[ ${#vendor} -gt 20 ] && vendor="${vendor:0:17}..."
|
||||
|
||||
printf "%-18s %-16s %-12s ${risk_color}%-12s${NC} %-8s %-6s %s\n" "$mac" "$ip" "$class" "$risk" "$score" "$zone" "$vendor"
|
||||
done
|
||||
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
show_device() {
|
||||
local mac="$1"
|
||||
[ -z "$mac" ] && { error "Usage: iot-guardctl show <mac>"; return 1; }
|
||||
|
||||
mac=$(echo "$mac" | tr '[:lower:]' '[:upper:]')
|
||||
init_db
|
||||
|
||||
local device=$(sqlite3 -line "$DB_FILE" "SELECT * FROM devices WHERE mac='$mac';")
|
||||
|
||||
if [ -z "$device" ]; then
|
||||
error "Device not found: $mac"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo -e "${BOLD}IoT Guard - Device Detail${NC}"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "$device"
|
||||
|
||||
# Show cloud dependencies
|
||||
echo ""
|
||||
echo -e "${BOLD}Cloud Dependencies:${NC}"
|
||||
sqlite3 -column "$DB_FILE" "SELECT domain, query_count, last_seen FROM cloud_deps WHERE mac='$mac' ORDER BY query_count DESC LIMIT 10;"
|
||||
|
||||
# Show recent anomalies
|
||||
echo ""
|
||||
echo -e "${BOLD}Recent Anomalies:${NC}"
|
||||
sqlite3 -column "$DB_FILE" "SELECT timestamp, anomaly_type, severity, description FROM anomalies WHERE mac='$mac' ORDER BY timestamp DESC LIMIT 5;"
|
||||
|
||||
echo ""
|
||||
}
|
||||
|
||||
isolate_device() {
|
||||
local mac="$1"
|
||||
[ -z "$mac" ] && { error "Usage: iot-guardctl isolate <mac>"; return 1; }
|
||||
|
||||
mac=$(echo "$mac" | tr '[:lower:]' '[:upper:]')
|
||||
init_db
|
||||
|
||||
log "Isolating device: $mac"
|
||||
|
||||
# Update database
|
||||
local now=$(date -Iseconds)
|
||||
sqlite3 "$DB_FILE" "UPDATE devices SET isolated=1, zone='iot', last_seen='$now' WHERE mac='$mac';"
|
||||
|
||||
# Call Client Guardian to set zone
|
||||
if [ -x /usr/sbin/client-guardian ]; then
|
||||
/usr/sbin/client-guardian set-zone "$mac" iot 2>/dev/null
|
||||
fi
|
||||
|
||||
# Notify other modules
|
||||
if [ -x /usr/sbin/bandwidth-manager ]; then
|
||||
/usr/sbin/bandwidth-manager set-profile "$mac" iot_limited 2>/dev/null
|
||||
fi
|
||||
|
||||
log "Device isolated: $mac -> IoT zone"
|
||||
}
|
||||
|
||||
trust_device() {
|
||||
local mac="$1"
|
||||
[ -z "$mac" ] && { error "Usage: iot-guardctl trust <mac>"; return 1; }
|
||||
|
||||
mac=$(echo "$mac" | tr '[:lower:]' '[:upper:]')
|
||||
init_db
|
||||
|
||||
log "Trusting device: $mac"
|
||||
|
||||
local now=$(date -Iseconds)
|
||||
sqlite3 "$DB_FILE" "UPDATE devices SET trusted=1, isolated=0, zone='lan', last_seen='$now' WHERE mac='$mac';"
|
||||
|
||||
# Add to UCI allowlist
|
||||
uci add_list iot-guard.trusted.mac="$mac" 2>/dev/null
|
||||
uci commit iot-guard
|
||||
|
||||
# Call MAC Guardian to trust
|
||||
if [ -x /usr/sbin/mac-guardian ]; then
|
||||
/usr/sbin/mac-guardian trust "$mac" 2>/dev/null
|
||||
fi
|
||||
|
||||
log "Device trusted: $mac"
|
||||
}
|
||||
|
||||
block_device() {
|
||||
local mac="$1"
|
||||
[ -z "$mac" ] && { error "Usage: iot-guardctl block <mac>"; return 1; }
|
||||
|
||||
mac=$(echo "$mac" | tr '[:lower:]' '[:upper:]')
|
||||
init_db
|
||||
|
||||
log "Blocking device: $mac"
|
||||
|
||||
local now=$(date -Iseconds)
|
||||
sqlite3 "$DB_FILE" "UPDATE devices SET blocked=1, status='blocked', last_seen='$now' WHERE mac='$mac';"
|
||||
|
||||
# Add to UCI blocklist
|
||||
uci add_list iot-guard.banned.mac="$mac" 2>/dev/null
|
||||
uci commit iot-guard
|
||||
|
||||
# Call MAC Guardian to block
|
||||
if [ -x /usr/sbin/mac-guardian ]; then
|
||||
/usr/sbin/mac-guardian block "$mac" 2>/dev/null
|
||||
fi
|
||||
|
||||
log "Device blocked: $mac"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Anomaly Detection
|
||||
# ============================================================================
|
||||
|
||||
show_anomalies() {
|
||||
init_db
|
||||
|
||||
echo ""
|
||||
echo -e "${BOLD}IoT Guard - Anomaly Events${NC}"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
sqlite3 -column -header "$DB_FILE" \
|
||||
"SELECT a.timestamp, a.mac, d.hostname, a.anomaly_type, a.severity, a.description
|
||||
FROM anomalies a
|
||||
LEFT JOIN devices d ON a.mac = d.mac
|
||||
WHERE a.resolved = 0
|
||||
ORDER BY a.timestamp DESC
|
||||
LIMIT 20;"
|
||||
|
||||
echo ""
|
||||
|
||||
local total=$(sqlite3 "$DB_FILE" "SELECT COUNT(*) FROM anomalies WHERE resolved=0;")
|
||||
echo -e "Total unresolved anomalies: ${YELLOW}$total${NC}"
|
||||
echo ""
|
||||
}
|
||||
|
||||
record_anomaly() {
|
||||
local mac="$1"
|
||||
local atype="$2"
|
||||
local severity="$3"
|
||||
local desc="$4"
|
||||
|
||||
init_db
|
||||
|
||||
local now=$(date -Iseconds)
|
||||
sqlite3 "$DB_FILE" "INSERT INTO anomalies (mac, timestamp, anomaly_type, severity, description)
|
||||
VALUES ('$mac', '$now', '$atype', '$severity', '$desc');"
|
||||
|
||||
# Update device risk score
|
||||
local new_score=$(calculate_risk_score "$mac" "" "")
|
||||
sqlite3 "$DB_FILE" "UPDATE devices SET risk_score=$new_score WHERE mac='$mac';"
|
||||
|
||||
log "Anomaly recorded: $mac - $atype ($severity)"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Cloud Dependency Mapping
|
||||
# ============================================================================
|
||||
|
||||
show_cloud_map() {
|
||||
local mac="$1"
|
||||
[ -z "$mac" ] && { error "Usage: iot-guardctl cloud-map <mac>"; return 1; }
|
||||
|
||||
mac=$(echo "$mac" | tr '[:lower:]' '[:upper:]')
|
||||
init_db
|
||||
|
||||
echo ""
|
||||
echo -e "${BOLD}IoT Guard - Cloud Dependencies for $mac${NC}"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
sqlite3 -column -header "$DB_FILE" \
|
||||
"SELECT domain, query_count, first_seen, last_seen
|
||||
FROM cloud_deps
|
||||
WHERE mac='$mac'
|
||||
ORDER BY query_count DESC;"
|
||||
|
||||
echo ""
|
||||
|
||||
local total=$(sqlite3 "$DB_FILE" "SELECT COUNT(DISTINCT domain) FROM cloud_deps WHERE mac='$mac';")
|
||||
echo -e "Total cloud services: ${CYAN}$total${NC}"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Status & Dashboard
|
||||
# ============================================================================
|
||||
|
||||
show_status() {
|
||||
init_dirs
|
||||
init_db
|
||||
|
||||
echo ""
|
||||
echo -e "${BOLD}╔══════════════════════════════════════════════════╗${NC}"
|
||||
echo -e "${BOLD}║ IoT Guard v$VERSION ║${NC}"
|
||||
echo -e "${BOLD}╠══════════════════════════════════════════════════╣${NC}"
|
||||
|
||||
# Get counts
|
||||
local total=$(sqlite3 "$DB_FILE" "SELECT COUNT(*) FROM devices;" 2>/dev/null || echo 0)
|
||||
local isolated=$(sqlite3 "$DB_FILE" "SELECT COUNT(*) FROM devices WHERE isolated=1;" 2>/dev/null || echo 0)
|
||||
local trusted=$(sqlite3 "$DB_FILE" "SELECT COUNT(*) FROM devices WHERE trusted=1;" 2>/dev/null || echo 0)
|
||||
local blocked=$(sqlite3 "$DB_FILE" "SELECT COUNT(*) FROM devices WHERE blocked=1;" 2>/dev/null || echo 0)
|
||||
local high_risk=$(sqlite3 "$DB_FILE" "SELECT COUNT(*) FROM devices WHERE risk_level='high';" 2>/dev/null || echo 0)
|
||||
local anomalies=$(sqlite3 "$DB_FILE" "SELECT COUNT(*) FROM anomalies WHERE resolved=0;" 2>/dev/null || echo 0)
|
||||
|
||||
# Calculate security score (inverse of risk)
|
||||
local avg_risk=$(sqlite3 "$DB_FILE" "SELECT COALESCE(AVG(risk_score), 0) FROM devices;" 2>/dev/null || echo 0)
|
||||
local security_score=$((100 - ${avg_risk%.*}))
|
||||
[ "$security_score" -lt 0 ] && security_score=0
|
||||
|
||||
echo -e "${BOLD}║${NC} IoT Devices: ${CYAN}$total${NC}"
|
||||
echo -e "${BOLD}║${NC} Isolated: ${YELLOW}$isolated${NC}"
|
||||
echo -e "${BOLD}║${NC} Trusted: ${GREEN}$trusted${NC}"
|
||||
echo -e "${BOLD}║${NC} Blocked: ${RED}$blocked${NC}"
|
||||
echo -e "${BOLD}║${NC} High Risk: ${RED}$high_risk${NC}"
|
||||
echo -e "${BOLD}║${NC} Active Anomalies:${YELLOW}$anomalies${NC}"
|
||||
echo -e "${BOLD}║${NC}"
|
||||
echo -e "${BOLD}║${NC} Security Score: ${GREEN}$security_score%${NC}"
|
||||
echo -e "${BOLD}╚══════════════════════════════════════════════════╝${NC}"
|
||||
echo ""
|
||||
|
||||
# Show by class
|
||||
echo -e "${BOLD}Devices by Class:${NC}"
|
||||
sqlite3 "$DB_FILE" "SELECT device_class || ': ' || COUNT(*) FROM devices GROUP BY device_class ORDER BY COUNT(*) DESC;" 2>/dev/null
|
||||
echo ""
|
||||
}
|
||||
|
||||
show_status_json() {
|
||||
init_db
|
||||
|
||||
local total=$(sqlite3 "$DB_FILE" "SELECT COUNT(*) FROM devices;" 2>/dev/null || echo 0)
|
||||
local isolated=$(sqlite3 "$DB_FILE" "SELECT COUNT(*) FROM devices WHERE isolated=1;" 2>/dev/null || echo 0)
|
||||
local trusted=$(sqlite3 "$DB_FILE" "SELECT COUNT(*) FROM devices WHERE trusted=1;" 2>/dev/null || echo 0)
|
||||
local blocked=$(sqlite3 "$DB_FILE" "SELECT COUNT(*) FROM devices WHERE blocked=1;" 2>/dev/null || echo 0)
|
||||
local high_risk=$(sqlite3 "$DB_FILE" "SELECT COUNT(*) FROM devices WHERE risk_level='high';" 2>/dev/null || echo 0)
|
||||
local anomalies=$(sqlite3 "$DB_FILE" "SELECT COUNT(*) FROM anomalies WHERE resolved=0;" 2>/dev/null || echo 0)
|
||||
local avg_risk=$(sqlite3 "$DB_FILE" "SELECT COALESCE(AVG(risk_score), 0) FROM devices;" 2>/dev/null || echo 0)
|
||||
local security_score=$((100 - ${avg_risk%.*}))
|
||||
[ "$security_score" -lt 0 ] && security_score=0
|
||||
|
||||
cat <<EOF
|
||||
{
|
||||
"total_devices": $total,
|
||||
"isolated": $isolated,
|
||||
"trusted": $trusted,
|
||||
"blocked": $blocked,
|
||||
"high_risk": $high_risk,
|
||||
"anomalies": $anomalies,
|
||||
"security_score": $security_score,
|
||||
"version": "$VERSION"
|
||||
}
|
||||
EOF
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Daemon Mode
|
||||
# ============================================================================
|
||||
|
||||
daemon_loop() {
|
||||
log "Starting IoT Guard daemon..."
|
||||
|
||||
init_dirs
|
||||
init_db
|
||||
|
||||
local scan_interval
|
||||
config_load iot-guard
|
||||
config_get scan_interval main scan_interval 300
|
||||
|
||||
while true; do
|
||||
scan_network
|
||||
|
||||
# Run anomaly detection if enabled
|
||||
local anomaly_detection
|
||||
config_get_bool anomaly_detection main anomaly_detection 0
|
||||
if [ "$anomaly_detection" -eq 1 ]; then
|
||||
detect_anomalies
|
||||
fi
|
||||
|
||||
sleep "$scan_interval"
|
||||
done
|
||||
}
|
||||
|
||||
detect_anomalies() {
|
||||
# Placeholder for anomaly detection logic
|
||||
# Would analyze traffic patterns, DNS queries, etc.
|
||||
:
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Usage
|
||||
# ============================================================================
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
IoT Guard - Device Isolation & Security
|
||||
|
||||
Usage: iot-guardctl <command> [options]
|
||||
|
||||
Status & Info:
|
||||
status Overview dashboard
|
||||
status --json JSON output
|
||||
list [--json] List all IoT devices
|
||||
show <mac> Device detail with cloud map
|
||||
|
||||
Actions:
|
||||
scan Scan network for IoT devices
|
||||
isolate <mac> Isolate device to IoT zone
|
||||
trust <mac> Add device to allowlist
|
||||
block <mac> Block device completely
|
||||
|
||||
Monitoring:
|
||||
anomalies Show anomaly events
|
||||
cloud-map <mac> Show cloud dependencies
|
||||
|
||||
Service:
|
||||
daemon Run in daemon mode
|
||||
|
||||
Examples:
|
||||
iot-guardctl scan
|
||||
iot-guardctl list
|
||||
iot-guardctl isolate AA:BB:CC:DD:EE:FF
|
||||
iot-guardctl cloud-map AA:BB:CC:DD:EE:FF
|
||||
EOF
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Main
|
||||
# ============================================================================
|
||||
|
||||
case "${1:-}" in
|
||||
status)
|
||||
shift
|
||||
case "${1:-}" in
|
||||
--json) show_status_json ;;
|
||||
*) show_status ;;
|
||||
esac
|
||||
;;
|
||||
|
||||
list)
|
||||
shift
|
||||
list_devices "$1"
|
||||
;;
|
||||
|
||||
show)
|
||||
shift
|
||||
show_device "$1"
|
||||
;;
|
||||
|
||||
scan)
|
||||
scan_network
|
||||
;;
|
||||
|
||||
isolate)
|
||||
shift
|
||||
isolate_device "$1"
|
||||
;;
|
||||
|
||||
trust)
|
||||
shift
|
||||
trust_device "$1"
|
||||
;;
|
||||
|
||||
block)
|
||||
shift
|
||||
block_device "$1"
|
||||
;;
|
||||
|
||||
anomalies)
|
||||
show_anomalies
|
||||
;;
|
||||
|
||||
cloud-map)
|
||||
shift
|
||||
show_cloud_map "$1"
|
||||
;;
|
||||
|
||||
daemon)
|
||||
daemon_loop
|
||||
;;
|
||||
|
||||
help|--help|-h)
|
||||
usage
|
||||
;;
|
||||
|
||||
"")
|
||||
show_status
|
||||
;;
|
||||
|
||||
*)
|
||||
error "Unknown command: $1"
|
||||
usage >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@ -0,0 +1,26 @@
|
||||
{
|
||||
"class": "assistant",
|
||||
"description": "Voice assistants (Echo, Google Home, HomePod)",
|
||||
"expected_behavior": {
|
||||
"bandwidth": {
|
||||
"avg_kbps_in": 10,
|
||||
"avg_kbps_out": 20,
|
||||
"peak_kbps_out": 500,
|
||||
"continuous_stream": false
|
||||
},
|
||||
"activity_hours": "24/7",
|
||||
"common_ports": [443, 8443, 4070],
|
||||
"cloud_services": ["alexa.amazon.com", "google.com", "apple.com"],
|
||||
"protocols": ["https", "mqtt", "coap"]
|
||||
},
|
||||
"risk_indicators": {
|
||||
"high": [
|
||||
"continuous_audio_upload",
|
||||
"connection_to_foreign_ip"
|
||||
],
|
||||
"medium": [
|
||||
"high_query_rate",
|
||||
"new_skill_connection"
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
{
|
||||
"class": "camera",
|
||||
"description": "IP cameras, video doorbells, security cameras",
|
||||
"expected_behavior": {
|
||||
"bandwidth": {
|
||||
"avg_kbps_in": 50,
|
||||
"avg_kbps_out": 500,
|
||||
"peak_kbps_out": 5000,
|
||||
"continuous_stream": true
|
||||
},
|
||||
"activity_hours": "24/7",
|
||||
"common_ports": [80, 443, 554, 8080, 8443],
|
||||
"cloud_services": ["ring.com", "nest.com", "wyze.com", "eufy.com", "arlo.com"],
|
||||
"protocols": ["http", "https", "rtsp", "rtmp"]
|
||||
},
|
||||
"risk_indicators": {
|
||||
"high": [
|
||||
"uploading_to_unknown_cloud",
|
||||
"large_data_transfer_at_night",
|
||||
"connection_to_foreign_ip"
|
||||
],
|
||||
"medium": [
|
||||
"new_cloud_destination",
|
||||
"port_change"
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
{
|
||||
"class": "plug",
|
||||
"description": "Smart plugs, outlets, switches",
|
||||
"expected_behavior": {
|
||||
"bandwidth": {
|
||||
"avg_kbps_in": 0.5,
|
||||
"avg_kbps_out": 1,
|
||||
"peak_kbps_out": 10,
|
||||
"continuous_stream": false
|
||||
},
|
||||
"activity_hours": "on_demand",
|
||||
"common_ports": [443, 6668, 6669],
|
||||
"cloud_services": ["tplinkcloud.com", "tuya.com", "wemo.com"],
|
||||
"protocols": ["https", "mqtt"]
|
||||
},
|
||||
"risk_indicators": {
|
||||
"high": [
|
||||
"scanning_network",
|
||||
"connection_to_unknown_ip",
|
||||
"high_bandwidth"
|
||||
],
|
||||
"medium": [
|
||||
"activity_at_unusual_hours"
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
{
|
||||
"class": "thermostat",
|
||||
"description": "Smart thermostats, HVAC controllers",
|
||||
"expected_behavior": {
|
||||
"bandwidth": {
|
||||
"avg_kbps_in": 1,
|
||||
"avg_kbps_out": 5,
|
||||
"peak_kbps_out": 50,
|
||||
"continuous_stream": false
|
||||
},
|
||||
"activity_hours": "24/7",
|
||||
"common_ports": [443, 8080],
|
||||
"cloud_services": ["home.nest.com", "ecobee.com", "tado.com"],
|
||||
"protocols": ["https", "mqtt"]
|
||||
},
|
||||
"risk_indicators": {
|
||||
"high": [
|
||||
"high_bandwidth_usage",
|
||||
"connection_to_unknown_ip"
|
||||
],
|
||||
"medium": [
|
||||
"frequent_cloud_queries"
|
||||
]
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user