Add Atlas metrics reporting, security audit tracker, and spec documents

This commit is contained in:
root
2026-04-24 17:30:06 +00:00
parent 8bf8dc55dd
commit 5ffedad02f
11 changed files with 2101 additions and 22 deletions

View File

@@ -31,16 +31,96 @@ function dbAll(db, sql, params = []) {
});
}
// ---------------------------------------------------------------------------
// Pure aggregation function — exported for testability
// ---------------------------------------------------------------------------
function aggregateAtlasMetrics(rows) {
const result = {
totalHosts: rows.length,
hostsWithPlans: 0,
hostsWithoutPlans: 0,
plansByType: {},
plansByStatus: {},
totalPlans: 0
};
for (const row of rows) {
if (row.has_action_plan === 1) {
result.hostsWithPlans++;
} else {
result.hostsWithoutPlans++;
}
let plans;
try {
plans = JSON.parse(row.plans_json);
} catch (e) {
// Invalid JSON — skip plan details for this row
continue;
}
if (!Array.isArray(plans)) continue;
for (const plan of plans) {
result.totalPlans++;
if (plan.plan_type) {
result.plansByType[plan.plan_type] = (result.plansByType[plan.plan_type] || 0) + 1;
}
if (plan.status) {
result.plansByStatus[plan.status] = (result.plansByStatus[plan.status] || 0) + 1;
}
}
}
return result;
}
// ---------------------------------------------------------------------------
// Router factory
// ---------------------------------------------------------------------------
function createAtlasRouter(db, requireAuth) {
const router = express.Router();
// -----------------------------------------------------------------------
// GET /metrics
// Return aggregated Atlas metrics for chart rendering.
// Auth: any authenticated user
//
// Response 200:
// { totalHosts: number, hostsWithPlans: number, hostsWithoutPlans: number,
// plansByType: { [type: string]: number }, plansByStatus: { [status: string]: number },
// totalPlans: number }
// Response 503: { error: string } — Atlas not configured
// Response 500: { error: string } — DB query failure
// -----------------------------------------------------------------------
router.get('/metrics', requireAuth(db), async (req, res) => {
if (!isConfigured) {
return res.status(503).json({ error: 'Atlas API is not configured. Check ATLAS_API_URL, ATLAS_API_USER, and ATLAS_API_PASS environment variables.' });
}
try {
const rows = await dbAll(db,
`SELECT has_action_plan, plans_json FROM atlas_action_plans_cache`
);
const metrics = aggregateAtlasMetrics(rows);
res.json(metrics);
} catch (err) {
console.error('[Atlas] Error fetching metrics:', err.message);
res.status(500).json({ error: 'Failed to fetch Atlas metrics.' });
}
});
// -----------------------------------------------------------------------
// GET /status
// Return all cached Atlas rows for badge rendering.
// Auth: any authenticated user
//
// Response 200:
// [ { host_id: number, has_action_plan: 0|1, plan_count: number, synced_at: string }, ... ]
// Response 503: { error: string } — Atlas not configured
// Response 500: { error: string } — DB query failure
// -----------------------------------------------------------------------
router.get('/status', requireAuth(db), async (req, res) => {
if (!isConfigured) {
@@ -62,6 +142,12 @@ function createAtlasRouter(db, requireAuth) {
// POST /sync
// Sync Atlas action plan data for all hosts found in the Ivanti cache.
// Auth: Admin or Standard_User
//
// Request body: none
// Response 200:
// { synced: number, withPlans: number, failed: number }
// Response 503: { error: string } — Atlas not configured
// Response 500: { error: string } — sync failure or Ivanti cache parse error
// -----------------------------------------------------------------------
router.post('/sync', requireAuth(db), requireGroup('Admin', 'Standard_User'), async (req, res) => {
if (!isConfigured) {
@@ -187,6 +273,12 @@ function createAtlasRouter(db, requireAuth) {
// GET /hosts/:hostId/action-plans
// Proxy to Atlas API — returns live action plan data for a single host.
// Auth: any authenticated user
//
// Params: hostId (positive integer)
// Response 2xx: proxied Atlas response body (parsed JSON or raw)
// Response 400: { error: string } — invalid hostId
// Response 503: { error: string } — Atlas not configured
// Response 502: { error: string } — Atlas API unreachable
// -----------------------------------------------------------------------
router.get('/hosts/:hostId/action-plans', requireAuth(db), async (req, res) => {
if (!isConfigured) {
@@ -229,6 +321,16 @@ function createAtlasRouter(db, requireAuth) {
// PUT /hosts/:hostId/action-plans
// Create a new action plan for a host.
// Auth: Admin or Standard_User
//
// Params: hostId (positive integer)
// Request body:
// { plan_type: string (one of VALID_PLAN_TYPES), commit_date: string (YYYY-MM-DD),
// qualys_id?: string, active_host_findings_id?: string,
// jira_vnr?: string, archer_exc?: string }
// Response 2xx: proxied Atlas response body
// Response 400: { error: string } — invalid hostId, plan_type, or commit_date
// Response 503: { error: string } — Atlas not configured
// Response 502: { error: string } — Atlas API unreachable
// -----------------------------------------------------------------------
router.put('/hosts/:hostId/action-plans', requireAuth(db), requireGroup('Admin', 'Standard_User'), async (req, res) => {
if (!isConfigured) {
@@ -290,6 +392,14 @@ function createAtlasRouter(db, requireAuth) {
// PATCH /hosts/:hostId/action-plans
// Update an existing action plan for a host.
// Auth: Admin or Standard_User
//
// Params: hostId (positive integer)
// Request body:
// { action_plan_id: string (non-empty), updates: object (non-null, non-array) }
// Response 2xx: proxied Atlas response body
// Response 400: { error: string } — invalid hostId, action_plan_id, or updates
// Response 503: { error: string } — Atlas not configured
// Response 502: { error: string } — Atlas API unreachable
// -----------------------------------------------------------------------
router.patch('/hosts/:hostId/action-plans', requireAuth(db), requireGroup('Admin', 'Standard_User'), async (req, res) => {
if (!isConfigured) {
@@ -351,6 +461,15 @@ function createAtlasRouter(db, requireAuth) {
// POST /hosts/bulk-action-plans
// Create action plans for multiple hosts at once.
// Auth: Admin or Standard_User
//
// Request body:
// { host_ids: number[] (non-empty, positive integers),
// plan_type: string (one of VALID_PLAN_TYPES),
// commit_date: string (YYYY-MM-DD) }
// Response 2xx: proxied Atlas response body
// Response 400: { error: string } — invalid host_ids, plan_type, or commit_date
// Response 503: { error: string } — Atlas not configured
// Response 502: { error: string } — Atlas API unreachable
// -----------------------------------------------------------------------
router.post('/hosts/bulk-action-plans', requireAuth(db), requireGroup('Admin', 'Standard_User'), async (req, res) => {
if (!isConfigured) {
@@ -407,3 +526,4 @@ function createAtlasRouter(db, requireAuth) {
}
module.exports = createAtlasRouter;
module.exports.aggregateAtlasMetrics = aggregateAtlasMetrics;