Files
cve-dashboard/backend/routes/infoblox.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

153 lines
5.5 KiB
JavaScript

// Infoblox DNS Lookup Routes (read-only)
// Provides reverse DNS lookups via the Infoblox WAPI proxy at api-proxy-ease.charterlab.com.
// Used by the Reporting page to resolve IPs to their authoritative FQDN.
const express = require('express');
const { requireAuth, requireGroup } = require('../middleware/auth');
const {
isConfigured,
missingVars,
lookupAnyByIp,
lookupByIpv6,
lookupByName,
} = require('../helpers/infobloxApi');
// ---------------------------------------------------------------------------
// Router factory
// ---------------------------------------------------------------------------
function createInfobloxRouter() {
const router = express.Router();
// All Infoblox routes require auth + Admin or Standard_User
router.use(requireAuth(), requireGroup('Admin', 'Standard_User'));
/**
* GET /status
*
* Returns whether the Infoblox integration is configured.
*/
router.get('/status', (_req, res) => {
if (!isConfigured) {
return res.status(503).json({ configured: false, error: 'Infoblox API is not configured.', missingVars });
}
return res.json({ configured: true });
});
/**
* GET /lookup/ip/:ip
*
* Look up host records by IPv4 address. Tries host record first, then
* falls back to PTR record (many IPs only have PTR in Infoblox).
*
* @param {string} ip - IPv4 address to look up
* @response 200 - { records: [...], source: 'host'|'ptr' }
* @response 404 - { error: string } — no records found
* @response 503 - not configured
*/
router.get('/lookup/ip/:ip', async (req, res) => {
if (!isConfigured) {
return res.status(503).json({ error: 'Infoblox API is not configured.', missingVars });
}
const { ip } = req.params;
if (!ip || !ip.trim()) {
return res.status(400).json({ error: 'IP address is required.' });
}
try {
const result = await lookupAnyByIp(ip.trim());
if (!result.ok) {
return res.status(502).json({ error: 'Infoblox lookup failed.', details: result.error });
}
if (result.records.length === 0) {
return res.status(404).json({ error: `No host or PTR records found for IP ${ip}.` });
}
return res.json({ records: result.records, source: result.source });
} catch (err) {
console.error('[infoblox]', err.message);
if (err.message.includes('timed out')) {
return res.status(504).json({ error: 'Infoblox request timed out.' });
}
return res.status(502).json({ error: 'Infoblox request failed.', details: err.message });
}
});
/**
* GET /lookup/ipv6/:ip
*
* Look up host records by IPv6 address.
*
* @param {string} ip - IPv6 address to look up
* @response 200 - { records: [...] }
* @response 404 - no records found
*/
router.get('/lookup/ipv6/:ip', async (req, res) => {
if (!isConfigured) {
return res.status(503).json({ error: 'Infoblox API is not configured.', missingVars });
}
const { ip } = req.params;
if (!ip || !ip.trim()) {
return res.status(400).json({ error: 'IPv6 address is required.' });
}
try {
const result = await lookupByIpv6(ip.trim());
if (!result.ok) {
return res.status(result.status || 502).json({ error: 'Infoblox lookup failed.', details: result.error });
}
if (result.records.length === 0) {
return res.status(404).json({ error: `No host records found for IPv6 ${ip}.` });
}
return res.json({ records: result.records });
} catch (err) {
console.error('[infoblox]', err.message);
if (err.message.includes('timed out')) {
return res.status(504).json({ error: 'Infoblox request timed out.' });
}
return res.status(502).json({ error: 'Infoblox request failed.', details: err.message });
}
});
/**
* GET /lookup/name/:fqdn
*
* Look up host records by FQDN (exact match).
*
* @param {string} fqdn - Fully qualified domain name
* @response 200 - { records: [...] }
* @response 404 - no records found
*/
router.get('/lookup/name/:fqdn', async (req, res) => {
if (!isConfigured) {
return res.status(503).json({ error: 'Infoblox API is not configured.', missingVars });
}
const { fqdn } = req.params;
if (!fqdn || !fqdn.trim()) {
return res.status(400).json({ error: 'FQDN is required.' });
}
try {
const result = await lookupByName(fqdn.trim());
if (!result.ok) {
return res.status(result.status || 502).json({ error: 'Infoblox lookup failed.', details: result.error });
}
if (result.records.length === 0) {
return res.status(404).json({ error: `No host records found for ${fqdn}.` });
}
return res.json({ records: result.records });
} catch (err) {
console.error('[infoblox]', err.message);
if (err.message.includes('timed out')) {
return res.status(504).json({ error: 'Infoblox request timed out.' });
}
return res.status(502).json({ error: 'Infoblox request failed.', details: err.message });
}
});
return router;
}
module.exports = createInfobloxRouter;