admin / Synapse-Sonar
publicAttack Surface Simulation
Synapse-Sonar / synapse-sonar / lib / governance.ts
3198 B · main
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | import crypto from "crypto"; import { getEnabledIntegration, IronNodeConfig } from "@/lib/integrations"; import { prisma } from "@/lib/prisma"; import { IntegrationProvider, RuleAction } from "@prisma/client"; /** Minimal CIDR membership test (IPv4) for the MVP rule engine. */ function ipInCidr(ip: string, cidr: string): boolean { if (cidr === "0.0.0.0/0") return true; const [range, bitsStr] = cidr.split("/"); const bits = parseInt(bitsStr ?? "32", 10); const toInt = (addr: string) => addr.split(".").reduce((acc, oct) => (acc << 8) + (parseInt(oct, 10) & 255), 0) >>> 0; const mask = bits === 0 ? 0 : (0xffffffff << (32 - bits)) >>> 0; return (toInt(ip) & mask) === (toInt(range) & mask); } export interface FlowDescriptor { sourceIp: string; destIp: string; port?: number | null; } /** * Zero-Trust evaluation. Walk the tenant baseline by priority; the first rule * whose CIDRs + port match decides. A matched DENY — or no matching ALLOW — * is a segmentation violation. */ export async function evaluateSegmentation( tenantId: string, flow: FlowDescriptor ): Promise<{ violation: boolean; ruleId?: string; reason: string }> { const rules = await prisma.segmentationRule.findMany({ where: { tenantId }, orderBy: { priority: "asc" }, }); for (const rule of rules) { const portMatches = rule.port == null || rule.port === flow.port; if ( portMatches && ipInCidr(flow.sourceIp, rule.sourceCidr) && ipInCidr(flow.destIp, rule.destCidr) ) { if (rule.action === RuleAction.DENY) { return { violation: true, ruleId: rule.id, reason: "Matched DENY rule" }; } return { violation: false, ruleId: rule.id, reason: "Matched ALLOW rule" }; } } // Default-deny posture: if no baseline rule explicitly allows the flow, it drifts. return { violation: rules.length > 0, reason: rules.length > 0 ? "No matching ALLOW rule (default-deny)" : "No baseline configured — flow permitted", }; } /** * SOAR hand-off. If the Synapse IronNode integration is enabled, fire a signed * JSON payload to the configured webhook describing the violation. No-ops * silently when the integration toggle is Off. */ export async function dispatchIronNodeAlert( tenantId: string, payload: Record<string, unknown> ): Promise<{ dispatched: boolean }> { const cfg = await getEnabledIntegration<IronNodeConfig>( tenantId, IntegrationProvider.SYNAPSE_IRONNODE ); if (!cfg) return { dispatched: false }; const body = JSON.stringify({ source: "synapse-sonar", emittedAt: new Date().toISOString(), ...payload, }); // HMAC-SHA256 signature so IronNode can verify authenticity. const signature = crypto .createHmac("sha256", cfg.signingKey) .update(body) .digest("hex"); try { await fetch(cfg.webhookUrl, { method: "POST", headers: { "Content-Type": "application/json", "X-Sonar-Signature": `sha256=${signature}`, }, body, }); return { dispatched: true }; } catch { // Alert dispatch is best-effort; never let it break ingestion. return { dispatched: false }; } } |