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:
CyberMind-FR 2026-02-11 10:36:04 +01:00
parent 0544adbee6
commit 8ef0c70d0f
24 changed files with 3429 additions and 0 deletions

View File

@ -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

View File

@ -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**

View 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))

View 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

View File

@ -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)
])
]);
}
});

View File

@ -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]);
});
}
});

View File

@ -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)
]);
}
});

View File

@ -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();
}
});

View File

@ -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

View File

@ -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"]
}
}
}

View File

@ -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"
]
}
}
}
}

View 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))

View 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

View 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'

View 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"
}

View File

@ -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"
}

View File

@ -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"
}

View File

@ -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" "$*"
}

View File

@ -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.

View 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

View File

@ -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"
]
}
}

View File

@ -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"
]
}
}

View File

@ -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"
]
}
}

View File

@ -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"
]
}
}