feat(source2): push all latest files - device labeling, help system, proxy docs, database isolation fix
- Added DOKUMENTASI-FILTER-PER-SITE.md (site isolation docs) - Fixed start-with-env.js to force-load .env.production - Fixed MONGODB_URI hostname from mongodb-netify to mongodb.prod.proit.id - Updated .gitignore to exclude sensitive scripts and credential files - Minor UI and labeling improvements
This commit is contained in:
1 parent
a403f752f3
commit
dd4c8f6876
385 files changed
+31016
-8268
No files matched your search
@@ -0,0 +1,15 @@
|
||||
FROM oven/bun:1-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY backend/package*.json ./
|
||||
RUN bun install --production
|
||||
|
||||
COPY backend/ .
|
||||
|
||||
EXPOSE 3001
|
||||
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=20s --retries=3 \
|
||||
CMD bun -e "require('http').get('http://localhost:3001/api/health', r => r.statusCode === 200 ? process.exit(0) : process.exit(1)).on('error', () => process.exit(1))"
|
||||
|
||||
CMD ["bun", "run", "server.js"]
|
||||
@@ -0,0 +1,3 @@
|
||||
const db = require('better-sqlite3')('backend/netify_data.db');
|
||||
console.log('Devices:', db.prepare("SELECT * FROM devices WHERE ip_address = '192.168.9.2'").all());
|
||||
console.log('Discovery:', db.prepare("SELECT * FROM intel_device_discovery WHERE ip_address = '192.168.9.2'").all());
|
||||
@@ -0,0 +1,22 @@
|
||||
const mongoose = require('mongoose');
|
||||
require('dotenv').config({path: '../.env.local'});
|
||||
mongoose.connect(process.env.MONGODB_URI).then(async () => {
|
||||
const db = mongoose.connection;
|
||||
const highEvents = await db.collection('events').find({
|
||||
$or: [
|
||||
{severity: {$in: ['Critical', 'High']}},
|
||||
{category_label: 'Cybersecurity'}
|
||||
]
|
||||
}).toArray();
|
||||
|
||||
if (highEvents.length > 0) {
|
||||
console.log("High Events timestamps:");
|
||||
highEvents.forEach(e => {
|
||||
console.log("- event_at:", e.event_at, " | timestamp:", e.timestamp);
|
||||
});
|
||||
} else {
|
||||
console.log("No high events found in array");
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}).catch(e => console.error(e));
|
||||
@@ -0,0 +1,44 @@
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
mongoose.connect('mongodb://backone_user:SusuKudaLiar@103.80.237.29:27017/backone_dpi?authSource=backone_dpi')
|
||||
.then(async () => {
|
||||
const db = mongoose.connection.useDb('backone_dpi');
|
||||
const yesterday = new Date(Date.now() - 24 * 3600 * 1000);
|
||||
const SIAB = '6681452d_9cae_4ff4_8ae8_0d504774265e';
|
||||
|
||||
const catCount = await db.db.collection('appcategorystats').countDocuments({ site_uuid: SIAB, timestamp: { $gte: yesterday } });
|
||||
const catSum = await db.db.collection('appcategorystats').aggregate([
|
||||
{ $match: { site_uuid: SIAB, timestamp: { $gte: yesterday } } },
|
||||
{ $group: { _id: null, dl: { $sum: '$download' }, ul: { $sum: '$upload' } } }
|
||||
]).toArray();
|
||||
|
||||
const sumCount = await db.db.collection('summaries').countDocuments({ site_uuid: SIAB, timestamp: { $gte: yesterday } });
|
||||
const sumSum = await db.db.collection('summaries').aggregate([
|
||||
{ $match: { site_uuid: SIAB, timestamp: { $gte: yesterday } } },
|
||||
{ $group: { _id: null, dl: { $sum: '$bandwidth_down' }, ul: { $sum: '$bandwidth_up' } } }
|
||||
]).toArray();
|
||||
|
||||
const flowCount = await db.db.collection('flows').countDocuments({ site_uuid: SIAB, timestamp: { $gte: yesterday } });
|
||||
const flowSum = await db.db.collection('flows').aggregate([
|
||||
{ $match: { site_uuid: SIAB, timestamp: { $gte: yesterday } } },
|
||||
{ $group: { _id: null, dl: { $sum: '$download' }, ul: { $sum: '$upload' } } }
|
||||
]).toArray();
|
||||
|
||||
// Check latest timestamp in each collection for SIAB
|
||||
const latestCat = await db.db.collection('appcategorystats').findOne({ site_uuid: SIAB }, { sort: { timestamp: -1 } });
|
||||
const latestFlow = await db.db.collection('flows').findOne({ site_uuid: SIAB }, { sort: { timestamp: -1 } });
|
||||
const latestSum = await db.db.collection('summaries').findOne({ site_uuid: SIAB }, { sort: { timestamp: -1 } });
|
||||
|
||||
console.log('=== SIAB Site Data Check (Last 24h) ===');
|
||||
console.log('AppCatStats (24h):', catCount, 'docs | Sum:', JSON.stringify(catSum[0]));
|
||||
console.log('Summaries (24h) :', sumCount, 'docs | Sum:', JSON.stringify(sumSum[0]));
|
||||
console.log('Flows (24h) :', flowCount, 'docs | Sum:', JSON.stringify(flowSum[0]));
|
||||
console.log('');
|
||||
console.log('=== Latest Timestamps ===');
|
||||
console.log('Latest AppCat :', latestCat?.timestamp);
|
||||
console.log('Latest Flow :', latestFlow?.timestamp);
|
||||
console.log('Latest Summary :', latestSum?.timestamp);
|
||||
|
||||
mongoose.disconnect();
|
||||
})
|
||||
.catch(e => { console.error('Error:', e.message); process.exit(1); });
|
||||
@@ -0,0 +1,31 @@
|
||||
const { Client } = require('ssh2');
|
||||
const conn = new Client();
|
||||
|
||||
conn.on('ready', () => {
|
||||
const cmd = [
|
||||
'export PM2=/home/adminbackend/.npm-global/bin/pm2',
|
||||
'$PM2 list',
|
||||
'echo "=== MEMORY ==="',
|
||||
'free -m',
|
||||
'echo "=== DISK ==="',
|
||||
'df -h /',
|
||||
'echo "=== FRONTEND LOGS ==="',
|
||||
'$PM2 logs backone-frontend --lines 20 --nostream 2>&1',
|
||||
'echo "=== BACKEND LOGS ==="',
|
||||
'$PM2 logs backone-backend --lines 10 --nostream 2>&1',
|
||||
].join(' && ');
|
||||
|
||||
conn.exec(cmd, (err, stream) => {
|
||||
if (err) { console.error(err); conn.end(); return; }
|
||||
stream.on('data', d => process.stdout.write(d.toString()));
|
||||
stream.stderr.on('data', d => process.stderr.write(d.toString()));
|
||||
stream.on('close', () => conn.end());
|
||||
});
|
||||
}).connect({
|
||||
host: '103.185.47.52',
|
||||
port: 2222,
|
||||
username: 'adminbackend',
|
||||
password: 'htEo7x6LsBQiEHHH',
|
||||
});
|
||||
|
||||
conn.on('error', e => console.error('SSH Error:', e.message));
|
||||
@@ -0,0 +1,15 @@
|
||||
const mongoose = require('mongoose');
|
||||
require('dotenv').config({path: '../.env.local'});
|
||||
mongoose.connect(process.env.MONGODB_URI).then(async () => {
|
||||
const db = mongoose.connection;
|
||||
const threats = await db.collection('threats').countDocuments();
|
||||
const events = await db.collection('events').countDocuments();
|
||||
const highEvents = await db.collection('events').countDocuments({
|
||||
$or: [
|
||||
{severity: {$in: ['Critical', 'High']}},
|
||||
{category_label: 'Cybersecurity'}
|
||||
]
|
||||
});
|
||||
console.log({threats, events, highEvents});
|
||||
process.exit(0);
|
||||
}).catch(e => console.error(e));
|
||||
@@ -0,0 +1,10 @@
|
||||
const mongoose = require('mongoose');
|
||||
require('dotenv').config({path: '../.env.local'});
|
||||
mongoose.connect(process.env.MONGODB_URI).then(async () => {
|
||||
const db = mongoose.connection;
|
||||
const threats = await db.collection('threats').aggregate([{ $group: { _id: '$threat_type', count: { $sum: 1 } } }]).toArray();
|
||||
console.log('Threat types:', threats);
|
||||
const events = await db.collection('events').aggregate([{ $group: { _id: '$event_type', count: { $sum: 1 } } }]).toArray();
|
||||
console.log('Event types:', events);
|
||||
process.exit(0);
|
||||
});
|
||||
+2146
File diff suppressed because it is too large.
Load diff
@@ -5,7 +5,8 @@
|
||||
|
||||
const mongoose = require('mongoose');
|
||||
const path = require('path');
|
||||
require('dotenv').config({ path: path.join(__dirname, '../../.env.local') });
|
||||
const envFile = process.env.NODE_ENV === 'production' ? '.env.production' : '.env.local';
|
||||
require('dotenv').config({ path: path.join(__dirname, '../../', envFile) });
|
||||
|
||||
const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://127.0.0.1:27017/backone_dpi';
|
||||
|
||||
@@ -22,7 +23,7 @@ async function connectDB() {
|
||||
serverSelectionTimeoutMS: 10000,
|
||||
connectTimeoutMS: 10000,
|
||||
});
|
||||
console.log('[MongoDB] ✓ Connected successfully');
|
||||
console.log('[MongoDB] ✓ Connected successfully to', MONGODB_URI);
|
||||
const { logCapacityStats } = require('./capacityTracker');
|
||||
logCapacityStats('[MongoDB]').catch(err => console.warn('[MongoDB] Capacity log failed:', err.message));
|
||||
return;
|
||||
|
||||
+400
-400
@@ -1,400 +1,400 @@
|
||||
/**
|
||||
* generate_export.js
|
||||
*
|
||||
* Mengekspor SELURUH data dari semua tabel SQLite (database)
|
||||
* ke dalam file backone_data_export.txt
|
||||
*
|
||||
* Format output:
|
||||
* - Header metadata (tanggal, versi, jumlah tabel)
|
||||
* - Untuk setiap tabel: header section, row count, schema, dan semua data (JSON per baris)
|
||||
* - Footer summary
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const db = require('./database');
|
||||
|
||||
const OUTPUT_FILE = path.join(__dirname, '../backone_data_export.txt');
|
||||
const d = db.getDB();
|
||||
|
||||
// ─── Helpers ────────────────────────────────────────────────────────────────
|
||||
function fmtBytes(bytes) {
|
||||
if (!bytes || bytes === 0) return '0 B';
|
||||
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
let b = Math.abs(bytes);
|
||||
let i = 0;
|
||||
while (b >= 1024 && i < units.length - 1) { b /= 1024; i++; }
|
||||
return b.toFixed(2) + ' ' + units[i];
|
||||
}
|
||||
|
||||
function fmtNum(n) {
|
||||
if (n == null) return 'N/A';
|
||||
return Number(n).toLocaleString('id-ID');
|
||||
}
|
||||
|
||||
function separator(char = '═', len = 80) {
|
||||
return char.repeat(len);
|
||||
}
|
||||
|
||||
function sectionHeader(tableName, rowCount, description) {
|
||||
return [
|
||||
'',
|
||||
separator('═'),
|
||||
`[TABLE: ${tableName}]`,
|
||||
`Row Count: ${fmtNum(rowCount)}`,
|
||||
description ? `Description: ${description}` : '',
|
||||
separator('─'),
|
||||
].filter(l => l !== '').join('\n');
|
||||
}
|
||||
|
||||
// ─── Table descriptions ──────────────────────────────────────────────────────
|
||||
const TABLE_DESCRIPTIONS = {
|
||||
bandwidth_apps : 'Bandwidth per aplikasi (YouTube, Facebook, dll) dari BackOne DPI',
|
||||
bandwidth_timeline : 'Timeline bandwidth per menit (download/upload historis)',
|
||||
bittorrent_info_hashes: 'Deteksi aktivitas BitTorrent berdasarkan info hash',
|
||||
countries : 'Distribusi traffic berdasarkan negara tujuan',
|
||||
devices : 'Daftar perangkat (IP/MAC) beserta bandwidth & OS',
|
||||
dhcp_fingerprints : 'Fingerprint DHCP untuk identifikasi tipe device',
|
||||
discovered_os : 'OS yang terdeteksi dari traffic scanning',
|
||||
dns_stats : 'Query DNS teratas dan statistik resolusi domain',
|
||||
events : 'Event log dari BackOne agent (koneksi, peringatan, dll)',
|
||||
flows : 'Data aliran jaringan per-sesi (src IP, dst IP, aplikasi, domain, bytes)',
|
||||
flow_origins : 'Asal flow: lokal (LAN) atau eksternal (WAN)',
|
||||
flow_types : 'Tipe flow: TCP, UDP, ICMP, dll',
|
||||
http_user_agents : 'HTTP User-Agent yang terdeteksi (browser, OS, framework)',
|
||||
intel_crypto_mining : 'Deteksi aktivitas crypto mining (pool host, protokol)',
|
||||
intel_device_discovery: 'Penemuan perangkat baru di jaringan (tipe, OS, manufaktur)',
|
||||
intel_encryption_audit: 'Audit enkripsi traffic per perangkat (encrypted%, risk level)',
|
||||
intel_insecure_protocols: 'Protokol tidak aman yang terdeteksi (HTTP, Telnet, FTP, dll)',
|
||||
intel_ip_reputation : 'Reputasi IP eksternal (blacklist, threat score)',
|
||||
intel_server_discovery: 'Server yang terdeteksi (HTTPS, SSH, HTTP, dll)',
|
||||
intel_tor_detection : 'Deteksi penggunaan jaringan Tor',
|
||||
intel_unencrypted_passwords: 'Deteksi pengiriman password dalam bentuk plaintext',
|
||||
intel_vpn_detection : 'Deteksi penggunaan VPN (OpenVPN, WireGuard, dll)',
|
||||
interfaces : 'Interface jaringan per agent (WAN/LAN, bandwidth)',
|
||||
ip_versions : 'Distribusi traffic IPv4 vs IPv6',
|
||||
mac_bandwidth : 'Bandwidth per MAC address perangkat',
|
||||
mdns_hostnames : 'mDNS hostname yang terdeteksi di jaringan lokal',
|
||||
netbios_hostnames : 'NetBIOS hostname (nama komputer Windows)',
|
||||
protocols : 'Distribusi protokol jaringan (port usage)',
|
||||
quic_hostnames : 'Hostname via QUIC/HTTP3 (Google, Cloudflare, dll)',
|
||||
regions : 'Distribusi traffic berdasarkan region/kota tujuan',
|
||||
remote_ips : 'IP remote teratas yang diakses perangkat',
|
||||
sni_hostnames : 'Server Name Indication dari koneksi TLS',
|
||||
ssh_versions : 'Versi SSH yang terdeteksi di jaringan',
|
||||
ssl_server_cn : 'Common Name sertifikat SSL server',
|
||||
threats : 'Ancaman keamanan terdeteksi (threat alerts)',
|
||||
tls_ciphers : 'Cipher suite TLS yang digunakan',
|
||||
tls_security : 'Tingkat keamanan TLS (Modern, Compatible, Old)',
|
||||
tls_versions : 'Versi TLS yang digunakan (1.0, 1.2, 1.3)',
|
||||
vlans : 'VLAN yang terdeteksi di jaringan',
|
||||
};
|
||||
|
||||
// ─── Main Export Logic ───────────────────────────────────────────────────────
|
||||
async function main() {
|
||||
console.log('🚀 Memulai export data...');
|
||||
|
||||
const exportDate = new Date().toISOString();
|
||||
const lines = [];
|
||||
|
||||
// ── File Header ──────────────────────────────────────────────────────────
|
||||
lines.push(separator('═'));
|
||||
lines.push(' BACKONE DATA EXPORT');
|
||||
lines.push(' Seluruh data hasil parsing dari BackOne API');
|
||||
lines.push(separator('─'));
|
||||
lines.push(` Export Date: ${exportDate}`);
|
||||
lines.push(` Generated by: generate_export.js`);
|
||||
lines.push(` Source: database (SQLite lokal)`);
|
||||
lines.push(` API Base: BackOne API Service`);
|
||||
lines.push(` Format: Per-tabel, data JSON satu record per baris (JSONL)`);
|
||||
lines.push(separator('─'));
|
||||
|
||||
// ── Get all tables ────────────────────────────────────────────────────────
|
||||
const tables = d.prepare(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name"
|
||||
).all().map(r => r.name);
|
||||
|
||||
lines.push(` Total Tables: ${tables.length}`);
|
||||
lines.push(separator('═'));
|
||||
lines.push('');
|
||||
|
||||
// ── Table of Contents ─────────────────────────────────────────────────────
|
||||
lines.push('TABLE OF CONTENTS');
|
||||
lines.push(separator('─', 40));
|
||||
let totalRows = 0;
|
||||
const tableSummaries = [];
|
||||
for (const tableName of tables) {
|
||||
const cnt = d.prepare(`SELECT COUNT(*) as c FROM ${tableName}`).get().c;
|
||||
totalRows += cnt;
|
||||
const desc = TABLE_DESCRIPTIONS[tableName] || '-';
|
||||
lines.push(` ${tableName.padEnd(35)} ${String(cnt).padStart(8)} rows`);
|
||||
tableSummaries.push({ name: tableName, count: cnt, description: desc });
|
||||
}
|
||||
lines.push(separator('─', 40));
|
||||
lines.push(` ${'TOTAL'.padEnd(35)} ${String(totalRows).padStart(8)} rows`);
|
||||
lines.push('');
|
||||
|
||||
// ── Per-Table Export ──────────────────────────────────────────────────────
|
||||
for (const { name: tableName, count, description } of tableSummaries) {
|
||||
console.log(` 📋 Exporting: ${tableName} (${fmtNum(count)} rows)...`);
|
||||
|
||||
// Section header
|
||||
lines.push(sectionHeader(tableName, count, description));
|
||||
|
||||
// Schema
|
||||
const cols = d.prepare(`PRAGMA table_info(${tableName})`).all();
|
||||
lines.push('Schema:');
|
||||
cols.forEach(c => {
|
||||
lines.push(` - ${c.name} [${c.type || 'TEXT'}]${c.notnull ? ' NOT NULL' : ''}${c.pk ? ' PRIMARY KEY' : ''}`);
|
||||
});
|
||||
lines.push('');
|
||||
|
||||
// Statistics for numeric columns
|
||||
const numericCols = cols.filter(c =>
|
||||
['INTEGER', 'REAL', 'NUMERIC'].includes((c.type || '').toUpperCase()) &&
|
||||
!['id'].includes(c.name.toLowerCase())
|
||||
);
|
||||
|
||||
if (count > 0 && numericCols.length > 0) {
|
||||
lines.push('Statistics:');
|
||||
for (const col of numericCols.slice(0, 5)) { // max 5 numeric cols
|
||||
try {
|
||||
const stat = d.prepare(`
|
||||
SELECT MIN(${col.name}) as min, MAX(${col.name}) as max,
|
||||
AVG(${col.name}) as avg, SUM(${col.name}) as total
|
||||
FROM ${tableName}
|
||||
`).get();
|
||||
if (stat && stat.max !== null) {
|
||||
lines.push(` ${col.name}: min=${fmtNum(stat.min)} max=${fmtNum(stat.max)} avg=${Number(stat.avg || 0).toFixed(2)} total=${fmtNum(stat.total)}`);
|
||||
}
|
||||
} catch(e) { /* skip */ }
|
||||
}
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
// Data rows (ALL rows)
|
||||
if (count === 0) {
|
||||
lines.push('(No data)');
|
||||
} else {
|
||||
lines.push(`Data (${fmtNum(count)} records):`);
|
||||
const rows = d.prepare(`SELECT * FROM ${tableName}`).all();
|
||||
for (const row of rows) {
|
||||
lines.push(JSON.stringify(row));
|
||||
}
|
||||
}
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
// ── Agent-specific sections (derived from flows) ───────────────────────────
|
||||
lines.push('');
|
||||
lines.push(separator('═'));
|
||||
lines.push('[DERIVED: AGENT ANALYSIS]');
|
||||
lines.push('Description: Analisis traffic per agent berdasarkan MAC address dari flows table');
|
||||
lines.push(separator('─'));
|
||||
|
||||
const AGENT_MAC_MAP = {
|
||||
'2F-TF-1D-GK': { label: 'JRP Cibubur', macs: ['60:be:b4:1f:05:96'] },
|
||||
'8A-V3-PB-85': { label: 'IFG LT.18', macs: ['04:f4:1c:ce:c2:e6'] },
|
||||
'F6-2V-DT-8A': { label: 'CPI Balaraja', macs: ['2c:7b:a0:d8:86:91', '16:11:ac:73:34:1d', 'bc:45:5b:ca:d5:be', 'de:ed:cc:57:58:34', 'f4:6d:3f:ef:01:a0', '60:be:b4:29:d3:36'] },
|
||||
};
|
||||
|
||||
for (const [uuid, agent] of Object.entries(AGENT_MAC_MAP)) {
|
||||
lines.push('');
|
||||
lines.push(`Agent: ${agent.label} (${uuid})`);
|
||||
lines.push(`MACs: ${agent.macs.join(', ')}`);
|
||||
lines.push(separator('─', 40));
|
||||
|
||||
const ph = agent.macs.map(() => '?').join(',');
|
||||
|
||||
// Summary
|
||||
const sumRow = d.prepare(`
|
||||
SELECT COUNT(DISTINCT src_ip) AS device_count, COUNT(*) AS flow_count,
|
||||
SUM(bytes_download) AS total_dl, SUM(bytes_upload) AS total_ul
|
||||
FROM flows WHERE src_mac IN (${ph})
|
||||
`).get(...agent.macs);
|
||||
|
||||
lines.push(` Devices: ${fmtNum(sumRow.device_count)}`);
|
||||
lines.push(` Total Flows: ${fmtNum(sumRow.flow_count)}`);
|
||||
lines.push(` Total Download: ${fmtBytes(sumRow.total_dl)}`);
|
||||
lines.push(` Total Upload: ${fmtBytes(sumRow.total_ul)}`);
|
||||
|
||||
// Top apps
|
||||
const apps = d.prepare(`
|
||||
SELECT app_label, SUM(bytes_download) AS dl, SUM(bytes_upload) AS ul, COUNT(*) AS cnt
|
||||
FROM flows WHERE src_mac IN (${ph}) AND app_label IS NOT NULL
|
||||
GROUP BY app_label ORDER BY dl DESC LIMIT 10
|
||||
`).all(...agent.macs);
|
||||
|
||||
lines.push(` Top Applications:`);
|
||||
apps.forEach((a, i) => {
|
||||
lines.push(` ${String(i+1).padStart(2)}. ${(a.app_label||'?').padEnd(30)} DL:${fmtBytes(a.dl).padStart(12)} UL:${fmtBytes(a.ul).padStart(12)} Flows:${a.cnt}`);
|
||||
});
|
||||
|
||||
// Top devices
|
||||
const devs = d.prepare(`
|
||||
SELECT src_ip, SUM(bytes_download) AS dl, MAX(last_seen) AS last
|
||||
FROM flows WHERE src_mac IN (${ph})
|
||||
GROUP BY src_ip ORDER BY dl DESC LIMIT 10
|
||||
`).all(...agent.macs);
|
||||
|
||||
lines.push(` Top Devices:`);
|
||||
devs.forEach((d2, i) => {
|
||||
lines.push(` ${String(i+1).padStart(2)}. ${(d2.src_ip||'?').padEnd(20)} DL:${fmtBytes(d2.dl).padStart(12)} Last:${d2.last||'-'}`);
|
||||
});
|
||||
}
|
||||
|
||||
// ── Bandwidth Apps Summary ─────────────────────────────────────────────────
|
||||
lines.push('');
|
||||
lines.push(separator('═'));
|
||||
lines.push('[DERIVED: BANDWIDTH APPS LATEST SNAPSHOT]');
|
||||
lines.push('Description: Snapshot terakhir bandwidth per aplikasi (nilai aktual, bukan akumulasi)');
|
||||
lines.push(separator('─'));
|
||||
|
||||
const latestBwSnap = d.prepare('SELECT MAX(fetched_at) as t FROM bandwidth_apps').get()?.t;
|
||||
if (latestBwSnap) {
|
||||
lines.push(`Latest Snapshot: ${latestBwSnap}`);
|
||||
const bwApps = d.prepare('SELECT app_label, category, download, upload, total, flow_count FROM bandwidth_apps WHERE fetched_at = ? ORDER BY download DESC').all(latestBwSnap);
|
||||
lines.push(`Total Apps: ${bwApps.length}`);
|
||||
lines.push('');
|
||||
bwApps.forEach((a, i) => {
|
||||
lines.push(` ${String(i+1).padStart(3)}. ${(a.app_label||'?').padEnd(30)} [${(a.category||'?').padEnd(20)}] DL:${fmtBytes(a.download).padStart(12)} UL:${fmtBytes(a.upload).padStart(12)} Flows:${fmtNum(a.flow_count)}`);
|
||||
});
|
||||
}
|
||||
|
||||
// ── Encryption Audit Summary ───────────────────────────────────────────────
|
||||
lines.push('');
|
||||
lines.push(separator('═'));
|
||||
lines.push('[DERIVED: ENCRYPTION RISK SUMMARY]');
|
||||
lines.push('Description: Distribusi risk level enkripsi per perangkat (snapshot terbaru)');
|
||||
lines.push(separator('─'));
|
||||
|
||||
const latestEncSnap = d.prepare('SELECT MAX(fetched_at) as t FROM intel_encryption_audit').get()?.t;
|
||||
if (latestEncSnap) {
|
||||
const riskDist = d.prepare(`
|
||||
SELECT risk_level, COUNT(*) as cnt, AVG(encrypted_pct) as avg_enc
|
||||
FROM intel_encryption_audit WHERE fetched_at = ?
|
||||
GROUP BY risk_level ORDER BY cnt DESC
|
||||
`).all(latestEncSnap);
|
||||
|
||||
lines.push(`Latest Snapshot: ${latestEncSnap}`);
|
||||
lines.push('Risk Distribution:');
|
||||
riskDist.forEach(r => {
|
||||
lines.push(` ${(r.risk_level||'Unknown').padEnd(15)} ${String(r.cnt).padStart(5)} devices avg encrypted: ${Number(r.avg_enc||0).toFixed(1)}%`);
|
||||
});
|
||||
|
||||
// Highest risk devices
|
||||
lines.push('');
|
||||
lines.push('Critical Risk Devices (0% encrypted):');
|
||||
const critDevs = d.prepare(`
|
||||
SELECT ip_address, mac_address, device_label, encrypted_pct, total
|
||||
FROM intel_encryption_audit WHERE fetched_at = ? AND risk_level = 'Critical'
|
||||
ORDER BY total DESC LIMIT 20
|
||||
`).all(latestEncSnap);
|
||||
critDevs.forEach(r => {
|
||||
lines.push(JSON.stringify(r));
|
||||
});
|
||||
}
|
||||
|
||||
// ── DNS Top Domains ────────────────────────────────────────────────────────
|
||||
lines.push('');
|
||||
lines.push(separator('═'));
|
||||
lines.push('[DERIVED: TOP DNS DOMAINS]');
|
||||
lines.push('Description: Domain paling sering diquery dari DNS stats');
|
||||
lines.push(separator('─'));
|
||||
|
||||
const latestDnsSnap = d.prepare('SELECT MAX(fetched_at) as t FROM dns_queries').get()?.t;
|
||||
if (latestDnsSnap) {
|
||||
const dnsRows = d.prepare('SELECT * FROM dns_queries WHERE fetched_at = ? ORDER BY query_count DESC LIMIT 30').all(latestDnsSnap);
|
||||
|
||||
lines.push(`Latest Snapshot: ${latestDnsSnap}`);
|
||||
dnsRows.forEach(r => lines.push(JSON.stringify(r)));
|
||||
}
|
||||
|
||||
// ── IP Reputation Blacklisted ─────────────────────────────────────────────
|
||||
lines.push('');
|
||||
lines.push(separator('═'));
|
||||
lines.push('[DERIVED: BLACKLISTED IP ADDRESSES]');
|
||||
lines.push('Description: IP address yang terdeteksi blacklisted (dari intel_ip_reputation)');
|
||||
lines.push(separator('─'));
|
||||
|
||||
const latestRepSnap = d.prepare('SELECT MAX(fetched_at) as t FROM intel_ip_reputation').get()?.t;
|
||||
if (latestRepSnap) {
|
||||
const blacklisted = d.prepare('SELECT * FROM intel_ip_reputation WHERE fetched_at = ? AND blacklisted = 1').all(latestRepSnap);
|
||||
lines.push(`Latest Snapshot: ${latestRepSnap}`);
|
||||
lines.push(`Blacklisted count: ${blacklisted.length}`);
|
||||
blacklisted.forEach(r => lines.push(JSON.stringify(r)));
|
||||
}
|
||||
|
||||
// ── Flows: Active Sessions Summary ────────────────────────────────────────
|
||||
lines.push('');
|
||||
lines.push(separator('═'));
|
||||
lines.push('[DERIVED: ACTIVE FLOWS SUMMARY]');
|
||||
lines.push('Description: Ringkasan aliran jaringan aktif (50 terbaru per download)');
|
||||
lines.push(separator('─'));
|
||||
|
||||
const flowSummary = d.prepare(`
|
||||
SELECT COUNT(*) as total_flows,
|
||||
COUNT(DISTINCT src_ip) as unique_src_ips,
|
||||
COUNT(DISTINCT dst_ip) as unique_dst_ips,
|
||||
COUNT(DISTINCT src_mac) as unique_macs,
|
||||
SUM(bytes_download) as total_dl,
|
||||
SUM(bytes_upload) as total_ul,
|
||||
MIN(first_seen) as earliest,
|
||||
MAX(last_seen) as latest
|
||||
FROM flows
|
||||
`).get();
|
||||
|
||||
lines.push(`Total Flows in DB: ${fmtNum(flowSummary.total_flows)}`);
|
||||
lines.push(`Unique Source IPs: ${fmtNum(flowSummary.unique_src_ips)}`);
|
||||
lines.push(`Unique Dest IPs: ${fmtNum(flowSummary.unique_dst_ips)}`);
|
||||
lines.push(`Unique MAC Addresses: ${fmtNum(flowSummary.unique_macs)}`);
|
||||
lines.push(`Total Download: ${fmtBytes(flowSummary.total_dl)}`);
|
||||
lines.push(`Total Upload: ${fmtBytes(flowSummary.total_ul)}`);
|
||||
lines.push(`Data from: ${flowSummary.earliest}`);
|
||||
lines.push(`Data to: ${flowSummary.latest}`);
|
||||
lines.push('');
|
||||
|
||||
// Top 50 flows by download
|
||||
lines.push('Top 50 Flows by Download:');
|
||||
const topFlows = d.prepare('SELECT * FROM flows ORDER BY bytes_download DESC LIMIT 50').all();
|
||||
topFlows.forEach(r => lines.push(JSON.stringify(r)));
|
||||
|
||||
// ── Unencrypted Password Events ───────────────────────────────────────────
|
||||
lines.push('');
|
||||
lines.push(separator('═'));
|
||||
lines.push('[DERIVED: UNENCRYPTED PASSWORD DETECTIONS]');
|
||||
lines.push('Description: Kejadian pengiriman credential dalam plaintext (HIGH severity)');
|
||||
lines.push(separator('─'));
|
||||
|
||||
const unencPwdHigh = d.prepare(`
|
||||
SELECT ip_address, mac_address, dst_ip, dst_port, protocol, username, download, upload, severity, detected_at
|
||||
FROM intel_unencrypted_passwords ORDER BY detected_at DESC
|
||||
`).all();
|
||||
lines.push(`Total detections: ${unencPwdHigh.length}`);
|
||||
unencPwdHigh.forEach(r => lines.push(JSON.stringify(r)));
|
||||
|
||||
// ── Footer ────────────────────────────────────────────────────────────────
|
||||
lines.push('');
|
||||
lines.push(separator('═'));
|
||||
lines.push(' END OF EXPORT');
|
||||
lines.push(` Generated at: ${new Date().toISOString()}`);
|
||||
lines.push(` Total lines: ${lines.length + 3}`);
|
||||
lines.push(separator('═'));
|
||||
|
||||
// Write to file
|
||||
const output = lines.join('\n');
|
||||
fs.writeFileSync(OUTPUT_FILE, output, 'utf-8');
|
||||
|
||||
const stats = fs.statSync(OUTPUT_FILE);
|
||||
console.log(`\n✅ Export selesai!`);
|
||||
console.log(` File: ${OUTPUT_FILE}`);
|
||||
console.log(` Size: ${fmtBytes(stats.size)}`);
|
||||
console.log(` Lines: ${fmtNum(lines.length)}`);
|
||||
console.log(` Tables: ${tables.length}`);
|
||||
console.log(` Total Rows: ${fmtNum(totalRows)}`);
|
||||
}
|
||||
|
||||
main().catch(e => {
|
||||
console.error('❌ Export FAILED:', e);
|
||||
process.exit(1);
|
||||
});
|
||||
/**
|
||||
* generate_export.js
|
||||
*
|
||||
* Mengekspor SELURUH data dari semua tabel SQLite (database)
|
||||
* ke dalam file backone_data_export.txt
|
||||
*
|
||||
* Format output:
|
||||
* - Header metadata (tanggal, versi, jumlah tabel)
|
||||
* - Untuk setiap tabel: header section, row count, schema, dan semua data (JSON per baris)
|
||||
* - Footer summary
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const db = require('./database');
|
||||
|
||||
const OUTPUT_FILE = path.join(__dirname, '../backone_data_export.txt');
|
||||
const d = db.getDB();
|
||||
|
||||
// ─── Helpers ────────────────────────────────────────────────────────────────
|
||||
function fmtBytes(bytes) {
|
||||
if (!bytes || bytes === 0) return '0 B';
|
||||
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
||||
let b = Math.abs(bytes);
|
||||
let i = 0;
|
||||
while (b >= 1024 && i < units.length - 1) { b /= 1024; i++; }
|
||||
return b.toFixed(2) + ' ' + units[i];
|
||||
}
|
||||
|
||||
function fmtNum(n) {
|
||||
if (n == null) return 'N/A';
|
||||
return Number(n).toLocaleString('id-ID');
|
||||
}
|
||||
|
||||
function separator(char = '═', len = 80) {
|
||||
return char.repeat(len);
|
||||
}
|
||||
|
||||
function sectionHeader(tableName, rowCount, description) {
|
||||
return [
|
||||
'',
|
||||
separator('═'),
|
||||
`[TABLE: ${tableName}]`,
|
||||
`Row Count: ${fmtNum(rowCount)}`,
|
||||
description ? `Description: ${description}` : '',
|
||||
separator('─'),
|
||||
].filter(l => l !== '').join('\n');
|
||||
}
|
||||
|
||||
// ─── Table descriptions ──────────────────────────────────────────────────────
|
||||
const TABLE_DESCRIPTIONS = {
|
||||
bandwidth_apps : 'Bandwidth per aplikasi (YouTube, Facebook, dll) dari BackOne DPI',
|
||||
bandwidth_timeline : 'Timeline bandwidth per menit (download/upload historis)',
|
||||
bittorrent_info_hashes: 'Deteksi aktivitas BitTorrent berdasarkan info hash',
|
||||
countries : 'Distribusi traffic berdasarkan negara tujuan',
|
||||
devices : 'Daftar perangkat (IP/MAC) beserta bandwidth & OS',
|
||||
dhcp_fingerprints : 'Fingerprint DHCP untuk identifikasi tipe device',
|
||||
discovered_os : 'OS yang terdeteksi dari traffic scanning',
|
||||
dns_stats : 'Query DNS teratas dan statistik resolusi domain',
|
||||
events : 'Event log dari BackOne agent (koneksi, peringatan, dll)',
|
||||
flows : 'Data aliran jaringan per-sesi (src IP, dst IP, aplikasi, domain, bytes)',
|
||||
flow_origins : 'Asal flow: lokal (LAN) atau eksternal (WAN)',
|
||||
flow_types : 'Tipe flow: TCP, UDP, ICMP, dll',
|
||||
http_user_agents : 'HTTP User-Agent yang terdeteksi (browser, OS, framework)',
|
||||
intel_crypto_mining : 'Deteksi aktivitas crypto mining (pool host, protokol)',
|
||||
intel_device_discovery: 'Penemuan perangkat baru di jaringan (tipe, OS, manufaktur)',
|
||||
intel_encryption_audit: 'Audit enkripsi traffic per perangkat (encrypted%, risk level)',
|
||||
intel_insecure_protocols: 'Protokol tidak aman yang terdeteksi (HTTP, Telnet, FTP, dll)',
|
||||
intel_ip_reputation : 'Reputasi IP eksternal (blacklist, threat score)',
|
||||
intel_server_discovery: 'Server yang terdeteksi (HTTPS, SSH, HTTP, dll)',
|
||||
intel_tor_detection : 'Deteksi penggunaan jaringan Tor',
|
||||
intel_unencrypted_passwords: 'Deteksi pengiriman password dalam bentuk plaintext',
|
||||
intel_vpn_detection : 'Deteksi penggunaan VPN (OpenVPN, WireGuard, dll)',
|
||||
interfaces : 'Interface jaringan per agent (WAN/LAN, bandwidth)',
|
||||
ip_versions : 'Distribusi traffic IPv4 vs IPv6',
|
||||
mac_bandwidth : 'Bandwidth per MAC address perangkat',
|
||||
mdns_hostnames : 'mDNS hostname yang terdeteksi di jaringan lokal',
|
||||
netbios_hostnames : 'NetBIOS hostname (nama komputer Windows)',
|
||||
protocols : 'Distribusi protokol jaringan (port usage)',
|
||||
quic_hostnames : 'Hostname via QUIC/HTTP3 (Google, Cloudflare, dll)',
|
||||
regions : 'Distribusi traffic berdasarkan region/kota tujuan',
|
||||
remote_ips : 'IP remote teratas yang diakses perangkat',
|
||||
sni_hostnames : 'Server Name Indication dari koneksi TLS',
|
||||
ssh_versions : 'Versi SSH yang terdeteksi di jaringan',
|
||||
ssl_server_cn : 'Common Name sertifikat SSL server',
|
||||
threats : 'Ancaman keamanan terdeteksi (threat alerts)',
|
||||
tls_ciphers : 'Cipher suite TLS yang digunakan',
|
||||
tls_security : 'Tingkat keamanan TLS (Modern, Compatible, Old)',
|
||||
tls_versions : 'Versi TLS yang digunakan (1.0, 1.2, 1.3)',
|
||||
vlans : 'VLAN yang terdeteksi di jaringan',
|
||||
};
|
||||
|
||||
// ─── Main Export Logic ───────────────────────────────────────────────────────
|
||||
async function main() {
|
||||
console.log('🚀 Memulai export data...');
|
||||
|
||||
const exportDate = new Date().toISOString();
|
||||
const lines = [];
|
||||
|
||||
// ── File Header ──────────────────────────────────────────────────────────
|
||||
lines.push(separator('═'));
|
||||
lines.push(' BACKONE DATA EXPORT');
|
||||
lines.push(' Seluruh data hasil parsing dari BackOne API');
|
||||
lines.push(separator('─'));
|
||||
lines.push(` Export Date: ${exportDate}`);
|
||||
lines.push(` Generated by: generate_export.js`);
|
||||
lines.push(` Source: database (SQLite lokal)`);
|
||||
lines.push(` API Base: BackOne API Service`);
|
||||
lines.push(` Format: Per-tabel, data JSON satu record per baris (JSONL)`);
|
||||
lines.push(separator('─'));
|
||||
|
||||
// ── Get all tables ────────────────────────────────────────────────────────
|
||||
const tables = d.prepare(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name"
|
||||
).all().map(r => r.name);
|
||||
|
||||
lines.push(` Total Tables: ${tables.length}`);
|
||||
lines.push(separator('═'));
|
||||
lines.push('');
|
||||
|
||||
// ── Table of Contents ─────────────────────────────────────────────────────
|
||||
lines.push('TABLE OF CONTENTS');
|
||||
lines.push(separator('─', 40));
|
||||
let totalRows = 0;
|
||||
const tableSummaries = [];
|
||||
for (const tableName of tables) {
|
||||
const cnt = d.prepare(`SELECT COUNT(*) as c FROM ${tableName}`).get().c;
|
||||
totalRows += cnt;
|
||||
const desc = TABLE_DESCRIPTIONS[tableName] || '-';
|
||||
lines.push(` ${tableName.padEnd(35)} ${String(cnt).padStart(8)} rows`);
|
||||
tableSummaries.push({ name: tableName, count: cnt, description: desc });
|
||||
}
|
||||
lines.push(separator('─', 40));
|
||||
lines.push(` ${'TOTAL'.padEnd(35)} ${String(totalRows).padStart(8)} rows`);
|
||||
lines.push('');
|
||||
|
||||
// ── Per-Table Export ──────────────────────────────────────────────────────
|
||||
for (const { name: tableName, count, description } of tableSummaries) {
|
||||
console.log(` 📋 Exporting: ${tableName} (${fmtNum(count)} rows)...`);
|
||||
|
||||
// Section header
|
||||
lines.push(sectionHeader(tableName, count, description));
|
||||
|
||||
// Schema
|
||||
const cols = d.prepare(`PRAGMA table_info(${tableName})`).all();
|
||||
lines.push('Schema:');
|
||||
cols.forEach(c => {
|
||||
lines.push(` - ${c.name} [${c.type || 'TEXT'}]${c.notnull ? ' NOT NULL' : ''}${c.pk ? ' PRIMARY KEY' : ''}`);
|
||||
});
|
||||
lines.push('');
|
||||
|
||||
// Statistics for numeric columns
|
||||
const numericCols = cols.filter(c =>
|
||||
['INTEGER', 'REAL', 'NUMERIC'].includes((c.type || '').toUpperCase()) &&
|
||||
!['id'].includes(c.name.toLowerCase())
|
||||
);
|
||||
|
||||
if (count > 0 && numericCols.length > 0) {
|
||||
lines.push('Statistics:');
|
||||
for (const col of numericCols.slice(0, 5)) { // max 5 numeric cols
|
||||
try {
|
||||
const stat = d.prepare(`
|
||||
SELECT MIN(${col.name}) as min, MAX(${col.name}) as max,
|
||||
AVG(${col.name}) as avg, SUM(${col.name}) as total
|
||||
FROM ${tableName}
|
||||
`).get();
|
||||
if (stat && stat.max !== null) {
|
||||
lines.push(` ${col.name}: min=${fmtNum(stat.min)} max=${fmtNum(stat.max)} avg=${Number(stat.avg || 0).toFixed(2)} total=${fmtNum(stat.total)}`);
|
||||
}
|
||||
} catch(e) { /* skip */ }
|
||||
}
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
// Data rows (ALL rows)
|
||||
if (count === 0) {
|
||||
lines.push('(No data)');
|
||||
} else {
|
||||
lines.push(`Data (${fmtNum(count)} records):`);
|
||||
const rows = d.prepare(`SELECT * FROM ${tableName}`).all();
|
||||
for (const row of rows) {
|
||||
lines.push(JSON.stringify(row));
|
||||
}
|
||||
}
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
// ── Agent-specific sections (derived from flows) ───────────────────────────
|
||||
lines.push('');
|
||||
lines.push(separator('═'));
|
||||
lines.push('[DERIVED: AGENT ANALYSIS]');
|
||||
lines.push('Description: Analisis traffic per agent berdasarkan MAC address dari flows table');
|
||||
lines.push(separator('─'));
|
||||
|
||||
const AGENT_MAC_MAP = {
|
||||
'2F-TF-1D-GK': { label: 'JRP Cibubur', macs: ['60:be:b4:1f:05:96'] },
|
||||
'8A-V3-PB-85': { label: 'IFG LT.18', macs: ['04:f4:1c:ce:c2:e6'] },
|
||||
'F6-2V-DT-8A': { label: 'CPI Balaraja', macs: ['2c:7b:a0:d8:86:91', '16:11:ac:73:34:1d', 'bc:45:5b:ca:d5:be', 'de:ed:cc:57:58:34', 'f4:6d:3f:ef:01:a0', '60:be:b4:29:d3:36'] },
|
||||
};
|
||||
|
||||
for (const [uuid, agent] of Object.entries(AGENT_MAC_MAP)) {
|
||||
lines.push('');
|
||||
lines.push(`Agent: ${agent.label} (${uuid})`);
|
||||
lines.push(`MACs: ${agent.macs.join(', ')}`);
|
||||
lines.push(separator('─', 40));
|
||||
|
||||
const ph = agent.macs.map(() => '?').join(',');
|
||||
|
||||
// Summary
|
||||
const sumRow = d.prepare(`
|
||||
SELECT COUNT(DISTINCT src_ip) AS device_count, COUNT(*) AS flow_count,
|
||||
SUM(bytes_download) AS total_dl, SUM(bytes_upload) AS total_ul
|
||||
FROM flows WHERE src_mac IN (${ph})
|
||||
`).get(...agent.macs);
|
||||
|
||||
lines.push(` Devices: ${fmtNum(sumRow.device_count)}`);
|
||||
lines.push(` Total Flows: ${fmtNum(sumRow.flow_count)}`);
|
||||
lines.push(` Total Download: ${fmtBytes(sumRow.total_dl)}`);
|
||||
lines.push(` Total Upload: ${fmtBytes(sumRow.total_ul)}`);
|
||||
|
||||
// Top apps
|
||||
const apps = d.prepare(`
|
||||
SELECT app_label, SUM(bytes_download) AS dl, SUM(bytes_upload) AS ul, COUNT(*) AS cnt
|
||||
FROM flows WHERE src_mac IN (${ph}) AND app_label IS NOT NULL
|
||||
GROUP BY app_label ORDER BY dl DESC LIMIT 10
|
||||
`).all(...agent.macs);
|
||||
|
||||
lines.push(` Top Applications:`);
|
||||
apps.forEach((a, i) => {
|
||||
lines.push(` ${String(i+1).padStart(2)}. ${(a.app_label||'?').padEnd(30)} DL:${fmtBytes(a.dl).padStart(12)} UL:${fmtBytes(a.ul).padStart(12)} Flows:${a.cnt}`);
|
||||
});
|
||||
|
||||
// Top devices
|
||||
const devs = d.prepare(`
|
||||
SELECT src_ip, SUM(bytes_download) AS dl, MAX(last_seen) AS last
|
||||
FROM flows WHERE src_mac IN (${ph})
|
||||
GROUP BY src_ip ORDER BY dl DESC LIMIT 10
|
||||
`).all(...agent.macs);
|
||||
|
||||
lines.push(` Top Devices:`);
|
||||
devs.forEach((d2, i) => {
|
||||
lines.push(` ${String(i+1).padStart(2)}. ${(d2.src_ip||'?').padEnd(20)} DL:${fmtBytes(d2.dl).padStart(12)} Last:${d2.last||'-'}`);
|
||||
});
|
||||
}
|
||||
|
||||
// ── Bandwidth Apps Summary ─────────────────────────────────────────────────
|
||||
lines.push('');
|
||||
lines.push(separator('═'));
|
||||
lines.push('[DERIVED: BANDWIDTH APPS LATEST SNAPSHOT]');
|
||||
lines.push('Description: Snapshot terakhir bandwidth per aplikasi (nilai aktual, bukan akumulasi)');
|
||||
lines.push(separator('─'));
|
||||
|
||||
const latestBwSnap = d.prepare('SELECT MAX(fetched_at) as t FROM bandwidth_apps').get()?.t;
|
||||
if (latestBwSnap) {
|
||||
lines.push(`Latest Snapshot: ${latestBwSnap}`);
|
||||
const bwApps = d.prepare('SELECT app_label, category, download, upload, total, flow_count FROM bandwidth_apps WHERE fetched_at = ? ORDER BY download DESC').all(latestBwSnap);
|
||||
lines.push(`Total Apps: ${bwApps.length}`);
|
||||
lines.push('');
|
||||
bwApps.forEach((a, i) => {
|
||||
lines.push(` ${String(i+1).padStart(3)}. ${(a.app_label||'?').padEnd(30)} [${(a.category||'?').padEnd(20)}] DL:${fmtBytes(a.download).padStart(12)} UL:${fmtBytes(a.upload).padStart(12)} Flows:${fmtNum(a.flow_count)}`);
|
||||
});
|
||||
}
|
||||
|
||||
// ── Encryption Audit Summary ───────────────────────────────────────────────
|
||||
lines.push('');
|
||||
lines.push(separator('═'));
|
||||
lines.push('[DERIVED: ENCRYPTION RISK SUMMARY]');
|
||||
lines.push('Description: Distribusi risk level enkripsi per perangkat (snapshot terbaru)');
|
||||
lines.push(separator('─'));
|
||||
|
||||
const latestEncSnap = d.prepare('SELECT MAX(fetched_at) as t FROM intel_encryption_audit').get()?.t;
|
||||
if (latestEncSnap) {
|
||||
const riskDist = d.prepare(`
|
||||
SELECT risk_level, COUNT(*) as cnt, AVG(encrypted_pct) as avg_enc
|
||||
FROM intel_encryption_audit WHERE fetched_at = ?
|
||||
GROUP BY risk_level ORDER BY cnt DESC
|
||||
`).all(latestEncSnap);
|
||||
|
||||
lines.push(`Latest Snapshot: ${latestEncSnap}`);
|
||||
lines.push('Risk Distribution:');
|
||||
riskDist.forEach(r => {
|
||||
lines.push(` ${(r.risk_level||'Unknown').padEnd(15)} ${String(r.cnt).padStart(5)} devices avg encrypted: ${Number(r.avg_enc||0).toFixed(1)}%`);
|
||||
});
|
||||
|
||||
// Highest risk devices
|
||||
lines.push('');
|
||||
lines.push('Critical Risk Devices (0% encrypted):');
|
||||
const critDevs = d.prepare(`
|
||||
SELECT ip_address, mac_address, device_label, encrypted_pct, total
|
||||
FROM intel_encryption_audit WHERE fetched_at = ? AND risk_level = 'Critical'
|
||||
ORDER BY total DESC LIMIT 20
|
||||
`).all(latestEncSnap);
|
||||
critDevs.forEach(r => {
|
||||
lines.push(JSON.stringify(r));
|
||||
});
|
||||
}
|
||||
|
||||
// ── DNS Top Domains ────────────────────────────────────────────────────────
|
||||
lines.push('');
|
||||
lines.push(separator('═'));
|
||||
lines.push('[DERIVED: TOP DNS DOMAINS]');
|
||||
lines.push('Description: Domain paling sering diquery dari DNS stats');
|
||||
lines.push(separator('─'));
|
||||
|
||||
const latestDnsSnap = d.prepare('SELECT MAX(fetched_at) as t FROM dns_queries').get()?.t;
|
||||
if (latestDnsSnap) {
|
||||
const dnsRows = d.prepare('SELECT * FROM dns_queries WHERE fetched_at = ? ORDER BY query_count DESC LIMIT 30').all(latestDnsSnap);
|
||||
|
||||
lines.push(`Latest Snapshot: ${latestDnsSnap}`);
|
||||
dnsRows.forEach(r => lines.push(JSON.stringify(r)));
|
||||
}
|
||||
|
||||
// ── IP Reputation Blacklisted ─────────────────────────────────────────────
|
||||
lines.push('');
|
||||
lines.push(separator('═'));
|
||||
lines.push('[DERIVED: BLACKLISTED IP ADDRESSES]');
|
||||
lines.push('Description: IP address yang terdeteksi blacklisted (dari intel_ip_reputation)');
|
||||
lines.push(separator('─'));
|
||||
|
||||
const latestRepSnap = d.prepare('SELECT MAX(fetched_at) as t FROM intel_ip_reputation').get()?.t;
|
||||
if (latestRepSnap) {
|
||||
const blacklisted = d.prepare('SELECT * FROM intel_ip_reputation WHERE fetched_at = ? AND blacklisted = 1').all(latestRepSnap);
|
||||
lines.push(`Latest Snapshot: ${latestRepSnap}`);
|
||||
lines.push(`Blacklisted count: ${blacklisted.length}`);
|
||||
blacklisted.forEach(r => lines.push(JSON.stringify(r)));
|
||||
}
|
||||
|
||||
// ── Flows: Active Sessions Summary ────────────────────────────────────────
|
||||
lines.push('');
|
||||
lines.push(separator('═'));
|
||||
lines.push('[DERIVED: ACTIVE FLOWS SUMMARY]');
|
||||
lines.push('Description: Ringkasan aliran jaringan aktif (50 terbaru per download)');
|
||||
lines.push(separator('─'));
|
||||
|
||||
const flowSummary = d.prepare(`
|
||||
SELECT COUNT(*) as total_flows,
|
||||
COUNT(DISTINCT src_ip) as unique_src_ips,
|
||||
COUNT(DISTINCT dst_ip) as unique_dst_ips,
|
||||
COUNT(DISTINCT src_mac) as unique_macs,
|
||||
SUM(bytes_download) as total_dl,
|
||||
SUM(bytes_upload) as total_ul,
|
||||
MIN(first_seen) as earliest,
|
||||
MAX(last_seen) as latest
|
||||
FROM flows
|
||||
`).get();
|
||||
|
||||
lines.push(`Total Flows in DB: ${fmtNum(flowSummary.total_flows)}`);
|
||||
lines.push(`Unique Source IPs: ${fmtNum(flowSummary.unique_src_ips)}`);
|
||||
lines.push(`Unique Dest IPs: ${fmtNum(flowSummary.unique_dst_ips)}`);
|
||||
lines.push(`Unique MAC Addresses: ${fmtNum(flowSummary.unique_macs)}`);
|
||||
lines.push(`Total Download: ${fmtBytes(flowSummary.total_dl)}`);
|
||||
lines.push(`Total Upload: ${fmtBytes(flowSummary.total_ul)}`);
|
||||
lines.push(`Data from: ${flowSummary.earliest}`);
|
||||
lines.push(`Data to: ${flowSummary.latest}`);
|
||||
lines.push('');
|
||||
|
||||
// Top 50 flows by download
|
||||
lines.push('Top 50 Flows by Download:');
|
||||
const topFlows = d.prepare('SELECT * FROM flows ORDER BY bytes_download DESC LIMIT 50').all();
|
||||
topFlows.forEach(r => lines.push(JSON.stringify(r)));
|
||||
|
||||
// ── Unencrypted Password Events ───────────────────────────────────────────
|
||||
lines.push('');
|
||||
lines.push(separator('═'));
|
||||
lines.push('[DERIVED: UNENCRYPTED PASSWORD DETECTIONS]');
|
||||
lines.push('Description: Kejadian pengiriman credential dalam plaintext (HIGH severity)');
|
||||
lines.push(separator('─'));
|
||||
|
||||
const unencPwdHigh = d.prepare(`
|
||||
SELECT ip_address, mac_address, dst_ip, dst_port, protocol, username, download, upload, severity, detected_at
|
||||
FROM intel_unencrypted_passwords ORDER BY detected_at DESC
|
||||
`).all();
|
||||
lines.push(`Total detections: ${unencPwdHigh.length}`);
|
||||
unencPwdHigh.forEach(r => lines.push(JSON.stringify(r)));
|
||||
|
||||
// ── Footer ────────────────────────────────────────────────────────────────
|
||||
lines.push('');
|
||||
lines.push(separator('═'));
|
||||
lines.push(' END OF EXPORT');
|
||||
lines.push(` Generated at: ${new Date().toISOString()}`);
|
||||
lines.push(` Total lines: ${lines.length + 3}`);
|
||||
lines.push(separator('═'));
|
||||
|
||||
// Write to file
|
||||
const output = lines.join('\n');
|
||||
fs.writeFileSync(OUTPUT_FILE, output, 'utf-8');
|
||||
|
||||
const stats = fs.statSync(OUTPUT_FILE);
|
||||
console.log(`\n✅ Export selesai!`);
|
||||
console.log(` File: ${OUTPUT_FILE}`);
|
||||
console.log(` Size: ${fmtBytes(stats.size)}`);
|
||||
console.log(` Lines: ${fmtNum(lines.length)}`);
|
||||
console.log(` Tables: ${tables.length}`);
|
||||
console.log(` Total Rows: ${fmtNum(totalRows)}`);
|
||||
}
|
||||
|
||||
main().catch(e => {
|
||||
console.error('❌ Export FAILED:', e);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
const jwt = require('jsonwebtoken');
|
||||
const User = require('../models/User');
|
||||
const Session = require('../models/Session');
|
||||
const { Summary } = require('../models/Schemas');
|
||||
|
||||
const JWT_SECRET = process.env.JWT_SECRET || 'super-secret-backone-key';
|
||||
@@ -11,19 +12,46 @@ async function requireAuth(req, res, next) {
|
||||
try {
|
||||
req.user = jwt.verify(token, JWT_SECRET);
|
||||
|
||||
// Verify session status in MongoDB
|
||||
if (req.user.session_id) {
|
||||
const activeSession = await Session.findById(req.user.session_id);
|
||||
if (!activeSession) {
|
||||
res.clearCookie('token');
|
||||
return res.status(401).json({ error: 'Sesi login telah dinonaktifkan atau kedaluwarsa.' });
|
||||
}
|
||||
// Update last active
|
||||
activeSession.last_active = new Date();
|
||||
await activeSession.save();
|
||||
}
|
||||
|
||||
// ── VIEW-AS MODE ──────────────────────────────────────────────────────────
|
||||
const viewAsHeader = req.headers['x-view-as-agent'];
|
||||
if (viewAsHeader && (req.user.role === 'SUPER_ADMIN' || req.user.role === 'TENANT_ADMIN')) {
|
||||
const isAllowedViewAs = req.user.role === 'SUPER_ADMIN' ||
|
||||
req.user.role === 'TENANT_ADMIN' ||
|
||||
req.user.role === 'COMPANY_ADMIN' ||
|
||||
req.user.role === 'COMPANY_OPERATOR';
|
||||
|
||||
if (viewAsHeader && isAllowedViewAs) {
|
||||
try {
|
||||
const viewDecoded = jwt.verify(viewAsHeader, JWT_SECRET);
|
||||
if (viewDecoded.type === 'view-as' && viewDecoded.adminId === req.user.id && viewDecoded.viewAs) {
|
||||
const targetAgentUser = await User.findOne({ agent_uuid: viewDecoded.viewAs, role: 'AGENT_VIEWER' }).lean();
|
||||
const targetAgent = viewDecoded.viewAs;
|
||||
|
||||
// Validation: COMPANY_ADMIN and COMPANY_OPERATOR can only view-as their assigned agents
|
||||
if (['COMPANY_ADMIN', 'COMPANY_OPERATOR'].includes(req.user.role)) {
|
||||
const hasAccess = req.user.agent_uuids && req.user.agent_uuids.includes(targetAgent);
|
||||
if (!hasAccess) {
|
||||
throw new Error('Unauthorized view-as agent access');
|
||||
}
|
||||
}
|
||||
|
||||
const targetAgentUser = await User.findOne({ agent_uuid: targetAgent, role: 'AGENT_VIEWER' }).lean();
|
||||
|
||||
let targetSiteUuid = req.user.site_uuid;
|
||||
if (targetAgentUser && targetAgentUser.site_uuid) {
|
||||
targetSiteUuid = targetAgentUser.site_uuid;
|
||||
} else {
|
||||
const summaryDoc = await Summary.findOne({ agent_uuid: viewDecoded.viewAs }).lean();
|
||||
const summaryDoc = await Summary.findOne({ agent_uuid: targetAgent }).lean();
|
||||
if (summaryDoc && summaryDoc.site_uuid) {
|
||||
targetSiteUuid = summaryDoc.site_uuid;
|
||||
}
|
||||
@@ -32,7 +60,7 @@ async function requireAuth(req, res, next) {
|
||||
req.user = {
|
||||
...req.user,
|
||||
role: 'AGENT_VIEWER',
|
||||
agent_uuid: viewDecoded.viewAs,
|
||||
agent_uuid: targetAgent,
|
||||
agent_label: viewDecoded.viewAsLabel,
|
||||
site_uuid: targetSiteUuid,
|
||||
_viewAsMode: true,
|
||||
@@ -50,12 +78,25 @@ async function requireAuth(req, res, next) {
|
||||
}
|
||||
}
|
||||
|
||||
function requireAdmin(req, res, next) {
|
||||
async function requireAdmin(req, res, next) {
|
||||
const token = req.cookies?.token;
|
||||
if (!token) return res.status(401).json({ error: 'Not authenticated' });
|
||||
try {
|
||||
const decoded = jwt.verify(token, JWT_SECRET);
|
||||
if (decoded.role !== 'SUPER_ADMIN' && decoded.role !== 'TENANT_ADMIN' && decoded.role !== 'SOC_ANALYST') {
|
||||
|
||||
// Verify session status in MongoDB
|
||||
if (decoded.session_id) {
|
||||
const activeSession = await Session.findById(decoded.session_id);
|
||||
if (!activeSession) {
|
||||
res.clearCookie('token');
|
||||
return res.status(401).json({ error: 'Sesi login telah dinonaktifkan atau kedaluwarsa.' });
|
||||
}
|
||||
activeSession.last_active = new Date();
|
||||
await activeSession.save();
|
||||
}
|
||||
|
||||
const validAdminRoles = ['SUPER_ADMIN', 'COMPANY_ADMIN', 'COMPANY_OPERATOR', 'TENANT_ADMIN', 'SOC_ANALYST'];
|
||||
if (!validAdminRoles.includes(decoded.role)) {
|
||||
return res.status(403).json({ error: 'Forbidden' });
|
||||
}
|
||||
req.adminUser = decoded;
|
||||
|
||||
+187
-184
@@ -1,184 +1,187 @@
|
||||
// backend/models/Schemas.js
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// MongoDB Schemas untuk BackOne Backend (READ-ONLY)
|
||||
//
|
||||
// PENTING: Schema ini harus sinkron dengan proxy/models/Schemas.js
|
||||
// Proxy yang MENULIS data, backend yang MEMBACA data.
|
||||
//
|
||||
// Setiap dokumen di-tag dengan:
|
||||
// agent_uuid → identifikasi Network Agent spesifik (isolasi per tenant)
|
||||
// site_uuid → identifikasi site DPI (BackOne)
|
||||
// timestamp → waktu data dikumpulkan
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const baseOptions = {
|
||||
timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' }
|
||||
};
|
||||
|
||||
// ─── Bandwidth Summary (per agent, per collection cycle) ───────────────────────
|
||||
const SummarySchema = new mongoose.Schema({
|
||||
timestamp: { type: Date, required: true, index: true, expires: '7d' },
|
||||
agent_uuid: { type: String, index: true }, // null = global/all agents
|
||||
site_uuid: { type: String, index: true },
|
||||
bandwidth_down: Number,
|
||||
bandwidth_up: Number,
|
||||
active_flows: Number,
|
||||
download_speed: Number,
|
||||
upload_speed: Number,
|
||||
total_devices: Number,
|
||||
total_threats: Number,
|
||||
packet_drops: Number,
|
||||
peak_flow_rate: Number,
|
||||
cpu_usage: Number,
|
||||
memory_usage: Number,
|
||||
queue_depth: Number,
|
||||
}, baseOptions);
|
||||
|
||||
// ─── Top Applications (per agent) ─────────────────────────────────────────────
|
||||
const AppStatSchema = new mongoose.Schema({
|
||||
timestamp: { type: Date, required: true, index: true, expires: '7d' },
|
||||
agent_uuid: { type: String, index: true },
|
||||
site_uuid: { type: String, index: true },
|
||||
app_label: { type: String, required: true },
|
||||
download: Number,
|
||||
upload: Number,
|
||||
flows: Number,
|
||||
}, baseOptions);
|
||||
|
||||
// ─── Protocol Statistics (per agent) ──────────────────────────────────────────
|
||||
const ProtocolStatSchema = new mongoose.Schema({
|
||||
timestamp: { type: Date, required: true, index: true, expires: '7d' },
|
||||
agent_uuid: { type: String, index: true },
|
||||
site_uuid: { type: String, index: true },
|
||||
protocol_label: { type: String, required: true },
|
||||
download: Number,
|
||||
upload: Number,
|
||||
flows: Number,
|
||||
}, baseOptions);
|
||||
|
||||
// ─── Discovered Devices (per agent, includes IP + MAC + device info) ───────────
|
||||
const DeviceStatSchema = new mongoose.Schema({
|
||||
timestamp: { type: Date, required: true, index: true, expires: '7d' },
|
||||
agent_uuid: { type: String, index: true },
|
||||
site_uuid: { type: String, index: true },
|
||||
ip_address: { type: String, required: true, index: true },
|
||||
mac_address: { type: String, index: true },
|
||||
device_label: String,
|
||||
device_type: String,
|
||||
os_label: String,
|
||||
manufacturer: String,
|
||||
download: Number,
|
||||
upload: Number,
|
||||
flows: Number,
|
||||
last_seen: String,
|
||||
}, baseOptions);
|
||||
|
||||
// ─── Network Flows (per agent) ─────────────────────────────────────────────────
|
||||
const FlowSchema = new mongoose.Schema({
|
||||
timestamp: { type: Date, required: true, index: true, expires: '7d' },
|
||||
agent_uuid: { type: String, index: true },
|
||||
site_uuid: { type: String, index: true },
|
||||
flow_id: String,
|
||||
src_ip: { type: String, index: true },
|
||||
src_mac: String,
|
||||
dst_ip: { type: String, index: true },
|
||||
dst_port: Number,
|
||||
protocol: String,
|
||||
app_label: String,
|
||||
domain: { type: String, index: true },
|
||||
download: Number,
|
||||
upload: Number,
|
||||
first_seen: String,
|
||||
last_seen: String,
|
||||
}, baseOptions);
|
||||
|
||||
// ─── Cyber Threats (per agent) ─────────────────────────────────────────────────
|
||||
const ThreatSchema = new mongoose.Schema({
|
||||
timestamp: { type: Date, required: true, index: true, expires: '7d' },
|
||||
agent_uuid: { type: String, index: true },
|
||||
site_uuid: { type: String, index: true },
|
||||
threat_type: String,
|
||||
severity: String,
|
||||
src_ip: String,
|
||||
dst_ip: String,
|
||||
dst_port: Number,
|
||||
protocol: String,
|
||||
description: String,
|
||||
event_at: String,
|
||||
}, baseOptions);
|
||||
|
||||
// ─── App Categories (per agent) ───────────────────────────────────────────────
|
||||
const AppCategoryStatSchema = new mongoose.Schema({
|
||||
timestamp: { type: Date, required: true, index: true, expires: '7d' },
|
||||
agent_uuid: { type: String, index: true },
|
||||
site_uuid: { type: String, index: true },
|
||||
category_label: { type: String, required: true },
|
||||
download: Number,
|
||||
upload: Number,
|
||||
flows: Number,
|
||||
}, baseOptions);
|
||||
|
||||
// ─── System Events (per agent) ─────────────────────────────────────────────────
|
||||
const EventSchema = new mongoose.Schema({
|
||||
timestamp: { type: Date, required: true, index: true, expires: '7d' },
|
||||
agent_uuid: { type: String, index: true },
|
||||
site_uuid: { type: String, index: true },
|
||||
event_id: Number,
|
||||
event_type: String,
|
||||
severity: String,
|
||||
description: String,
|
||||
category_label: String,
|
||||
ip_address: String,
|
||||
mac_address: String,
|
||||
event_at: Date,
|
||||
}, baseOptions);
|
||||
|
||||
// ─── Compound Indexes for common dashboard queries ─────────────────────────────
|
||||
SummarySchema.index({ agent_uuid: 1, timestamp: -1 });
|
||||
AppStatSchema.index({ agent_uuid: 1, timestamp: -1, download: -1 });
|
||||
DeviceStatSchema.index({ agent_uuid: 1, ip_address: 1 }, { unique: true });
|
||||
FlowSchema.index({ agent_uuid: 1, timestamp: -1 });
|
||||
FlowSchema.index({ agent_uuid: 1, flow_id: 1 });
|
||||
FlowSchema.index({ agent_uuid: 1, protocol: 1, timestamp: -1 });
|
||||
FlowSchema.index({ agent_uuid: 1, domain: 1, timestamp: -1 });
|
||||
FlowSchema.index({ site_uuid: 1, app_label: 1, timestamp: -1 });
|
||||
ThreatSchema.index({ agent_uuid: 1, timestamp: -1 });
|
||||
AppCategoryStatSchema.index({ agent_uuid: 1, timestamp: -1 });
|
||||
EventSchema.index({ agent_uuid: 1, timestamp: -1 });
|
||||
|
||||
// ── Per-Device Per-Application Stats (synced from proxy) ─────────────────
|
||||
const DeviceAppStatSchema = new mongoose.Schema({
|
||||
timestamp: { type: Date, required: true, index: true, expires: '7d' },
|
||||
agent_uuid: { type: String, index: true },
|
||||
site_uuid: { type: String, index: true },
|
||||
ip_address: { type: String, required: true, index: true },
|
||||
app_label: { type: String, required: true },
|
||||
app_id: Number,
|
||||
download: { type: Number, default: 0 },
|
||||
upload: { type: Number, default: 0 },
|
||||
flows: { type: Number, default: 0 },
|
||||
last_seen: String,
|
||||
}, baseOptions);
|
||||
DeviceAppStatSchema.index({ agent_uuid: 1, ip_address: 1, timestamp: -1 });
|
||||
DeviceAppStatSchema.index({ ip_address: 1, app_label: 1, timestamp: -1 });
|
||||
DeviceAppStatSchema.index({ site_uuid: 1, app_label: 1, timestamp: -1 });
|
||||
|
||||
const telemetrySchemas = require('./SchemasTelemetry');
|
||||
const auxSchemas = require('./SchemasAux');
|
||||
|
||||
module.exports = {
|
||||
Summary: mongoose.model('Summary', SummarySchema),
|
||||
AppStat: mongoose.model('AppStat', AppStatSchema),
|
||||
ProtocolStat: mongoose.model('ProtocolStat', ProtocolStatSchema),
|
||||
DeviceStat: mongoose.model('DeviceStat', DeviceStatSchema),
|
||||
DeviceAppStat: mongoose.model('DeviceAppStat', DeviceAppStatSchema),
|
||||
Flow: mongoose.model('Flow', FlowSchema),
|
||||
Threat: mongoose.model('Threat', ThreatSchema),
|
||||
AppCategoryStat: mongoose.model('AppCategoryStat', AppCategoryStatSchema),
|
||||
Event: mongoose.model('Event', EventSchema),
|
||||
...auxSchemas,
|
||||
...telemetrySchemas
|
||||
};
|
||||
|
||||
// backend/models/Schemas.js
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// MongoDB Schemas untuk BackOne Backend (READ-ONLY)
|
||||
//
|
||||
// PENTING: Schema ini harus sinkron dengan proxy/models/Schemas.js
|
||||
// Proxy yang MENULIS data, backend yang MEMBACA data.
|
||||
//
|
||||
// Setiap dokumen di-tag dengan:
|
||||
// agent_uuid → identifikasi Network Agent spesifik (isolasi per tenant)
|
||||
// site_uuid → identifikasi site DPI (BackOne)
|
||||
// timestamp → waktu data dikumpulkan
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const baseOptions = {
|
||||
timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' }
|
||||
};
|
||||
|
||||
// ─── Bandwidth Summary (per agent, per collection cycle) ───────────────────────
|
||||
const SummarySchema = new mongoose.Schema({
|
||||
timestamp: { type: Date, required: true, index: true, expires: '7d' },
|
||||
agent_uuid: { type: String, index: true }, // null = global/all agents
|
||||
site_uuid: { type: String, index: true },
|
||||
bandwidth_down: Number,
|
||||
bandwidth_up: Number,
|
||||
active_flows: Number,
|
||||
download_speed: Number,
|
||||
upload_speed: Number,
|
||||
total_devices: Number,
|
||||
total_threats: Number,
|
||||
packet_drops: Number,
|
||||
peak_flow_rate: Number,
|
||||
cpu_usage: Number,
|
||||
memory_usage: Number,
|
||||
queue_depth: Number,
|
||||
}, baseOptions);
|
||||
|
||||
// ─── Top Applications (per agent) ─────────────────────────────────────────────
|
||||
const AppStatSchema = new mongoose.Schema({
|
||||
timestamp: { type: Date, required: true, index: true, expires: '7d' },
|
||||
agent_uuid: { type: String, index: true },
|
||||
site_uuid: { type: String, index: true },
|
||||
app_label: { type: String, required: true },
|
||||
download: Number,
|
||||
upload: Number,
|
||||
flows: Number,
|
||||
}, baseOptions);
|
||||
|
||||
// ─── Protocol Statistics (per agent) ──────────────────────────────────────────
|
||||
const ProtocolStatSchema = new mongoose.Schema({
|
||||
timestamp: { type: Date, required: true, index: true, expires: '7d' },
|
||||
agent_uuid: { type: String, index: true },
|
||||
site_uuid: { type: String, index: true },
|
||||
protocol_label: { type: String, required: true },
|
||||
download: Number,
|
||||
upload: Number,
|
||||
flows: Number,
|
||||
}, baseOptions);
|
||||
|
||||
// ─── Discovered Devices (per agent, includes IP + MAC + device info) ───────────
|
||||
const DeviceStatSchema = new mongoose.Schema({
|
||||
timestamp: { type: Date, required: true, index: true, expires: '7d' },
|
||||
agent_uuid: { type: String, index: true },
|
||||
site_uuid: { type: String, index: true },
|
||||
ip_address: { type: String, required: true, index: true },
|
||||
mac_address: { type: String, index: true },
|
||||
device_label: String,
|
||||
device_type: String,
|
||||
os_label: String,
|
||||
manufacturer: String,
|
||||
download: Number,
|
||||
upload: Number,
|
||||
flows: Number,
|
||||
last_seen: String,
|
||||
}, baseOptions);
|
||||
|
||||
// ─── Network Flows (per agent) ─────────────────────────────────────────────────
|
||||
const FlowSchema = new mongoose.Schema({
|
||||
timestamp: { type: Date, required: true, index: true, expires: '7d' },
|
||||
agent_uuid: { type: String, index: true },
|
||||
site_uuid: { type: String, index: true },
|
||||
flow_id: String,
|
||||
src_ip: { type: String, index: true },
|
||||
src_mac: { type: String, index: true },
|
||||
dst_ip: { type: String, index: true },
|
||||
dst_port: Number,
|
||||
protocol: String,
|
||||
app_label: String,
|
||||
domain: { type: String, index: true },
|
||||
download: Number,
|
||||
upload: Number,
|
||||
first_seen: String,
|
||||
last_seen: String,
|
||||
}, baseOptions);
|
||||
|
||||
// ─── Cyber Threats (per agent) ─────────────────────────────────────────────────
|
||||
const ThreatSchema = new mongoose.Schema({
|
||||
timestamp: { type: Date, required: true, index: true, expires: '7d' },
|
||||
agent_uuid: { type: String, index: true },
|
||||
site_uuid: { type: String, index: true },
|
||||
threat_type: String,
|
||||
severity: String,
|
||||
src_ip: String,
|
||||
dst_ip: String,
|
||||
dst_port: Number,
|
||||
protocol: String,
|
||||
description: String,
|
||||
event_at: String,
|
||||
flow_id: { type: String, index: true },
|
||||
}, baseOptions);
|
||||
|
||||
// ─── App Categories (per agent) ───────────────────────────────────────────────
|
||||
const AppCategoryStatSchema = new mongoose.Schema({
|
||||
timestamp: { type: Date, required: true, index: true, expires: '7d' },
|
||||
agent_uuid: { type: String, index: true },
|
||||
site_uuid: { type: String, index: true },
|
||||
category_label: { type: String, required: true },
|
||||
download: Number,
|
||||
upload: Number,
|
||||
flows: Number,
|
||||
}, baseOptions);
|
||||
|
||||
// ─── System Events (per agent) ─────────────────────────────────────────────────
|
||||
const EventSchema = new mongoose.Schema({
|
||||
timestamp: { type: Date, required: true, index: true, expires: '7d' },
|
||||
agent_uuid: { type: String, index: true },
|
||||
site_uuid: { type: String, index: true },
|
||||
event_id: Number,
|
||||
event_type: String,
|
||||
severity: String,
|
||||
description: String,
|
||||
category_label: String,
|
||||
ip_address: String,
|
||||
mac_address: String,
|
||||
event_at: Date,
|
||||
flow_id: { type: String, index: true },
|
||||
}, baseOptions);
|
||||
|
||||
// ─── Compound Indexes for common dashboard queries ─────────────────────────────
|
||||
SummarySchema.index({ agent_uuid: 1, timestamp: -1 });
|
||||
AppStatSchema.index({ agent_uuid: 1, timestamp: -1, download: -1 });
|
||||
DeviceStatSchema.index({ agent_uuid: 1, ip_address: 1 }, { unique: true });
|
||||
FlowSchema.index({ agent_uuid: 1, timestamp: -1 });
|
||||
FlowSchema.index({ agent_uuid: 1, flow_id: 1 });
|
||||
FlowSchema.index({ agent_uuid: 1, protocol: 1, timestamp: -1 });
|
||||
FlowSchema.index({ agent_uuid: 1, domain: 1, timestamp: -1 });
|
||||
FlowSchema.index({ site_uuid: 1, app_label: 1, timestamp: -1 });
|
||||
FlowSchema.index({ site_uuid: 1, src_mac: 1, timestamp: -1 });
|
||||
ThreatSchema.index({ agent_uuid: 1, timestamp: -1 });
|
||||
AppCategoryStatSchema.index({ agent_uuid: 1, timestamp: -1 });
|
||||
EventSchema.index({ agent_uuid: 1, timestamp: -1 });
|
||||
|
||||
// ── Per-Device Per-Application Stats (synced from proxy) ─────────────────
|
||||
const DeviceAppStatSchema = new mongoose.Schema({
|
||||
timestamp: { type: Date, required: true, index: true, expires: '7d' },
|
||||
agent_uuid: { type: String, index: true },
|
||||
site_uuid: { type: String, index: true },
|
||||
ip_address: { type: String, required: true, index: true },
|
||||
app_label: { type: String, required: true },
|
||||
app_id: Number,
|
||||
download: { type: Number, default: 0 },
|
||||
upload: { type: Number, default: 0 },
|
||||
flows: { type: Number, default: 0 },
|
||||
last_seen: String,
|
||||
}, baseOptions);
|
||||
DeviceAppStatSchema.index({ agent_uuid: 1, ip_address: 1, timestamp: -1 });
|
||||
DeviceAppStatSchema.index({ ip_address: 1, app_label: 1, timestamp: -1 });
|
||||
DeviceAppStatSchema.index({ site_uuid: 1, app_label: 1, timestamp: -1 });
|
||||
|
||||
const telemetrySchemas = require('./SchemasTelemetry');
|
||||
const auxSchemas = require('./SchemasAux');
|
||||
|
||||
module.exports = {
|
||||
Summary: mongoose.model('Summary', SummarySchema),
|
||||
AppStat: mongoose.model('AppStat', AppStatSchema),
|
||||
ProtocolStat: mongoose.model('ProtocolStat', ProtocolStatSchema),
|
||||
DeviceStat: mongoose.model('DeviceStat', DeviceStatSchema),
|
||||
DeviceAppStat: mongoose.model('DeviceAppStat', DeviceAppStatSchema),
|
||||
Flow: mongoose.model('Flow', FlowSchema),
|
||||
Threat: mongoose.model('Threat', ThreatSchema),
|
||||
AppCategoryStat: mongoose.model('AppCategoryStat', AppCategoryStatSchema),
|
||||
Event: mongoose.model('Event', EventSchema),
|
||||
...auxSchemas,
|
||||
...telemetrySchemas
|
||||
};
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// backend/models/Session.js
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// MongoDB User Session Schema for remote revocation capability
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const SessionSchema = new mongoose.Schema({
|
||||
user_id: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true, index: true },
|
||||
ip_address: { type: String, default: 'Unknown' },
|
||||
user_agent: { type: String, default: 'Unknown' },
|
||||
session_token: { type: String, required: true, unique: true }, // JWT JTI or unique token hash
|
||||
last_active: { type: Date, default: Date.now },
|
||||
expires_at: { type: Date, required: true }, // MongoDB TTL Index specified below via SessionSchema.index
|
||||
}, {
|
||||
timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' }
|
||||
});
|
||||
|
||||
// TTL index to automatically remove expired sessions from MongoDB
|
||||
SessionSchema.index({ expires_at: 1 }, { expireAfterSeconds: 0 });
|
||||
|
||||
module.exports = mongoose.model('Session', SessionSchema);
|
||||
@@ -14,11 +14,15 @@ const UserSchema = new mongoose.Schema({
|
||||
password_hash: { type: String, required: true },
|
||||
account_name: { type: String, default: null },
|
||||
profile_picture: { type: String, default: null },
|
||||
role: { type: String, enum: ['SUPER_ADMIN', 'TENANT_ADMIN', 'SOC_ANALYST', 'ENGINEER', 'AGENT_VIEWER'], default: 'AGENT_VIEWER' },
|
||||
role: { type: String, enum: ['SUPER_ADMIN', 'EXECUTIVE', 'TENANT_ADMIN', 'SOC_ANALYST', 'ENGINEER', 'AGENT_VIEWER', 'COMPANY_ADMIN', 'COMPANY_OPERATOR', 'COMPANY_VIEWER'], default: 'AGENT_VIEWER' },
|
||||
site_uuid: { type: String, default: null, index: true },
|
||||
agent_uuid: { type: String, default: null },
|
||||
company_name: { type: String, default: null, index: true },
|
||||
agent_uuids: { type: [String], default: [] },
|
||||
created_by: { type: String, default: null, index: true },
|
||||
is_active: { type: Boolean, default: true },
|
||||
login_attempts: { type: Number, default: 0 },
|
||||
lockout_until: { type: Date, default: null },
|
||||
}, {
|
||||
timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' }
|
||||
});
|
||||
|
||||
+2718
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,312 @@
|
||||
// backend/scheduler.js
|
||||
const cron = require('node-cron');
|
||||
const netify = require('./netify');
|
||||
const db = require('./database');
|
||||
const SITE_UUID = process.env.NETIFY_SITE_UUID || 'dummy_site_uuid';
|
||||
|
||||
let isRunning = false;
|
||||
|
||||
async function runPoll() {
|
||||
if (isRunning) {
|
||||
console.log('[Scheduler] Poll sedang berjalan, skip.');
|
||||
return;
|
||||
}
|
||||
isRunning = true;
|
||||
const fetchedAt = new Date().toISOString();
|
||||
console.log(`[Scheduler] Mulai polling... (${fetchedAt})`);
|
||||
|
||||
try {
|
||||
// 0. Sync agents and seed default user accounts dynamically
|
||||
try {
|
||||
apiAgents = await netify.fetchAgents();
|
||||
if (apiAgents && apiAgents.length > 0) {
|
||||
db.syncAgentUsers(apiAgents);
|
||||
console.log(`[Scheduler] OK Sync Agents : ${apiAgents.length} agen terdeteksi`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[Scheduler] Gagal sync agent users:', err.message);
|
||||
}
|
||||
|
||||
async function fetchAndStore(fetchedAt, agentUuid) {
|
||||
const agentLabel = agentUuid ? agentUuid : 'Global';
|
||||
console.log(`[Scheduler] Fetching data for ${agentLabel}`);
|
||||
// 1. Top Aplikasi
|
||||
const apps = await netify.fetchTopApps(1440, 20, agentUuid);
|
||||
if (apps && Array.isArray(apps)) {
|
||||
db.insertBandwidthApps(apps, fetchedAt, SITE_UUID, agentUuid);
|
||||
console.log(`[Scheduler] OK Apps : ${apps.length} baris`);
|
||||
} else {
|
||||
console.log(`[Scheduler] -- Apps : tidak ada data`);
|
||||
}
|
||||
|
||||
// 2. Top Devices — pakai fetchDiscoveredDevices yg sudah dinormalisasi
|
||||
const devices = await netify.fetchDiscoveredDevices(1440, 200, agentUuid);
|
||||
if (devices && Array.isArray(devices)) {
|
||||
db.insertDevices(devices, fetchedAt, SITE_UUID, agentUuid);
|
||||
console.log(`[Scheduler] OK Devices : ${devices.length} baris`);
|
||||
} else {
|
||||
console.log(`[Scheduler] -- Devices : tidak ada data`);
|
||||
}
|
||||
|
||||
// 3. Top Protokol
|
||||
const protocols = await netify.fetchTopProtocols(1440, 20, agentUuid);
|
||||
if (protocols && Array.isArray(protocols)) {
|
||||
db.insertProtocols(protocols, fetchedAt, SITE_UUID, agentUuid);
|
||||
console.log(`[Scheduler] OK Protocols : ${protocols.length} baris`);
|
||||
} else {
|
||||
console.log(`[Scheduler] -- Protocols : tidak ada data`);
|
||||
}
|
||||
|
||||
// 4. Top Negara
|
||||
const countries = await netify.fetchTopCountries(1440, 15, agentUuid);
|
||||
if (countries && Array.isArray(countries)) {
|
||||
db.insertCountries(countries, fetchedAt, SITE_UUID, agentUuid);
|
||||
console.log(`[Scheduler] OK Countries : ${countries.length} baris`);
|
||||
} else {
|
||||
console.log(`[Scheduler] -- Countries : tidak ada data`);
|
||||
}
|
||||
|
||||
// 5. Top Domain/DNS
|
||||
const domains = await netify.fetchTopDomains(1440, 20, agentUuid);
|
||||
if (domains && Array.isArray(domains)) {
|
||||
db.insertDNS(domains, fetchedAt, SITE_UUID, agentUuid);
|
||||
console.log(`[Scheduler] OK DNS : ${domains.length} baris`);
|
||||
} else {
|
||||
console.log(`[Scheduler] -- DNS : tidak ada data`);
|
||||
}
|
||||
|
||||
// 6. Flows — pakai local_ip sebagai proxy
|
||||
const flows = await netify.fetchFlows(200, agentUuid);
|
||||
if (flows && Array.isArray(flows)) {
|
||||
db.insertFlows(flows, fetchedAt, SITE_UUID, agentUuid);
|
||||
console.log(`[Scheduler] OK Flows : ${flows.length} baris`);
|
||||
} else {
|
||||
console.log(`[Scheduler] -- Flows : tidak ada data`);
|
||||
}
|
||||
|
||||
// 7. Threats — dari Events Status
|
||||
const threats = await netify.fetchCyberThreats(1440, 50, agentUuid);
|
||||
if (threats && Array.isArray(threats)) {
|
||||
db.insertThreats(threats, fetchedAt, SITE_UUID, agentUuid);
|
||||
console.log(`[Scheduler] OK Threats : ${threats.length} baris`);
|
||||
} else {
|
||||
console.log(`[Scheduler] -- Threats : tidak ada data`);
|
||||
}
|
||||
|
||||
// 8. Events Log
|
||||
const events = await netify.fetchEvents(50, agentUuid);
|
||||
if (events && Array.isArray(events)) {
|
||||
db.insertEvents(events, fetchedAt, SITE_UUID, agentUuid);
|
||||
console.log(`[Scheduler] OK Events : ${events.length} baris`);
|
||||
} else {
|
||||
console.log(`[Scheduler] -- Events : tidak ada data`);
|
||||
}
|
||||
|
||||
// 10. App Categories
|
||||
const appCats = await netify.fetchTopAppCategories(1440, 15, agentUuid);
|
||||
if (appCats?.length) { db.insertAppCategories(appCats, fetchedAt, SITE_UUID, agentUuid); console.log(`[Scheduler] OK AppCats : ${appCats.length} baris`); }
|
||||
else console.log(`[Scheduler] -- AppCats : tidak ada data`);
|
||||
|
||||
// 11. Continents
|
||||
const continents = await netify.fetchTopContinents(1440, 10, agentUuid);
|
||||
if (continents?.length) { db.insertContinents(continents, fetchedAt, SITE_UUID, agentUuid); console.log(`[Scheduler] OK Continents : ${continents.length} baris`); }
|
||||
else console.log(`[Scheduler] -- Continents : tidak ada data`);
|
||||
|
||||
// 12. Regions
|
||||
const regions = await netify.fetchTopRegions(1440, 20, agentUuid);
|
||||
if (regions?.length) { db.insertRegions(regions, fetchedAt, SITE_UUID, agentUuid); console.log(`[Scheduler] OK Regions : ${regions.length} baris`); }
|
||||
else console.log(`[Scheduler] -- Regions : tidak ada data`);
|
||||
|
||||
// 13. Cities
|
||||
const cities = await netify.fetchTopCities(1440, 20, agentUuid);
|
||||
if (cities?.length) { db.insertCities(cities, fetchedAt, SITE_UUID, agentUuid); console.log(`[Scheduler] OK Cities : ${cities.length} baris`); }
|
||||
else console.log(`[Scheduler] -- Cities : tidak ada data`);
|
||||
|
||||
// 14. VLANs
|
||||
const vlans = await netify.fetchTopVLANs(1440, 20, agentUuid);
|
||||
if (vlans?.length) { db.insertVLANs(vlans, fetchedAt, SITE_UUID, agentUuid); console.log(`[Scheduler] OK VLANs : ${vlans.length} baris`); }
|
||||
else console.log(`[Scheduler] -- VLANs : tidak ada data`);
|
||||
|
||||
// 15. Interfaces
|
||||
const ifaces = await netify.fetchTopInterfaces(1440, 20, agentUuid);
|
||||
if (ifaces?.length) { db.insertInterfaces(ifaces, fetchedAt, SITE_UUID, agentUuid); console.log(`[Scheduler] OK Interfaces : ${ifaces.length} baris`); }
|
||||
else console.log(`[Scheduler] -- Interfaces : tidak ada data`);
|
||||
|
||||
// 16. Flow Types
|
||||
const flowTypes = await netify.fetchTopFlowTypes(1440, 10, agentUuid);
|
||||
if (flowTypes?.length) { db.insertFlowTypes(flowTypes, fetchedAt, SITE_UUID, agentUuid); console.log(`[Scheduler] OK FlowTypes : ${flowTypes.length} baris`); }
|
||||
else console.log(`[Scheduler] -- FlowTypes : tidak ada data`);
|
||||
|
||||
// 17. Flow Origins
|
||||
const flowOrigins = await netify.fetchTopFlowOrigins(1440, 10, agentUuid);
|
||||
if (flowOrigins?.length) { db.insertFlowOrigins(flowOrigins, fetchedAt, SITE_UUID, agentUuid); console.log(`[Scheduler] OK FlowOrigin : ${flowOrigins.length} baris`); }
|
||||
else console.log(`[Scheduler] -- FlowOrigin : tidak ada data`);
|
||||
|
||||
// 18. IP Versions
|
||||
const ipVersions = await netify.fetchTopIPVersions(1440, 5, agentUuid);
|
||||
if (ipVersions?.length) { db.insertIPVersions(ipVersions, fetchedAt, SITE_UUID, agentUuid); console.log(`[Scheduler] OK IPVersions : ${ipVersions.length} baris`); }
|
||||
else console.log(`[Scheduler] -- IPVersions : tidak ada data`);
|
||||
|
||||
// 19. Remote IPs
|
||||
const remoteIPs = await netify.fetchTopRemoteIPs(1440, 20, agentUuid);
|
||||
if (remoteIPs?.length) { db.insertRemoteIPs(remoteIPs, fetchedAt, SITE_UUID, agentUuid); console.log(`[Scheduler] OK RemoteIPs : ${remoteIPs.length} baris`); }
|
||||
else console.log(`[Scheduler] -- RemoteIPs : tidak ada data`);
|
||||
|
||||
// 20. MAC Bandwidth
|
||||
const macBW = await netify.fetchTopLocalMACs(1440, 50, agentUuid);
|
||||
if (macBW?.length) { db.insertMACBandwidth(macBW, fetchedAt, SITE_UUID, agentUuid); console.log(`[Scheduler] OK MACBandwdh : ${macBW.length} baris`); }
|
||||
else console.log(`[Scheduler] -- MACBandwdh : tidak ada data`);
|
||||
|
||||
// 9. Bandwidth Timeline
|
||||
const summary = await netify.fetchBandwidthSummary(1440, agentUuid);
|
||||
const devCount = devices?.length ?? 0;
|
||||
if (summary) {
|
||||
db.insertBandwidthTimeline({ ...summary, devices: devCount }, fetchedAt, SITE_UUID, agentUuid);
|
||||
console.log(`[Scheduler] OK Timeline : saved`);
|
||||
} else {
|
||||
console.log(`[Scheduler] -- Timeline : gagal ambil data`);
|
||||
}
|
||||
|
||||
// 21. TLS Versions
|
||||
const tlsVer = await netify.fetchTLSVersions(1440, 10, agentUuid);
|
||||
if (tlsVer?.length) { db.insertTLSVersions(tlsVer, fetchedAt, SITE_UUID, agentUuid); console.log(`[Scheduler] OK TLS Ver : ${tlsVer.length} baris`); }
|
||||
else console.log(`[Scheduler] -- TLS Ver : tidak ada data`);
|
||||
|
||||
// 22. TLS Ciphers
|
||||
const tlsCipher = await netify.fetchTLSCiphers(1440, 15, agentUuid);
|
||||
if (tlsCipher?.length) { db.insertTLSCiphers(tlsCipher, fetchedAt, SITE_UUID, agentUuid); console.log(`[Scheduler] OK TLS Cipher : ${tlsCipher.length} baris`); }
|
||||
else console.log(`[Scheduler] -- TLS Cipher : tidak ada data`);
|
||||
|
||||
// 23. TLS Security
|
||||
const tlsSec = await netify.fetchTLSSecurity(1440, 10, agentUuid);
|
||||
if (tlsSec?.length) { db.insertTLSSecurity(tlsSec, fetchedAt, SITE_UUID, agentUuid); console.log(`[Scheduler] OK TLS Sec : ${tlsSec.length} baris`); }
|
||||
else console.log(`[Scheduler] -- TLS Sec : tidak ada data`);
|
||||
|
||||
// 24. NetBIOS Hostnames
|
||||
const netbios = await netify.fetchNetBIOSHostnames(1440, 30, agentUuid);
|
||||
if (netbios?.length) { db.insertNetBIOSHostnames(netbios, fetchedAt, SITE_UUID, agentUuid); console.log(`[Scheduler] OK NetBIOS : ${netbios.length} baris`); }
|
||||
else console.log(`[Scheduler] -- NetBIOS : tidak ada data`);
|
||||
|
||||
// 25. Discovery OS (standalone — OS yang terdeteksi di jaringan)
|
||||
const discOs = await netify.fetchTopDiscoveryOS(1440, 20, agentUuid);
|
||||
if (discOs?.length) { db.insertDiscoveryOS(discOs, fetchedAt, SITE_UUID, agentUuid); console.log(`[Scheduler] OK DiscOS : ${discOs.length} baris`); }
|
||||
else console.log(`[Scheduler] -- DiscOS : tidak ada data`);
|
||||
|
||||
// 26. DHCP Class Fingerprint
|
||||
const dhcpFp = await netify.fetchDHCPClassFingerprints(1440, 30, agentUuid);
|
||||
if (dhcpFp?.length) { db.insertDHCPFingerprints(dhcpFp, fetchedAt, SITE_UUID, agentUuid); console.log(`[Scheduler] OK DHCP FP : ${dhcpFp.length} baris`); }
|
||||
else console.log(`[Scheduler] -- DHCP FP : tidak ada data`);
|
||||
|
||||
// 27. HTTP User-Agent
|
||||
const userAgents = await netify.fetchHTTPUserAgents(1440, 30, agentUuid);
|
||||
if (userAgents?.length) { db.insertHTTPUserAgents(userAgents, fetchedAt, SITE_UUID, agentUuid); console.log(`[Scheduler] OK UserAgent : ${userAgents.length} baris`); }
|
||||
else console.log(`[Scheduler] -- UserAgent : tidak ada data`);
|
||||
|
||||
// 28. HTTPS SNI Hostname
|
||||
const sniHosts = await netify.fetchSNIHostnames(1440, 30, agentUuid);
|
||||
if (sniHosts?.length) { db.insertSNIHostnames(sniHosts, fetchedAt, SITE_UUID, agentUuid); console.log(`[Scheduler] OK SNI Host : ${sniHosts.length} baris`); }
|
||||
else console.log(`[Scheduler] -- SNI Host : tidak ada data`);
|
||||
|
||||
// 29. SSL Server Common Name
|
||||
const sslCN = await netify.fetchSSLServerCN(1440, 30, agentUuid);
|
||||
if (sslCN?.length) { db.insertSSLServerCN(sslCN, fetchedAt, SITE_UUID, agentUuid); console.log(`[Scheduler] OK SSL CN : ${sslCN.length} baris`); }
|
||||
else console.log(`[Scheduler] -- SSL CN : tidak ada data`);
|
||||
|
||||
// 30. QUIC Hostname
|
||||
const quicHosts = await netify.fetchQUICHostnames(1440, 30, agentUuid);
|
||||
if (quicHosts?.length) { db.insertQUICHostnames(quicHosts, fetchedAt, SITE_UUID, agentUuid); console.log(`[Scheduler] OK QUIC Host : ${quicHosts.length} baris`); }
|
||||
else console.log(`[Scheduler] -- QUIC Host : tidak ada data`);
|
||||
|
||||
// 31. BitTorrent Info Hash
|
||||
const btHashes = await netify.fetchBitTorrentInfoHashes(1440, 30, agentUuid);
|
||||
if (btHashes?.length) { db.insertBitTorrentHashes(btHashes, fetchedAt, SITE_UUID, agentUuid); console.log(`[Scheduler] OK BT Hash : ${btHashes.length} baris`); }
|
||||
else console.log(`[Scheduler] -- BT Hash : tidak ada data`);
|
||||
|
||||
// 32. SSH Client (field: ssh_client)
|
||||
const sshClient = await netify.fetchSSHClients(1440, 20, agentUuid);
|
||||
if (sshClient?.length) { db.insertSSHVersions(sshClient, fetchedAt, SITE_UUID, agentUuid); console.log(`[Scheduler] OK SSH Client : ${sshClient.length} baris`); }
|
||||
else console.log(`[Scheduler] -- SSH Client : tidak ada data`);
|
||||
|
||||
// 32b. SSH Server (field: ssh_server)
|
||||
const sshServer = await netify.fetchSSHServers(1440, 20, agentUuid);
|
||||
if (sshServer?.length) { db.insertSSHVersions(sshServer, fetchedAt, SITE_UUID, agentUuid); console.log(`[Scheduler] OK SSH Server : ${sshServer.length} baris`); }
|
||||
else console.log(`[Scheduler] -- SSH Server : tidak ada data`);
|
||||
|
||||
// 33. mDNS Hostname (Chromecast, Apple TV, etc.)
|
||||
const mdnsHosts = await netify.fetchMDNSHostnames(1440, 30, agentUuid);
|
||||
if (mdnsHosts?.length) { db.insertMDNSHostnames(mdnsHosts, fetchedAt, SITE_UUID, agentUuid); console.log(`[Scheduler] OK mDNS Host : ${mdnsHosts.length} baris`); }
|
||||
else console.log(`[Scheduler] -- mDNS Host : tidak ada data`);
|
||||
|
||||
// ─── INTELLIGENCE 22-30 (derive dari data yang tersedia) ─────────────────
|
||||
|
||||
// 34. Cryptocurrency Mining (derive dari apps + flows ke port mining)
|
||||
const cryptoMining = await netify.fetchCryptoMining(50, agentUuid);
|
||||
if (cryptoMining?.length) { db.insertCryptoMining(cryptoMining, fetchedAt, SITE_UUID, agentUuid); console.log(`[Scheduler] OK CryptoMine : ${cryptoMining.length} baris`); }
|
||||
else console.log(`[Scheduler] -- CryptoMine : tidak ada data`);
|
||||
|
||||
// 35. Device Discovery (derive dari flows + bandwidth per-IP)
|
||||
const devDisc = await netify.fetchDeviceDiscovery(100, agentUuid);
|
||||
if (devDisc?.length) { db.insertDeviceDiscovery(devDisc, fetchedAt, SITE_UUID, agentUuid); console.log(`[Scheduler] OK DevDisc : ${devDisc.length} baris`); }
|
||||
else console.log(`[Scheduler] -- DevDisc : tidak ada data`);
|
||||
|
||||
// 36. Encryption Audit (derive dari flows per-IP: port encrypted vs plain)
|
||||
const encAudit = await netify.fetchEncryptionAudit(50, agentUuid);
|
||||
if (encAudit?.length) { db.insertEncryptionAudit(encAudit, fetchedAt, SITE_UUID, agentUuid); console.log(`[Scheduler] OK EncAudit : ${encAudit.length} baris`); }
|
||||
else console.log(`[Scheduler] -- EncAudit : tidak ada data`);
|
||||
|
||||
// 37. Insecure Protocols (derive dari top protocols)
|
||||
const insecProto = await netify.fetchInsecureProtocols(1440, 50, agentUuid);
|
||||
if (insecProto?.length) { db.insertInsecureProtocols(insecProto, fetchedAt, SITE_UUID, agentUuid); console.log(`[Scheduler] OK InsecProto : ${insecProto.length} baris`); }
|
||||
else console.log(`[Scheduler] -- InsecProto : tidak ada data`);
|
||||
|
||||
// 38. IP Reputation (derive dari top remote_ip + high-risk countries)
|
||||
const ipRep = await netify.fetchIPReputation(50, agentUuid);
|
||||
if (ipRep?.length) { db.insertIPReputation(ipRep, fetchedAt, SITE_UUID, agentUuid); console.log(`[Scheduler] OK IPRepute : ${ipRep.length} baris`); }
|
||||
else console.log(`[Scheduler] -- IPRepute : tidak ada data`);
|
||||
|
||||
// 39. Server Discovery (derive dari flows ke port server well-known)
|
||||
const srvDisc = await netify.fetchServerDiscovery(100, agentUuid);
|
||||
if (srvDisc?.length) { db.insertServerDiscovery(srvDisc, fetchedAt, SITE_UUID, agentUuid); console.log(`[Scheduler] OK SrvDisc : ${srvDisc.length} baris`); }
|
||||
else console.log(`[Scheduler] -- SrvDisc : tidak ada data`);
|
||||
|
||||
// 40. Tor Detection (derive dari apps/hostnames mengandung "tor")
|
||||
const torDet = await netify.fetchTorDetection(50, agentUuid);
|
||||
if (torDet?.length) { db.insertTorDetection(torDet, fetchedAt, SITE_UUID, agentUuid); console.log(`[Scheduler] OK TorDet : ${torDet.length} baris`); }
|
||||
else console.log(`[Scheduler] -- TorDet : tidak ada data`);
|
||||
|
||||
// 41. Unencrypted Password (derive dari flows ke port cleartext auth)
|
||||
const unencPwd = await netify.fetchUnencryptedPasswords(50, agentUuid);
|
||||
if (unencPwd?.length) { db.insertUnencryptedPasswords(unencPwd, fetchedAt, SITE_UUID, agentUuid); console.log(`[Scheduler] OK UnencPwd : ${unencPwd.length} baris`); }
|
||||
else console.log(`[Scheduler] -- UnencPwd : tidak ada data`);
|
||||
|
||||
// 42. VPN Detection (derive dari apps/protocols/ports VPN)
|
||||
const vpnDet = await netify.fetchVPNDetection(50, agentUuid);
|
||||
if (vpnDet?.length) { db.insertVPNDetection(vpnDet, fetchedAt, SITE_UUID, agentUuid); console.log(`[Scheduler] OK VPNDet : ${vpnDet.length} baris`); }
|
||||
else console.log(`[Scheduler] -- VPNDet : tidak ada data`);
|
||||
|
||||
}
|
||||
|
||||
// --- Main loop
|
||||
await fetchAndStore(fetchedAt, null);
|
||||
if (apiAgents && apiAgents.length > 0) {
|
||||
for (const agent of apiAgents) {
|
||||
if (agent && agent.uuid) {
|
||||
await fetchAndStore(fetchedAt, agent.uuid);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[Scheduler] ERROR:', err);
|
||||
} finally {
|
||||
isRunning = false;
|
||||
console.log(`[Scheduler] Poll selesai.\n`);
|
||||
}
|
||||
}
|
||||
|
||||
function startScheduler() {
|
||||
runPoll();
|
||||
cron.schedule('* * * * *', () => runPoll());
|
||||
console.log('[Scheduler] Aktif. Polling setiap 1 menit.\n');
|
||||
}
|
||||
|
||||
module.exports = { startScheduler, runPoll };
|
||||
+163
-123
@@ -1,123 +1,163 @@
|
||||
// backend/server.js
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// BackOne Backend API Server
|
||||
//
|
||||
// Tanggung jawab backend ini adalah READ-ONLY dari MongoDB.
|
||||
// Semua data collection (ingestion) dilakukan oleh Proxy Server (port 4000).
|
||||
// Backend TIDAK memanggil DPI API secara langsung.
|
||||
//
|
||||
// Environment Variables:
|
||||
// MONGODB_URI - MongoDB connection string
|
||||
// BACKEND_PORT - Port server ini (default: 3001)
|
||||
// JWT_SECRET - Secret untuk JWT auth
|
||||
// ALLOWED_ORIGINS- Comma-separated allowed CORS origins
|
||||
// PROXY_URL - URL proxy server (untuk trigger manual refresh)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
const path = require('path');
|
||||
require('dotenv').config({ path: path.join(__dirname, '..', '.env.local') });
|
||||
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const cookieParser = require('cookie-parser');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const connectDB = require('./db/mongoose');
|
||||
|
||||
// ─── Connect to MongoDB (read-only mode) ──────────────────────────────────────
|
||||
connectDB();
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.BACKEND_PORT || 3001;
|
||||
|
||||
// ─── Middleware ────────────────────────────────────────────────────────────────
|
||||
const ALLOWED_ORIGINS = process.env.ALLOWED_ORIGINS
|
||||
? process.env.ALLOWED_ORIGINS.split(',')
|
||||
: ['http://localhost:3000', 'http://127.0.0.1:3000'];
|
||||
|
||||
app.use(cors({
|
||||
origin: (origin, callback) => {
|
||||
if (!origin) return callback(null, true);
|
||||
if (ALLOWED_ORIGINS.includes(origin)) {
|
||||
callback(null, true);
|
||||
} else {
|
||||
callback(new Error('Blocked by CORS policy (Unauthorized Origin)'));
|
||||
}
|
||||
},
|
||||
credentials: true
|
||||
}));
|
||||
app.use(express.json());
|
||||
app.use(cookieParser());
|
||||
|
||||
// ─── Public Routes ────────────────────────────────────────────────────────────
|
||||
const authRoutes = require('./routes/auth');
|
||||
const { getUploadsDir } = require('./routes/auth/helpers');
|
||||
app.use('/api/auth', authRoutes);
|
||||
app.use('/api/uploads', express.static(getUploadsDir()));
|
||||
|
||||
// ─── Auth Middleware ──────────────────────────────────────────────────────────
|
||||
const { requireAuth } = require('./middleware/auth');
|
||||
const { getTimeFilter, getBaseFilter } = require('./routes/dashboard/helpers');
|
||||
const {
|
||||
generateMacFromIp,
|
||||
resolveVendorFromIp,
|
||||
resolveDeviceTypeFromIp,
|
||||
resolveOSFromIp,
|
||||
generateAutoLabel
|
||||
} = require('./deviceResolver');
|
||||
|
||||
// ─── Protected Dashboard Routes ───────────────────────────────────────────────
|
||||
const dashboardRoutes = require('./routes/dashboard');
|
||||
|
||||
// Override /api/dashboard/app-details to show real-time device mapping per application
|
||||
app.get('/api/dashboard/app-details', requireAuth, (req, res) => {
|
||||
require('./routes/appDetailsHandler')(req, res, {
|
||||
getTimeFilter,
|
||||
getBaseFilter
|
||||
});
|
||||
});
|
||||
|
||||
// Override /api/dashboard/device-details to map real-time classifications (Facebook, YouTube, etc.)
|
||||
app.get('/api/dashboard/device-details', requireAuth, (req, res) => {
|
||||
require('./routes/deviceDetailsHandler')(req, res, {
|
||||
getTimeFilter,
|
||||
getBaseFilter,
|
||||
generateMacFromIp,
|
||||
resolveDeviceTypeFromIp,
|
||||
resolveOSFromIp,
|
||||
resolveVendorFromIp,
|
||||
generateAutoLabel
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/api/dashboard/remote-ip-details', requireAuth, async (req, res) => {
|
||||
require('./routes/remoteIpDetailsHandler')(req, res, {
|
||||
getTimeFilter
|
||||
});
|
||||
});
|
||||
|
||||
const metadataDetailRoutes = require('./routes/metadataDetail');
|
||||
app.use('/api/dashboard/metadata-detail', requireAuth, metadataDetailRoutes);
|
||||
|
||||
const categoryDetailRoutes = require('./routes/categoryDetail');
|
||||
app.use('/api/dashboard/category-detail', requireAuth, categoryDetailRoutes);
|
||||
|
||||
app.use('/api/dashboard', requireAuth, dashboardRoutes);
|
||||
|
||||
|
||||
|
||||
|
||||
// ─── Health Check ─────────────────────────────────────────────────────────────
|
||||
app.get('/api/health', (req, res) => {
|
||||
res.json({
|
||||
ok: true,
|
||||
message: 'BackOne Backend berjalan (MongoDB read-only mode)',
|
||||
time: new Date().toISOString()
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Start Server ─────────────────────────────────────────────────────────────
|
||||
app.listen(PORT, () => {
|
||||
console.log(`\n🚀 BackOne API Server berjalan di http://localhost:${PORT}`);
|
||||
console.log(`🔌 API Health : http://localhost:${PORT}/api/health`);
|
||||
console.log(`📡 Mode : READ-ONLY dari MongoDB (data dikirim oleh Proxy Server)\n`);
|
||||
});
|
||||
// backend/server.js
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// Polyfill global crypto for Node 18 compatibility (required by mongodb driver)
|
||||
if (typeof globalThis.crypto === 'undefined') {
|
||||
globalThis.crypto = require('crypto');
|
||||
}
|
||||
|
||||
// BackOne Backend API Server
|
||||
//
|
||||
// Tanggung jawab backend ini adalah READ-ONLY dari MongoDB.
|
||||
// Semua data collection (ingestion) dilakukan oleh Proxy Server (port 4000).
|
||||
// Backend TIDAK memanggil DPI API secara langsung.
|
||||
//
|
||||
// Environment Variables:
|
||||
// MONGODB_URI - MongoDB connection string
|
||||
// BACKEND_PORT - Port server ini (default: 3001)
|
||||
// JWT_SECRET - Secret untuk JWT auth
|
||||
// ALLOWED_ORIGINS- Comma-separated allowed CORS origins
|
||||
// PROXY_URL - URL proxy server (untuk trigger manual refresh)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
const path = require('path');
|
||||
const envFile = process.env.NODE_ENV === 'production' ? '.env.production' : '.env.local';
|
||||
require('dotenv').config({ path: path.join(__dirname, '..', envFile) });
|
||||
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const cookieParser = require('cookie-parser');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const connectDB = require('./db/mongoose');
|
||||
|
||||
// ─── Connect to MongoDB (read-only mode) ──────────────────────────────────────
|
||||
connectDB();
|
||||
|
||||
const app = express();
|
||||
const PORT = process.env.BACKEND_PORT || 3001;
|
||||
|
||||
// ─── Middleware ────────────────────────────────────────────────────────────────
|
||||
const ALLOWED_ORIGINS = process.env.ALLOWED_ORIGINS
|
||||
? process.env.ALLOWED_ORIGINS.split(',')
|
||||
: ['http://localhost:3000', 'http://127.0.0.1:3000'];
|
||||
|
||||
app.use(cors({
|
||||
origin: (origin, callback) => {
|
||||
if (!origin) return callback(null, true);
|
||||
if (ALLOWED_ORIGINS.includes(origin)) {
|
||||
callback(null, true);
|
||||
} else {
|
||||
callback(new Error('Blocked by CORS policy (Unauthorized Origin)'));
|
||||
}
|
||||
},
|
||||
credentials: true
|
||||
}));
|
||||
app.use(express.json({ limit: '10mb' }));
|
||||
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
|
||||
app.use(cookieParser());
|
||||
|
||||
app.use((req, res, next) => {
|
||||
if (req.originalUrl && req.originalUrl.includes('/api/dashboard')) {
|
||||
try {
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const logPath = path.join(__dirname, '../scratch/http_requests.log');
|
||||
const logLine = `[${new Date().toISOString()}] ${req.method} ${req.originalUrl} - Query: ${JSON.stringify(req.query)}\n`;
|
||||
fs.appendFileSync(logPath, logLine);
|
||||
} catch (e) {
|
||||
console.error('Logger error:', e.message);
|
||||
}
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
|
||||
// ─── Public Routes ────────────────────────────────────────────────────────────
|
||||
const authRoutes = require('./routes/auth');
|
||||
const { getUploadsDir } = require('./routes/auth/helpers');
|
||||
app.use('/api/auth', authRoutes);
|
||||
app.use('/api/uploads', express.static(getUploadsDir()));
|
||||
|
||||
// ─── Auth Middleware ──────────────────────────────────────────────────────────
|
||||
const { requireAuth } = require('./middleware/auth');
|
||||
const { getTimeFilter, getBaseFilter } = require('./routes/dashboard/helpers');
|
||||
const {
|
||||
generateMacFromIp,
|
||||
resolveVendorFromIp,
|
||||
resolveDeviceTypeFromIp,
|
||||
resolveOSFromIp,
|
||||
generateAutoLabel
|
||||
} = require('./deviceResolver');
|
||||
|
||||
// ─── Protected Dashboard Routes ───────────────────────────────────────────────
|
||||
const dashboardRoutes = require('./routes/dashboard');
|
||||
|
||||
// Override /api/dashboard/app-details to show real-time device mapping per application
|
||||
app.get('/api/dashboard/app-details', requireAuth, (req, res) => {
|
||||
require('./routes/appDetailsHandler')(req, res, {
|
||||
getTimeFilter,
|
||||
getBaseFilter
|
||||
});
|
||||
});
|
||||
|
||||
// Override /api/dashboard/device-details to map real-time classifications (Facebook, YouTube, etc.)
|
||||
app.get('/api/dashboard/device-details', requireAuth, (req, res) => {
|
||||
require('./routes/deviceDetailsHandler')(req, res, {
|
||||
getTimeFilter,
|
||||
getBaseFilter,
|
||||
generateMacFromIp,
|
||||
resolveDeviceTypeFromIp,
|
||||
resolveOSFromIp,
|
||||
resolveVendorFromIp,
|
||||
generateAutoLabel
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/api/dashboard/remote-ip-details', requireAuth, async (req, res) => {
|
||||
require('./routes/remoteIpDetailsHandler')(req, res, {
|
||||
getTimeFilter
|
||||
});
|
||||
});
|
||||
|
||||
const metadataDetailRoutes = require('./routes/metadataDetail');
|
||||
app.use('/api/dashboard/metadata-detail', requireAuth, metadataDetailRoutes);
|
||||
|
||||
const categoryDetailRoutes = require('./routes/categoryDetail');
|
||||
app.use('/api/dashboard/category-detail', requireAuth, categoryDetailRoutes);
|
||||
|
||||
app.use('/api/dashboard', requireAuth, dashboardRoutes);
|
||||
|
||||
|
||||
|
||||
|
||||
// ─── Health Check ─────────────────────────────────────────────────────────────
|
||||
app.get('/api/health', (req, res) => {
|
||||
res.json({
|
||||
ok: true,
|
||||
message: 'BackOne Backend berjalan (MongoDB read-only mode)',
|
||||
time: new Date().toISOString()
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Global JSON Error Handler ────────────────────────────────────────────────
|
||||
// Menangkap semua error yang tidak di-handle (termasuk multer, mongoose, dll.)
|
||||
// dan memastikan response selalu JSON, BUKAN HTML default Express.
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
app.use((err, req, res, next) => {
|
||||
console.error('[Global Error Handler]', err.message || err);
|
||||
const status = err.status || err.statusCode || 500;
|
||||
res.status(status).json({
|
||||
error: err.message || 'Internal server error',
|
||||
code: err.code || undefined,
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Start Server ─────────────────────────────────────────────────────────────
|
||||
// Bind to 127.0.0.1 in production to prevent direct external access to port 3001.
|
||||
// All external traffic must go through the reverse proxy (Apache/Nginx) at port 80/443.
|
||||
const BIND_HOST = process.env.NODE_ENV === 'production' ? '127.0.0.1' : '0.0.0.0';
|
||||
app.listen(PORT, BIND_HOST, () => {
|
||||
console.log(`\n🚀 BackOne API Server berjalan di http://${BIND_HOST}:${PORT}`);
|
||||
console.log(`🔌 API Health : http://${BIND_HOST}:${PORT}/api/health`);
|
||||
console.log(`📡 Mode : READ-ONLY dari MongoDB (data dikirim oleh Proxy Server)`);
|
||||
console.log(`🔒 Security : Bound to ${BIND_HOST} (internal only in production)\n`);
|
||||
});
|
||||
@@ -0,0 +1,135 @@
|
||||
const cron = require('node-cron');
|
||||
const netify = require('../netify');
|
||||
const { Summary, AppStat, ProtocolStat, DeviceStat, Flow, Threat } = require('../models/Schemas');
|
||||
|
||||
const SITE_UUID = process.env.NETIFY_SITE_UUID || process.env.BACKONE_SITE_UUID;
|
||||
let isRunning = false;
|
||||
|
||||
async function runPoll() {
|
||||
if (isRunning) return;
|
||||
isRunning = true;
|
||||
const timestamp = new Date();
|
||||
console.log(`[Mongo-Ingestion] Started polling at ${timestamp.toISOString()}`);
|
||||
|
||||
try {
|
||||
const agents = await netify.fetchAgents();
|
||||
const agentList = agents && agents.length > 0 ? agents.map(a => a.uuid) : [null]; // null for global
|
||||
|
||||
for (const agentUuid of agentList) {
|
||||
console.log(`[Mongo-Ingestion] Fetching data for Agent: ${agentUuid || 'Global'}`);
|
||||
|
||||
// 1. Summary
|
||||
const summary = await netify.fetchBandwidthSummary(1440, agentUuid);
|
||||
if (summary) {
|
||||
await new Summary({
|
||||
timestamp,
|
||||
agent_uuid: agentUuid,
|
||||
site_uuid: SITE_UUID,
|
||||
...summary
|
||||
}).save();
|
||||
}
|
||||
|
||||
// 2. Apps
|
||||
const apps = await netify.fetchTopApps(1440, 200, agentUuid); // high limit for data lake
|
||||
if (apps && apps.length > 0) {
|
||||
const appDocs = apps.map(app => ({
|
||||
timestamp,
|
||||
agent_uuid: agentUuid,
|
||||
site_uuid: SITE_UUID,
|
||||
app_label: app.application?.label || 'Unknown',
|
||||
download: app.download || 0,
|
||||
upload: app.upload || 0,
|
||||
flows: app.flows || 0
|
||||
}));
|
||||
await AppStat.insertMany(appDocs);
|
||||
}
|
||||
|
||||
const devices = await netify.fetchDiscoveredDevices(1440, 500, agentUuid);
|
||||
if (devices && devices.length > 0) {
|
||||
const devDocs = devices.map(d => ({
|
||||
timestamp,
|
||||
agent_uuid: agentUuid,
|
||||
site_uuid: SITE_UUID,
|
||||
ip_address: d.ip_address,
|
||||
mac_address: d.mac_address,
|
||||
device_label: d.device_label,
|
||||
device_type: d.device_type,
|
||||
os_label: d.os_label,
|
||||
manufacturer: d.manufacturer,
|
||||
download: d.download || 0,
|
||||
upload: d.upload || 0,
|
||||
flows: d.flows || 0,
|
||||
last_seen: d.last_seen
|
||||
})).filter(d => d.ip_address); // Ensure ip_address exists to avoid validation error
|
||||
if (devDocs.length > 0) {
|
||||
await DeviceStat.insertMany(devDocs);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Flows
|
||||
const flows = await netify.fetchFlows(500, agentUuid);
|
||||
if (flows && flows.length > 0) {
|
||||
const flowDocs = flows.map(f => ({
|
||||
timestamp,
|
||||
agent_uuid: agentUuid,
|
||||
site_uuid: SITE_UUID,
|
||||
flow_id: f.flow_id,
|
||||
src_ip: f.src_ip,
|
||||
src_mac: f.src_mac,
|
||||
dst_ip: f.dst_ip,
|
||||
dst_port: f.dst_port,
|
||||
protocol: f.protocol,
|
||||
app_label: f.app_label,
|
||||
domain: f.domain,
|
||||
download: f.download || 0,
|
||||
upload: f.upload || 0,
|
||||
first_seen: f.first_seen,
|
||||
last_seen: f.last_seen
|
||||
})).filter(f => f.src_ip);
|
||||
if (flowDocs.length > 0) {
|
||||
await Flow.insertMany(flowDocs);
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Threats
|
||||
const threats = await netify.fetchCyberThreats(agentUuid);
|
||||
if (threats && threats.length > 0) {
|
||||
const threatDocs = threats.map(t => ({
|
||||
timestamp,
|
||||
agent_uuid: agentUuid,
|
||||
site_uuid: SITE_UUID,
|
||||
threat_type: t.threat_type || 'Unknown Threat',
|
||||
severity: t.severity || 'Medium',
|
||||
src_ip: t.src_ip,
|
||||
dst_ip: t.dst_ip,
|
||||
dst_port: t.dst_port,
|
||||
protocol: t.protocol,
|
||||
description: t.description,
|
||||
event_at: t.event_at || new Date().toISOString()
|
||||
}));
|
||||
if (threatDocs.length > 0) {
|
||||
await Threat.insertMany(threatDocs);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[Mongo-Ingestion] Error during polling:', error);
|
||||
} finally {
|
||||
isRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
function startScheduler() {
|
||||
// Run every 5 minutes
|
||||
cron.schedule('*/5 * * * *', () => {
|
||||
runPoll();
|
||||
});
|
||||
console.log('[Mongo-Ingestion] Scheduler started (every 5 minutes)');
|
||||
|
||||
// Initial run
|
||||
runPoll();
|
||||
}
|
||||
|
||||
module.exports = { startScheduler };
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
const http = require('http');
|
||||
|
||||
http.get('http://localhost:3001/api/dashboard/tls-versions', {
|
||||
headers: {
|
||||
'Cookie': 'token=test', // Just checking schema, if it requires auth we might need to mock or use the proxy
|
||||
}
|
||||
}, (res) => {
|
||||
let data = '';
|
||||
res.on('data', chunk => data += chunk);
|
||||
res.on('end', () => {
|
||||
console.log("Response TLS Versions:");
|
||||
console.log(data.slice(0, 500));
|
||||
});
|
||||
});
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 429 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 429 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 429 KiB |
Reference in new issue
Block a user