aere-quantum/control-plane/control-plane.mjs

87 lines
4.6 KiB
JavaScript

#!/usr/bin/env node
'use strict';
// AERE Quantum Security Control Plane (B1), CLI cap la cap: inventar criptografic + (optional) scanare de pregatire PQ -> plan de
// migrare prioritizat. Compune uneltele care exista deja - nu reinventeaza: inventarul (crypto-inventory), scanerul (dat ca JSON),
// planificatorul (plan-migrare.mjs). Ruleaza offline pe un dosar de cod; scanarea unui hostname se da aici cu --scan <fisier.json>.
// Iesirea e in engleza (forma planului 2, vezi plan-migrare.mjs).
//
// node control-plane.mjs --code <dir> [--scan scan.json] [--json]
// iesire 0 = plan produs (chiar si gol); 2 = nu s-a putut rula.
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
const AICI = path.dirname(fileURLToPath(import.meta.url));
const RAD = path.resolve(AICI, '..', '..');
// inventarul: langa planul de control intr-o copie publica (../crypto-inventory), sau in depozitul de dezvoltare (tools/)
export function caleaInventarului() {
const c = [path.join(AICI, '..', 'crypto-inventory', 'inventar.mjs'), path.join(RAD, 'tools', 'crypto-inventory', 'inventar.mjs')];
return c.find((p) => fs.existsSync(p)) || c[c.length - 1];
}
// scanerul de pregatire PQ: in dosarul readiness/ al unei copii publice, sau langa planul de control (depozitul de dezvoltare)
export function caleaScanerului() {
const c = [path.join(AICI, '..', 'readiness', 'readiness-service.mjs'), path.join(AICI, '..', 'readiness-service.mjs')];
return c.find((p) => fs.existsSync(p)) || c[c.length - 1];
}
// adaptor: findings-ul inventarului e DEJA clasificat (are assetType, name, primitive, quantumVulnerable, certificate in engleza);
// nu se re-cheama classify. Se adauga doar ref (unic pe fisier:linie) si locatia.
function laPlanificator(g) {
return {
ref: `crypto:${g.assetType || 'algorithm'}:${g.name || g.primitive || 'unknown'}:${g.file || ''}:${g.line || ''}`,
assetType: g.assetType || 'algorithm',
name: g.name,
// primitiva inventarului trece NEATINSA: pentru certificate ea e 'unknown'/'other' (a cheii), iar ce inseamna asta pentru un
// certificat decide planificatorul (primitivaDe), intr-un singur loc
primitive: g.primitive,
quantumVulnerable: g.quantumVulnerable,
curve: g.curve, parameterSetIdentifier: g.parameterSetIdentifier,
certificate: g.certificate,
material: g.material,
location: g.file ? `${g.file}:${g.line || '?'}` : undefined,
};
}
async function main() {
const args = process.argv.slice(2);
const jsonOut = args.includes('--json');
const codeI = args.indexOf('--code'); const code = codeI >= 0 ? args[codeI + 1] : null;
const scanI = args.indexOf('--scan'); const scanFile = scanI >= 0 ? args[scanI + 1] : null;
if (!code) { console.error('usage: node control-plane.mjs --code <dir> [--scan scan.json] [--json]'); process.exit(2); }
const inv = await import(pathToFileURL(caleaInventarului()).href);
const { planeaza } = await import(pathToFileURL(path.join(AICI, 'plan-migrare.mjs')).href);
let rez; try { rez = inv.scan(code); } catch (e) { console.error('the inventory could not scan: ' + e.message); process.exit(2); }
const clasificate = (rez.findings || []).map(laPlanificator);
let scan = null;
if (scanFile) {
try { scan = JSON.parse(fs.readFileSync(scanFile, 'utf8')); } catch (e) { console.error('cannot read the scan: ' + e.message); process.exit(2); }
}
const plan = planeaza({ inventory: clasificate, scan });
const iesire = {
generatedAt: new Date().toISOString(),
code, scan: scanFile || null,
inventory: { findings: (rez.findings || []).length, quantumVulnerable: clasificate.filter((c) => c.quantumVulnerable).length },
...plan,
};
if (jsonOut) { console.log(JSON.stringify(iesire, null, 1)); process.exit(0); }
console.log(`AERE Control Plane - post-quantum migration plan for ${code}${scanFile ? ' + ' + scanFile : ''}`);
console.log(` inventory: ${iesire.inventory.findings} uses, ${iesire.inventory.quantumVulnerable} quantum-vulnerable`);
console.log(` plan: ${plan.summary.total} actions | ${JSON.stringify(plan.summary.byUrgency)} | auto=${plan.summary.autoAere} manual=${plan.summary.manual} blocked=${plan.summary.blocked}`);
for (const a of plan.actions) {
console.log(` [${a.urgency.padEnd(8)}] ${a.asset}${a.location ? ' (' + a.location + ')' : ''}`);
console.log(` ${a.problem} -> ${a.target} [${a.method}${a.product ? ' via ' + a.product : ''}]${a.blocker ? ' BLOCKED BY: ' + a.blocker : ''}`);
}
process.exit(0);
}
if (import.meta.url === pathToFileURL(process.argv[1] || '').href) {
main().catch((e) => { console.error('error: ' + (e.stack || e.message)); process.exit(2); });
}