Skip to content

E4 — Resource Graph Query Library: Test Runbook

Asset: Resource Graph Query Library (E4) Location: apps/evidence-collector/src/resource-graph/ Owner: SnowOps (SH) Milestone: M4


Overview

The Resource Graph library contains 7 pre-built KQL queries mapped to SOC 2 CC and ISO 27001 A.x controls. The mapper converts query results to EvidenceItem[] for consumption by any EvidencePlatform.

Queries: | ID | Name | Controls | |----|------|---------| | storage-cmk | Storage CMK Encryption | SOC2 CC6.7, ISO A.8.24 | | public-network-access | Public Network Access Disabled | SOC2 CC6.6, ISO A.8.20 | | storage-tls | Storage TLS 1.2+ | SOC2 CC6.7, ISO A.8.24 | | owner-role-assignments | No Direct User Owner Assignments | SOC2 CC6.1, ISO A.8.5 | | defender-plans | Defender Plans Enabled | SOC2 CC7.1, ISO A.8.8 | | diagnostic-settings | Diagnostic Settings Coverage | SOC2 CC7.1, ISO A.8.15 | | required-tags | Required Tags Compliance | SOC2 CC8.1, ISO A.5.9 |


Part A — Offline: query evaluator unit tests

cd apps/evidence-collector
npm ci
npm test -- --testPathPattern="resource-graph/(queries|mapper)"

Expected: All tests pass. Tests cover: - 7 query evaluators with pass and fail scenarios - Mapper: item count per (result × control mapping) - Mapper: observedAt + subscriptionId stamping - Mapper: unknown queryId skipped


Part B — Offline: TypeScript type-check

cd apps/evidence-collector
npm run typecheck

Expected: No type errors.


Part C — Live: Resource Graph execution (requires Azure subscription)

Prerequisites: - az login with Reader on the sandbox subscription - AZURE_SUBSCRIPTION_ID set

cd apps/evidence-collector
npm run build
AZURE_SUBSCRIPTION_ID=<your-sub-id> \
node -e "
import('./dist/resource-graph/index.js').then(async ({ RESOURCE_GRAPH_QUERIES, runAllQueries, mapQueryResultsToEvidence }) => {
  const { FetchAzureRestClient } = await import('./dist/azure-rest.js');
  const { execFileSync } = await import('node:child_process');
  const token = execFileSync('az', ['account','get-access-token','--resource','https://management.azure.com','--query','accessToken','-o','tsv'], { encoding: 'utf8' }).trim();
  const client = new FetchAzureRestClient({ baseUrl: 'https://management.azure.com', accessToken: token });
  const sub = process.env.AZURE_SUBSCRIPTION_ID;
  const results = await runAllQueries(client, RESOURCE_GRAPH_QUERIES, sub);
  const items = mapQueryResultsToEvidence(RESOURCE_GRAPH_QUERIES, results, new Date().toISOString(), sub);
  console.log(JSON.stringify(items.map(i => ({ framework: i.framework, controlId: i.controlId, status: i.status, evidence: i.evidence })), null, 2));
});
"

Expected: 14 EvidenceItems (7 queries × 2 controls each, approximately). Each item shows a status of pass or fail and an evidenceSummary with counts from your sandbox subscription.


Sign-Off Block

Step Executed by Date Result
Part A (unit tests)
Part B (typecheck)
Part C (live Resource Graph)

Signed off: _____ Date: ___


Failure Modes

Failure Cause Remediation
Azure REST POST failed: 403 Insufficient permissions Assign Reader on the subscription
Azure REST POST failed: 400 Invalid KQL syntax Check the KQL query against the Azure portal Resource Graph Explorer
Results truncated Query returns > 1000 rows Add | take 1000 or refine the filter