- Infoblox WAPI integration (read-only) for resolving IPs to FQDNs - backend/helpers/infobloxApi.js — Basic auth, host/PTR/IPv6 lookups - backend/routes/infoblox.js — /api/infoblox endpoints - Globe icon on Reporting page hostName/dns columns for one-click DNS lookup - Pending: WAPI credentials with API access permissions - Scan Posture page for Access Ops platform/version tracking - backend/routes/scanPosture.js - frontend/src/components/pages/ScanPosturePage.js - Page visibility and nav drawer entries - Ivanti findings OS field enrichment - Migration to add os_name/os_class/os_version columns - Backfill script for existing findings - ivantiFindings route updates to persist OS data on sync
103 lines
3.6 KiB
JavaScript
103 lines
3.6 KiB
JavaScript
#!/usr/bin/env node
|
|
// One-time backfill: Fetch OS info from Ivanti API for existing local findings
|
|
// and populate the new os_name, os_family, os_vendor columns.
|
|
//
|
|
// Strategy: Get all distinct host_ids from local DB, then fetch findings by host
|
|
// in batches to get the operatingSystemScanner data. Much faster than scanning
|
|
// all 800K+ Ivanti findings page by page.
|
|
//
|
|
// Safe to re-run — only updates rows where os_name IS NULL.
|
|
|
|
require('dotenv').config();
|
|
const pool = require('../db');
|
|
const { ivantiPost } = require('../helpers/ivantiApi');
|
|
|
|
const apiKey = process.env.IVANTI_API_KEY;
|
|
const clientId = process.env.IVANTI_CLIENT_ID || '1550';
|
|
const skipTls = process.env.IVANTI_SKIP_TLS === 'true';
|
|
|
|
async function backfill() {
|
|
if (!apiKey) {
|
|
console.error('IVANTI_API_KEY not set');
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log('[Backfill OS] Starting...');
|
|
|
|
// Get all local finding IDs that need OS backfill
|
|
const { rows } = await pool.query(
|
|
`SELECT id FROM ivanti_findings WHERE os_name IS NULL LIMIT 10000`
|
|
);
|
|
|
|
if (rows.length === 0) {
|
|
console.log('[Backfill OS] No findings need backfill — all have os_name populated.');
|
|
await pool.end();
|
|
return;
|
|
}
|
|
|
|
console.log(`[Backfill OS] ${rows.length} findings need OS data`);
|
|
|
|
// Process in batches of 100 IDs via Ivanti search filter
|
|
const BATCH = 100;
|
|
let updated = 0;
|
|
const allIds = rows.map(r => r.id);
|
|
|
|
for (let i = 0; i < allIds.length; i += BATCH) {
|
|
const batchIds = allIds.slice(i, i + BATCH);
|
|
const urlPath = `/client/${encodeURIComponent(clientId)}/hostFinding/search`;
|
|
|
|
try {
|
|
const result = await ivantiPost(urlPath, {
|
|
filters: [
|
|
{
|
|
field: 'id',
|
|
operator: 'IN',
|
|
value: batchIds.join(','),
|
|
exclusive: false,
|
|
orWithPrevious: false,
|
|
implicitFilters: [],
|
|
caseSensitive: false
|
|
}
|
|
],
|
|
projection: 'internal',
|
|
sort: [{ field: 'id', direction: 'ASC' }],
|
|
page: 0,
|
|
size: BATCH
|
|
}, apiKey, skipTls);
|
|
|
|
if (result.status !== 200) {
|
|
console.warn(`[Backfill OS] API returned ${result.status} for batch starting at ${i}`);
|
|
continue;
|
|
}
|
|
|
|
const data = JSON.parse(result.body);
|
|
const findings = data._embedded?.hostFindings || [];
|
|
|
|
for (const f of findings) {
|
|
const id = String(f.id);
|
|
const os = f.operatingSystemScanner || {};
|
|
const osName = os.name || null;
|
|
const osFamily = (os.family && os.family !== 'Not Reported') ? os.family : null;
|
|
const osVendor = (os.vendor && os.vendor !== 'Not Reported') ? os.vendor : null;
|
|
|
|
if (osName) {
|
|
await pool.query(
|
|
`UPDATE ivanti_findings SET os_name = $1, os_family = $2, os_vendor = $3 WHERE id = $4`,
|
|
[osName, osFamily, osVendor, id]
|
|
);
|
|
updated++;
|
|
}
|
|
}
|
|
|
|
console.log(`[Backfill OS] Batch ${Math.floor(i / BATCH) + 1}/${Math.ceil(allIds.length / BATCH)} — ${updated} updated`);
|
|
} catch (err) {
|
|
console.error(`[Backfill OS] Batch error at offset ${i}:`, err.message);
|
|
}
|
|
}
|
|
|
|
console.log(`[Backfill OS] Complete — ${updated} findings updated with OS info`);
|
|
await pool.end();
|
|
}
|
|
|
|
backfill();
|