Files
cve-dashboard/backend/scripts/backfill-os-fields.js
Jordan Ramos 416bfd2e28 Add Infoblox DNS lookup, Scan Posture page, and Ivanti OS fields
- 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
2026-08-21 10:33:46 -06:00

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