Add optional Fingerbank device fingerprinting to deep-check
Folded into the existing on-demand Deep check button: queries Fingerbank's interrogate API with the device's MAC plus the SSDP SERVER header when deep-check-device.sh finds one, showing the confidence band alongside the result. Runs directly from the API container (no host-level access needed, just an outbound HTTPS call), unlike the SSDP/mDNS steps. Confirmed via direct testing: without DHCP fingerprint data (which we structurally don't have, not being the DHCP server), MAC-only queries often can't get past manufacturer-level confidence -- same info the free OUI lookup already provides. Documented honestly in docs/device-discovery.md rather than overselling it. Still worth having as opt-in enrichment for devices that do expose richer signals. Gated behind optional FINGERBANK_API_KEY -- missing key, API errors, or no match all degrade gracefully without affecting the rest of deep-check's local findings. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,8 +6,15 @@ import type Database from "better-sqlite3";
|
||||
// require()) -- import the default and destructure instead.
|
||||
import macOuiLookup from "mac-oui-lookup";
|
||||
const { getVendor } = macOuiLookup;
|
||||
import { getRecentDevices, setDeviceLabel, clearDeviceLabel, type DeviceRow } from "../db/index.js";
|
||||
import {
|
||||
getRecentDevices,
|
||||
getDeviceMac,
|
||||
setDeviceLabel,
|
||||
clearDeviceLabel,
|
||||
type DeviceRow,
|
||||
} from "../db/index.js";
|
||||
import { deepCheckDevice } from "../discovery/deepCheck.js";
|
||||
import { identifyDevice } from "../discovery/fingerbank.js";
|
||||
import type { DeepCheckHostConfig } from "../config/index.js";
|
||||
|
||||
async function requireAuth(req: FastifyRequest, reply: FastifyReply) {
|
||||
@@ -40,7 +47,8 @@ const IP_RE = /^192\.168\.1\.([0-9]{1,3})$/;
|
||||
export function registerDeviceRoutes(
|
||||
app: FastifyInstance,
|
||||
db: Database.Database,
|
||||
deepCheckConfig: DeepCheckHostConfig | undefined
|
||||
deepCheckConfig: DeepCheckHostConfig | undefined,
|
||||
fingerbankApiKey: string | undefined
|
||||
): void {
|
||||
app.get("/api/devices", { preHandler: requireAuth }, async () => {
|
||||
const rows = getRecentDevices(db);
|
||||
@@ -72,9 +80,10 @@ export function registerDeviceRoutes(
|
||||
);
|
||||
|
||||
// Admin-triggered, single-device, on-demand -- not automatic, to avoid the
|
||||
// noise/risk of doing this for the whole subnet on every poll. Runs on the
|
||||
// CT122 host via SSH (see docs/device-discovery.md); can take up to ~15s
|
||||
// (SSDP listen window + bounded port scan).
|
||||
// noise/risk of doing this for the whole subnet on every poll (and, for
|
||||
// Fingerbank, avoids sending every device's MAC to a third party by
|
||||
// default). Runs on the CT122 host via SSH (see docs/device-discovery.md);
|
||||
// can take up to ~15s (SSDP listen window + bounded port scan).
|
||||
app.post<{ Params: { ip: string } }>(
|
||||
"/api/devices/:ip/deep-check",
|
||||
{ preHandler: requireAuth },
|
||||
@@ -83,7 +92,26 @@ export function registerDeviceRoutes(
|
||||
if (!IP_RE.test(req.params.ip)) return reply.code(400).send({ error: "invalid IP address" });
|
||||
try {
|
||||
const result = await deepCheckDevice(deepCheckConfig, req.params.ip);
|
||||
return result;
|
||||
|
||||
// Fingerbank is optional enrichment -- a missing key, rate limit, or
|
||||
// network hiccup shouldn't sink the local findings that already
|
||||
// succeeded via SSH.
|
||||
let fingerbank = null;
|
||||
if (fingerbankApiKey) {
|
||||
const mac = getDeviceMac(db, req.params.ip);
|
||||
if (mac) {
|
||||
try {
|
||||
fingerbank = await identifyDevice(fingerbankApiKey, {
|
||||
mac,
|
||||
upnpServer: result.ssdp?.server ?? null,
|
||||
});
|
||||
} catch (err) {
|
||||
req.log.error({ err, ip: req.params.ip }, "fingerbank lookup failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { ...result, fingerbank };
|
||||
} catch (err) {
|
||||
req.log.error({ err, ip: req.params.ip }, "deep check failed");
|
||||
return reply.code(502).send({ error: err instanceof Error ? err.message : "deep check failed" });
|
||||
|
||||
Reference in New Issue
Block a user