feat(security): Security Center — firewall, NAT, bandwidth, posture, connections (v0.14.0-beta)
A dedicated top-level Security section plus a Meraki/UniFi-grade firewall built
on the full RouterOS feature set.
Security Center (new /security page)
- Fleet hardening scores, per-device posture list, and a "Common Findings"
rollup aggregating identical issues across the fleet
- "Manage" deep-links into the device's Security tab (?tab= support added)
Firewall & NAT (per device + in Security Center)
- Friendly rule builder: Allow/Drop/Reject chips, Any/Address/Address-List
pickers, port presets, connection-state chips, logging, live plain-English
preview
- Address lists as reusable objects, referenced from rules
- Rule reordering (/ip/firewall/{filter,nat}/move); per-rule hit counters
(print stats) with dead-rule highlighting + reset
- NAT wizards: Port Forward / Masquerade / 1:1 NAT + Custom
- Safe-apply lockout guard: backend 409 on an unscoped input drop/reject that
would sever management; UI confirms before forcing (detectLockoutRisk, tested)
Access & security
- Security posture audit: graduated, de-duplicated scoring that never proposes
disabling the management service the platform connects through (no self-
lockout); switch-aware input-firewall severity; service hardening table
- Bandwidth: simple-queue CRUD + one-click "Limit this client" on client detail
- Active connections viewer
These L3 features are available on all managed RouterOS devices (routers,
switches, APs), not gated by device type.
Backend: DeviceCollector address-list/move/stats/connections/queues/service
methods; device routes for all of the above; firewallSafety + firewallSummary
utils with unit tests (19 new).
Security: bump nodemailer 8 -> 9.0.1 to clear GHSA-p6gq-j5cr-w38f (our
createTransport/sendMail usage is unaffected by the advisory's raw-option path).
Verified live against RouterOS devices; lint/typecheck/build/jest green in both
workspaces; npm audit --audit-level=high clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
623e54120d
commit
ee9c9ff9f7
@@ -135,6 +135,14 @@ export interface BulkAddJobStatus {
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface SecurityCheck {
|
||||
id: string;
|
||||
severity: 'high' | 'medium' | 'low' | 'ok';
|
||||
title: string;
|
||||
detail: string;
|
||||
serviceId?: string;
|
||||
}
|
||||
|
||||
export const devicesApi = {
|
||||
list: () => api.get<Device[]>('/devices'),
|
||||
discovered: () => api.get<DiscoveredDevice[]>('/devices/discovered'),
|
||||
@@ -236,6 +244,38 @@ export const devicesApi = {
|
||||
api.put<Record<string, string>[]>(`/devices/${id}/nat/${encodeURIComponent(ruleId)}`, data),
|
||||
deleteNatRule: (id: number, ruleId: string) =>
|
||||
api.delete(`/devices/${id}/nat/${encodeURIComponent(ruleId)}`),
|
||||
// Firewall: reorder + counters (order is decisive in RouterOS)
|
||||
moveFirewallRule: (id: number, ruleId: string, destination?: string) =>
|
||||
api.post<Record<string, string>[]>(`/devices/${id}/firewall/move`, { id: ruleId, destination }),
|
||||
resetFirewallCounters: (id: number) =>
|
||||
api.post<Record<string, string>[]>(`/devices/${id}/firewall/reset-counters`),
|
||||
moveNatRule: (id: number, ruleId: string, destination?: string) =>
|
||||
api.post<Record<string, string>[]>(`/devices/${id}/nat/move`, { id: ruleId, destination }),
|
||||
// Firewall address lists (reusable address objects)
|
||||
getAddressLists: (id: number) => api.get<Record<string, string>[]>(`/devices/${id}/address-lists`),
|
||||
addAddressListEntry: (id: number, data: { list: string; address: string; comment?: string; timeout?: string }) =>
|
||||
api.post<Record<string, string>[]>(`/devices/${id}/address-lists`, data),
|
||||
updateAddressListEntry: (id: number, entryId: string, data: Record<string, unknown>) =>
|
||||
api.put<Record<string, string>[]>(`/devices/${id}/address-lists/${encodeURIComponent(entryId)}`, data),
|
||||
removeAddressListEntry: (id: number, entryId: string) =>
|
||||
api.delete(`/devices/${id}/address-lists/${encodeURIComponent(entryId)}`),
|
||||
// Active connections (read-only)
|
||||
getConnections: (id: number, limit = 500) =>
|
||||
api.get<{ total: number; connections: Record<string, string>[] }>(`/devices/${id}/connections`, { params: { limit } }),
|
||||
// Simple queues (bandwidth control)
|
||||
getQueues: (id: number) => api.get<Record<string, string>[]>(`/devices/${id}/queues`),
|
||||
addQueue: (id: number, data: Record<string, unknown>) =>
|
||||
api.post<Record<string, string>[]>(`/devices/${id}/queues`, data),
|
||||
updateQueue: (id: number, queueId: string, data: Record<string, unknown>) =>
|
||||
api.put<Record<string, string>[]>(`/devices/${id}/queues/${encodeURIComponent(queueId)}`, data),
|
||||
removeQueue: (id: number, queueId: string) =>
|
||||
api.delete(`/devices/${id}/queues/${encodeURIComponent(queueId)}`),
|
||||
// IP services + security posture
|
||||
getServices: (id: number) => api.get<Record<string, string>[]>(`/devices/${id}/services`),
|
||||
setServiceDisabled: (id: number, serviceId: string, disabled: boolean) =>
|
||||
api.put<Record<string, string>[]>(`/devices/${id}/services/${encodeURIComponent(serviceId)}`, { disabled }),
|
||||
getSecurityPosture: (id: number) =>
|
||||
api.get<{ score: number; checks: SecurityCheck[] }>(`/devices/${id}/security-posture`),
|
||||
patchLocation: (id: number, data: {
|
||||
location_address?: string | null;
|
||||
location_lat?: number | null;
|
||||
|
||||
Reference in New Issue
Block a user