331 lines
13 KiB
JavaScript
331 lines
13 KiB
JavaScript
const express = require('express');
|
|
const router = express.Router();
|
|
const { Threat, Event, Flow, DeviceStat } = require('../../models/Schemas');
|
|
const { getTimeFilter, getBaseFilter } = require('./helpers');
|
|
const { generateMacFromIp, resolveDeviceTypeFromIp, resolveOSFromIp, resolveVendorFromIp } = require('../../deviceResolver');
|
|
|
|
// GET /api/dashboard/threats
|
|
router.get('/threats', async (req, res) => {
|
|
try {
|
|
const limit = req.query.limit !== undefined ? parseInt(req.query.limit) : 0;
|
|
const skip = parseInt(req.query.skip ?? 0);
|
|
const timeFilter = getTimeFilter(req);
|
|
const query = getBaseFilter(req, timeFilter);
|
|
|
|
let dbQuery = Threat.find(query).sort({ detected_at: -1, timestamp: -1 }).skip(skip);
|
|
if (limit > 0) dbQuery = dbQuery.limit(limit);
|
|
const rawThreats = await dbQuery.lean();
|
|
|
|
if (rawThreats.length > 0) {
|
|
const data = rawThreats.map(t => ({
|
|
id: t._id?.toString(),
|
|
threat_type: t.threat_type,
|
|
severity: t.severity,
|
|
ip_address: t.ip_address || t.src_ip,
|
|
dst_ip: t.dst_ip,
|
|
mac_address: t.mac_address || t.src_mac || null,
|
|
app_label: t.app_label || null,
|
|
domain: t.domain || null,
|
|
detected_at: t.detected_at || t.event_at || t.timestamp,
|
|
description: t.description || `Suspicious activity from ${t.ip_address || t.src_ip}`,
|
|
agent_uuid: t.agent_uuid,
|
|
}));
|
|
return res.json({ ok: true, data });
|
|
}
|
|
|
|
const baseEventFilter = {};
|
|
if (query.agent_uuid) baseEventFilter.agent_uuid = query.agent_uuid;
|
|
if (query.site_uuid) baseEventFilter.site_uuid = query.site_uuid;
|
|
if (timeFilter) {
|
|
baseEventFilter.$and = [
|
|
{ $or: [{ event_at: timeFilter }, { timestamp: timeFilter }] }
|
|
];
|
|
}
|
|
|
|
let evtQuery = Event.find({
|
|
...baseEventFilter,
|
|
$or: [
|
|
{ severity: { $in: ['Critical', 'High'] } },
|
|
{ category_label: 'Cybersecurity' }
|
|
]
|
|
}).sort({ event_at: -1, timestamp: -1 });
|
|
if (limit > 0) evtQuery = evtQuery.skip(skip).limit(limit);
|
|
|
|
const rawEvents = await evtQuery.lean();
|
|
const macs = [...new Set(rawEvents.map(e => e.mac_address).filter(Boolean))];
|
|
const macEnrichment = {};
|
|
|
|
if (macs.length > 0) {
|
|
const flowLookupFilter = { src_mac: { $in: macs } };
|
|
if (query.agent_uuid) flowLookupFilter.agent_uuid = query.agent_uuid;
|
|
if (query.site_uuid) flowLookupFilter.site_uuid = query.site_uuid;
|
|
|
|
const flowsForMac = await Flow.aggregate([
|
|
{ $match: flowLookupFilter },
|
|
{ $sort: { timestamp: -1 } },
|
|
{ $group: {
|
|
_id: '$src_mac',
|
|
src_ip: { $first: '$src_ip' },
|
|
dst_ip: { $first: '$dst_ip' },
|
|
app_label: { $first: '$app_label' },
|
|
domain: { $first: '$domain' },
|
|
}},
|
|
]);
|
|
|
|
flowsForMac.forEach(f => {
|
|
if (f._id) macEnrichment[f._id] = {
|
|
ip_address: f.src_ip || null,
|
|
dst_ip: f.dst_ip || null,
|
|
app_label: f.app_label || null,
|
|
domain: f.domain || null,
|
|
};
|
|
});
|
|
}
|
|
|
|
const THREAT_TYPE_MAP = {
|
|
'encryption.audit': 'Weak Encryption Detected',
|
|
'server.discovery': 'Unauthorized Server Detected',
|
|
'new.device': 'New Unknown Device',
|
|
'update.device': 'Device Configuration Change',
|
|
};
|
|
|
|
const data = rawEvents.map(e => {
|
|
const enrich = (e.mac_address && macEnrichment[e.mac_address]) || {};
|
|
return {
|
|
id: e._id?.toString(),
|
|
threat_type: THREAT_TYPE_MAP[e.event_type] || e.event_type || 'Security Event',
|
|
severity: e.severity || 'Warning',
|
|
ip_address: e.ip_address || enrich.ip_address || null,
|
|
dst_ip: enrich.dst_ip || null,
|
|
mac_address: e.mac_address || null,
|
|
app_label: enrich.app_label || null,
|
|
domain: enrich.domain || null,
|
|
detected_at: e.event_at || e.timestamp,
|
|
description: e.description || `Security event: ${e.event_type}`,
|
|
agent_uuid: e.agent_uuid,
|
|
};
|
|
});
|
|
|
|
res.json({ ok: true, data });
|
|
} catch (err) {
|
|
res.status(500).json({ ok: false, error: err.message });
|
|
}
|
|
});
|
|
|
|
// GET /api/dashboard/intelligence/stats
|
|
router.get('/intelligence/stats', async (req, res) => {
|
|
try {
|
|
const timeFilter = getTimeFilter(req);
|
|
const base = getBaseFilter(req, timeFilter);
|
|
|
|
// Get real counts for all 9 categories
|
|
const [
|
|
intel_crypto_mining,
|
|
intel_tor_detection,
|
|
intel_vpn_detection,
|
|
intel_ip_reputation,
|
|
intel_insecure_protocols,
|
|
intel_unencrypted_passwords,
|
|
rawDevices,
|
|
intel_server_discovery
|
|
] = await Promise.all([
|
|
Threat.countDocuments({ ...base, threat_type: /mining/i }),
|
|
Threat.countDocuments({ ...base, threat_type: /tor/i }),
|
|
Threat.countDocuments({ ...base, threat_type: /vpn/i }),
|
|
Threat.countDocuments({ ...base, threat_type: /reputation/i }),
|
|
Threat.countDocuments({ ...base, threat_type: /insecure/i, $nor: [{ threat_type: /password/i }] }),
|
|
Threat.countDocuments({ ...base, threat_type: /password/i }),
|
|
DeviceStat.distinct('ip_address', base),
|
|
Event.countDocuments({ ...base, event_type: 'server.discovery' })
|
|
]);
|
|
|
|
const intel_device_discovery = rawDevices.length;
|
|
const intel_encryption_audit = rawDevices.length; // Same as devices for now, as each device is audited
|
|
|
|
res.json({
|
|
ok: true,
|
|
data: {
|
|
intel_crypto_mining,
|
|
intel_tor_detection,
|
|
intel_vpn_detection,
|
|
intel_ip_reputation,
|
|
intel_insecure_protocols,
|
|
intel_unencrypted_passwords,
|
|
intel_encryption_audit,
|
|
intel_device_discovery,
|
|
intel_server_discovery
|
|
}
|
|
});
|
|
} catch (err) {
|
|
res.status(500).json({ ok: false, error: err.message });
|
|
}
|
|
});
|
|
|
|
// Helper for detail threat intelligence tables
|
|
async function getIntelData(req, threatTypeRegex = null, limit = 0) {
|
|
const timeFilter = getTimeFilter(req);
|
|
const query = getBaseFilter(req, timeFilter);
|
|
|
|
if (threatTypeRegex) {
|
|
query.threat_type = { $regex: threatTypeRegex, $options: 'i' };
|
|
}
|
|
|
|
let dbQuery = Threat.find(query).sort({ detected_at: -1, timestamp: -1 });
|
|
if (limit > 0) dbQuery = dbQuery.limit(limit);
|
|
|
|
const list = await dbQuery.lean();
|
|
|
|
return list.map((t) => {
|
|
const ip = t.ip_address || t.src_ip;
|
|
const mac = t.mac_address || t.src_mac;
|
|
const eTime = t.detected_at || t.timestamp?.toISOString() || new Date().toISOString();
|
|
return {
|
|
id: t._id?.toString(),
|
|
detected_at: eTime,
|
|
ip_address: ip,
|
|
mac_address: mac,
|
|
pool_host: t.domain || null,
|
|
pool_ip: t.dst_ip || null,
|
|
protocol: t.protocol || 'TCP',
|
|
app_label: t.app_label || 'Unknown',
|
|
confidence: t.severity === 'Critical' ? 99 : (t.severity === 'High' ? 90 : 75),
|
|
download: t.download || 0,
|
|
upload: t.upload || 0,
|
|
exit_node: t.dst_ip || null,
|
|
circuit_id: t.flow_id || null,
|
|
country: 'Unknown', // Geo IP not in Threat schema yet
|
|
vpn_type: t.app_label || 'Unknown VPN',
|
|
remote_ip: t.dst_ip || null,
|
|
device_label: ip,
|
|
device_type: 'Unknown',
|
|
os_label: 'Unknown',
|
|
manufacturer: 'Unknown',
|
|
risk_level: t.severity || 'Medium',
|
|
risk: t.severity || 'Medium',
|
|
reputation: t.threat_type || 'Malicious IP',
|
|
severity: t.severity || 'Warning'
|
|
};
|
|
});
|
|
}
|
|
|
|
router.get('/intelligence/crypto-mining', async (req, res) => { try { res.json({ ok: true, data: await getIntelData(req, 'mining', 0) }); } catch(e){ res.status(500).json({ok:false,error:e.message}); } });
|
|
router.get('/intelligence/insecure-protocols', async (req, res) => { try { res.json({ ok: true, data: await getIntelData(req, 'Insecure', 0) }); } catch(e){ res.status(500).json({ok:false,error:e.message}); } });
|
|
router.get('/intelligence/ip-reputation', async (req, res) => { try { res.json({ ok: true, data: await getIntelData(req, 'Reputation', 0) }); } catch(e){ res.status(500).json({ok:false,error:e.message}); } });
|
|
router.get('/intelligence/tor', async (req, res) => { try { res.json({ ok: true, data: await getIntelData(req, 'tor', 0) }); } catch(e){ res.status(500).json({ok:false,error:e.message}); } });
|
|
router.get('/intelligence/unencrypted-passwords', async (req, res) => { try { res.json({ ok: true, data: await getIntelData(req, 'password', 0) }); } catch(e){ res.status(500).json({ok:false,error:e.message}); } });
|
|
router.get('/intelligence/vpn', async (req, res) => { try { res.json({ ok: true, data: await getIntelData(req, 'vpn', 0) }); } catch(e){ res.status(500).json({ok:false,error:e.message}); } });
|
|
|
|
// Specialized Intelligence Data
|
|
router.get('/intelligence/device-discovery', async (req, res) => {
|
|
try {
|
|
const timeFilter = getTimeFilter(req);
|
|
const query = getBaseFilter(req, timeFilter);
|
|
const devices = await require('../../models/Schemas').DeviceStat.find(query).sort({ timestamp: -1 }).lean();
|
|
|
|
const uniqueMap = new Map();
|
|
devices.forEach(d => {
|
|
if (!uniqueMap.has(d.ip_address)) {
|
|
uniqueMap.set(d.ip_address, {
|
|
id: d._id?.toString(),
|
|
ip_address: d.ip_address,
|
|
mac_address: d.mac_address || '-',
|
|
device_type: d.device_type || 'Unknown',
|
|
os_label: d.os_label || 'Unknown',
|
|
manufacturer: d.manufacturer || 'Unknown',
|
|
download: d.download || 0,
|
|
upload: d.upload || 0,
|
|
last_seen: d.timestamp || new Date()
|
|
});
|
|
}
|
|
});
|
|
res.json({ ok: true, data: Array.from(uniqueMap.values()) });
|
|
} catch(e) { res.status(500).json({ ok: false, error: e.message }); }
|
|
});
|
|
|
|
router.get('/intelligence/encryption-audit', async (req, res) => {
|
|
try {
|
|
const timeFilter = getTimeFilter(req);
|
|
const query = getBaseFilter(req, timeFilter);
|
|
const devices = await require('../../models/Schemas').DeviceStat.find(query).sort({ timestamp: -1 }).lean();
|
|
|
|
const uniqueMap = new Map();
|
|
devices.forEach(d => {
|
|
if (!uniqueMap.has(d.ip_address)) {
|
|
const download = d.download || 0;
|
|
const upload = d.upload || 0;
|
|
uniqueMap.set(d.ip_address, {
|
|
id: d._id?.toString(),
|
|
ip_address: d.ip_address,
|
|
mac_address: d.mac_address || '-',
|
|
device_label: d.device_label || d.ip_address,
|
|
encrypted_pct: 85, // Default for now as per DPI capability
|
|
unencrypted: Math.floor(download * 0.15),
|
|
encrypted: Math.floor(download * 0.85),
|
|
total: download + upload,
|
|
risk_level: download > 1024 * 1024 * 1024 ? 'medium' : 'safe',
|
|
last_seen: d.last_seen || d.timestamp || new Date().toISOString()
|
|
});
|
|
}
|
|
});
|
|
res.json({ ok: true, data: Array.from(uniqueMap.values()) });
|
|
} catch(e) { res.status(500).json({ ok: false, error: e.message }); }
|
|
});
|
|
|
|
router.get('/intelligence/server-discovery', async (req, res) => {
|
|
try {
|
|
const timeFilter = getTimeFilter(req);
|
|
const query = getBaseFilter(req, timeFilter);
|
|
query.event_type = 'server.discovery';
|
|
|
|
const events = await Event.find(query).sort({ timestamp: -1 }).lean();
|
|
|
|
// Resolve IPs using DeviceStat
|
|
const macs = events.map(e => e.mac_address).filter(Boolean);
|
|
const agentFilter = {};
|
|
if (query.agent_uuid) agentFilter.agent_uuid = query.agent_uuid;
|
|
if (query.site_uuid) agentFilter.site_uuid = query.site_uuid;
|
|
const devices = await DeviceStat.find({ mac_address: { $in: macs }, ...agentFilter }).lean();
|
|
const macMap = {};
|
|
devices.forEach(d => {
|
|
macMap[d.mac_address] = d;
|
|
});
|
|
|
|
const data = events.map(e => {
|
|
let serverType = e.category_label || 'Local Server';
|
|
let osLabel = 'Unknown';
|
|
let port = 0;
|
|
|
|
// Parse description: "Detected DHCP server on External Gateway"
|
|
const match = e.description?.match(/Detected (.*?) server on (.*)/i);
|
|
if (match) {
|
|
serverType = match[1].trim();
|
|
osLabel = match[2].trim();
|
|
}
|
|
|
|
// Infer Port
|
|
const sTypeUpper = serverType.toUpperCase();
|
|
if (sTypeUpper.includes('DHCP')) port = 67;
|
|
else if (sTypeUpper.includes('DNS')) port = 53;
|
|
else if (sTypeUpper.includes('SSH')) port = 22;
|
|
else if (sTypeUpper.includes('HTTP')) port = 80;
|
|
else if (sTypeUpper.includes('HTTPS')) port = 443;
|
|
else if (sTypeUpper.includes('FTP')) port = 21;
|
|
|
|
const device = macMap[e.mac_address] || {};
|
|
|
|
return {
|
|
id: e._id?.toString(),
|
|
ip_address: e.ip_address || device.ip_address || null,
|
|
mac_address: e.mac_address,
|
|
server_type: serverType,
|
|
port: port,
|
|
os_label: osLabel !== 'Unknown' ? osLabel : (device.os_label || 'Unknown'),
|
|
last_seen: e.event_at || e.timestamp || device.last_seen || device.timestamp || new Date().toISOString()
|
|
};
|
|
});
|
|
res.json({ ok: true, data });
|
|
} catch(e) { res.status(500).json({ ok: false, error: e.message }); }
|
|
});
|
|
|
|
module.exports = router;
|