k8s-av 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/docker.d.ts +0 -16
- package/dist/cli/docker.js +3 -42
- package/dist/cli/index.d.ts +0 -14
- package/dist/cli/index.js +0 -36
- package/dist/cli/scan.d.ts +0 -8
- package/dist/cli/scan.js +0 -17
- package/dist/cli/start.d.ts +0 -14
- package/dist/cli/start.js +2 -40
- package/dist/core/attack-path.d.ts +0 -11
- package/dist/core/attack-path.js +0 -38
- package/dist/core/cve-enricher.d.ts +0 -7
- package/dist/core/cve-enricher.js +1 -38
- package/dist/core/fetcher.d.ts +0 -8
- package/dist/core/fetcher.js +2 -29
- package/dist/core/schema.d.ts +0 -30
- package/dist/core/schema.js +0 -35
- package/dist/core/transformer.d.ts +0 -15
- package/dist/core/transformer.js +0 -85
- package/dist/db/loader.d.ts +0 -12
- package/dist/db/loader.js +0 -75
- package/dist/db/neo4j-client.d.ts +0 -27
- package/dist/db/neo4j-client.js +1 -58
- package/dist/db/queries.d.ts +0 -47
- package/dist/db/queries.js +0 -63
- package/dist/db/test.d.ts +0 -17
- package/dist/db/test.js +1 -41
- package/dist/db/types.d.ts +0 -24
- package/dist/db/types.js +0 -14
- package/dist/schemas/index.js +0 -10
- package/dist/server/routes/blast.js +0 -10
- package/dist/server/routes/critical.js +0 -21
- package/dist/server/routes/cycles.js +0 -10
- package/dist/server/routes/graph.js +0 -6
- package/dist/server/routes/ingest.js +0 -14
- package/dist/server/routes/paths.js +0 -15
- package/dist/server/routes/report.js +0 -15
- package/dist/server/routes/simulate.js +0 -15
- package/dist/server/routes/vulnerabilities.js +0 -18
- package/dist/server/server.d.ts +0 -13
- package/dist/server/server.js +0 -36
- package/dist/services/ingestion.service.d.ts +0 -11
- package/dist/services/ingestion.service.js +0 -21
- package/dist/services/report/formatter.d.ts +0 -9
- package/dist/services/report/formatter.js +0 -25
- package/dist/services/report/generator.d.ts +0 -11
- package/dist/services/report/generator.js +0 -23
- package/dist/services/risk-explainer.d.ts +0 -29
- package/dist/services/risk-explainer.js +0 -53
- package/package.json +49 -46
- package/readme.md +284 -0
- package/ui/src/components/Sidebar.tsx +8 -8
- package/ui/src/lib/api.ts +0 -6
- package/ui/src/store/useAppStore.ts +0 -7
- package/ui/src/views/CriticalNodeView.tsx +29 -23
- package/ui/src/views/PathsView.tsx +29 -18
- package/ui/src/views/VulnerabilitiesView.tsx +12 -12
package/dist/cli/docker.d.ts
CHANGED
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* docker.ts — Docker detection and Neo4j container lifecycle helpers.
|
|
3
|
-
*
|
|
4
|
-
* Used by the `start` and `ingest` CLI commands before they touch Neo4j.
|
|
5
|
-
* The `scan` command is Docker-free and never calls this module.
|
|
6
|
-
*/
|
|
7
1
|
declare const NEO4J_BOLT_PORT = 7687;
|
|
8
2
|
declare const NEO4J_HTTP_PORT = 7474;
|
|
9
3
|
export declare function checkDockerInstalled(): Promise<boolean>;
|
|
@@ -14,16 +8,6 @@ export declare function waitForNeo4j(timeoutMs?: number, pollMs?: number): Promi
|
|
|
14
8
|
export interface PreflightResult {
|
|
15
9
|
ok: boolean;
|
|
16
10
|
}
|
|
17
|
-
/**
|
|
18
|
-
* Run the full Docker preflight:
|
|
19
|
-
* 1. Docker installed?
|
|
20
|
-
* 2. Docker daemon running?
|
|
21
|
-
* 3. Neo4j container up? (start it if not)
|
|
22
|
-
* 4. Neo4j accepting connections?
|
|
23
|
-
*
|
|
24
|
-
* Prints clear, actionable messages for every failure case.
|
|
25
|
-
* Never throws — returns { ok: false } so the caller can exit cleanly.
|
|
26
|
-
*/
|
|
27
11
|
export declare function runPreflight(): Promise<PreflightResult>;
|
|
28
12
|
export { NEO4J_BOLT_PORT, NEO4J_HTTP_PORT };
|
|
29
13
|
//# sourceMappingURL=docker.d.ts.map
|
package/dist/cli/docker.js
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* docker.ts — Docker detection and Neo4j container lifecycle helpers.
|
|
4
|
-
*
|
|
5
|
-
* Used by the `start` and `ingest` CLI commands before they touch Neo4j.
|
|
6
|
-
* The `scan` command is Docker-free and never calls this module.
|
|
7
|
-
*/
|
|
8
2
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
3
|
if (k2 === undefined) k2 = k;
|
|
10
4
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -50,22 +44,17 @@ const child_process_1 = require("child_process");
|
|
|
50
44
|
const path = __importStar(require("path"));
|
|
51
45
|
const util = __importStar(require("util"));
|
|
52
46
|
const exec = util.promisify(child_process_1.exec);
|
|
53
|
-
// ─── Constants ───────────────────────────────────────────────────────────────
|
|
54
47
|
const CONTAINER_NAME = 'k8s-attack-neo4j';
|
|
55
48
|
const COMPOSE_DIR = path.resolve(__dirname, '..', '..', 'docker');
|
|
56
49
|
const NEO4J_BOLT_PORT = 7687;
|
|
57
50
|
exports.NEO4J_BOLT_PORT = NEO4J_BOLT_PORT;
|
|
58
51
|
const NEO4J_HTTP_PORT = 7474;
|
|
59
52
|
exports.NEO4J_HTTP_PORT = NEO4J_HTTP_PORT;
|
|
60
|
-
// ─── Logging helpers ─────────────────────────────────────────────────────────
|
|
61
53
|
const ok = (msg) => console.log(` ✔ ${msg}`);
|
|
62
54
|
const warn = (msg) => console.log(` ⚠ ${msg}`);
|
|
63
55
|
const fail = (msg) => console.error(` ✖ ${msg}`);
|
|
64
56
|
const info = (msg) => console.log(` ${msg}`);
|
|
65
57
|
const line = () => console.log(' ' + '─'.repeat(58));
|
|
66
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
67
|
-
// PART 1 — Is Docker installed?
|
|
68
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
69
58
|
async function checkDockerInstalled() {
|
|
70
59
|
try {
|
|
71
60
|
const { stdout } = await exec('docker --version');
|
|
@@ -77,12 +66,9 @@ async function checkDockerInstalled() {
|
|
|
77
66
|
return false;
|
|
78
67
|
}
|
|
79
68
|
}
|
|
80
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
81
|
-
// PART 2 — Is the Docker daemon running?
|
|
82
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
83
69
|
async function checkDockerRunning() {
|
|
84
70
|
try {
|
|
85
|
-
await exec('docker ps');
|
|
71
|
+
await exec('docker ps', { timeout: 8000 });
|
|
86
72
|
ok('Docker daemon is running');
|
|
87
73
|
return true;
|
|
88
74
|
}
|
|
@@ -90,9 +76,6 @@ async function checkDockerRunning() {
|
|
|
90
76
|
return false;
|
|
91
77
|
}
|
|
92
78
|
}
|
|
93
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
94
|
-
// PART 3 — Is the Neo4j container already up?
|
|
95
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
96
79
|
async function isNeo4jContainerRunning() {
|
|
97
80
|
try {
|
|
98
81
|
const { stdout } = await exec(`docker inspect --format "{{.State.Running}}" ${CONTAINER_NAME}`);
|
|
@@ -102,9 +85,6 @@ async function isNeo4jContainerRunning() {
|
|
|
102
85
|
return false;
|
|
103
86
|
}
|
|
104
87
|
}
|
|
105
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
106
|
-
// PART 4 — Start Neo4j via docker-compose
|
|
107
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
108
88
|
async function startNeo4j() {
|
|
109
89
|
return new Promise((resolve, reject) => {
|
|
110
90
|
const child = (0, child_process_1.spawn)('docker', ['compose', 'up', '-d', '--remove-orphans'], { cwd: COMPOSE_DIR, stdio: 'pipe', shell: process.platform === 'win32' });
|
|
@@ -121,18 +101,14 @@ async function startNeo4j() {
|
|
|
121
101
|
child.once('error', reject);
|
|
122
102
|
});
|
|
123
103
|
}
|
|
124
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
125
|
-
// PART 5 — Poll until Neo4j Bolt port is accepting connections
|
|
126
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
127
104
|
async function waitForNeo4j(timeoutMs = 120000, pollMs = 3000) {
|
|
128
105
|
const deadline = Date.now() + timeoutMs;
|
|
129
106
|
let attempt = 0;
|
|
130
107
|
while (Date.now() < deadline) {
|
|
131
108
|
attempt++;
|
|
132
109
|
try {
|
|
133
|
-
// Use `docker exec` to run a lightweight cypher-shell ping
|
|
134
110
|
await exec(`docker exec ${CONTAINER_NAME} cypher-shell -u neo4j -p password "RETURN 1" --format plain`);
|
|
135
|
-
return;
|
|
111
|
+
return;
|
|
136
112
|
}
|
|
137
113
|
catch {
|
|
138
114
|
const remaining = Math.ceil((deadline - Date.now()) / 1000);
|
|
@@ -144,21 +120,10 @@ async function waitForNeo4j(timeoutMs = 120000, pollMs = 3000) {
|
|
|
144
120
|
throw new Error(`Neo4j did not become ready within ${timeoutMs / 1000}s.\n` +
|
|
145
121
|
` Check container logs: docker logs ${CONTAINER_NAME}`);
|
|
146
122
|
}
|
|
147
|
-
/**
|
|
148
|
-
* Run the full Docker preflight:
|
|
149
|
-
* 1. Docker installed?
|
|
150
|
-
* 2. Docker daemon running?
|
|
151
|
-
* 3. Neo4j container up? (start it if not)
|
|
152
|
-
* 4. Neo4j accepting connections?
|
|
153
|
-
*
|
|
154
|
-
* Prints clear, actionable messages for every failure case.
|
|
155
|
-
* Never throws — returns { ok: false } so the caller can exit cleanly.
|
|
156
|
-
*/
|
|
157
123
|
async function runPreflight() {
|
|
158
124
|
console.log('\n' + '─'.repeat(62));
|
|
159
125
|
console.log(' Docker & Neo4j Preflight');
|
|
160
126
|
console.log('─'.repeat(62));
|
|
161
|
-
// ── 1. Docker installed ───────────────────────────────────────────────────
|
|
162
127
|
const installed = await checkDockerInstalled();
|
|
163
128
|
if (!installed) {
|
|
164
129
|
fail('Docker is not installed.\n');
|
|
@@ -175,7 +140,6 @@ async function runPreflight() {
|
|
|
175
140
|
line();
|
|
176
141
|
return { ok: false };
|
|
177
142
|
}
|
|
178
|
-
// ── 2. Docker daemon running ──────────────────────────────────────────────
|
|
179
143
|
const running = await checkDockerRunning();
|
|
180
144
|
if (!running) {
|
|
181
145
|
warn('Docker is installed but the daemon is not running.\n');
|
|
@@ -189,7 +153,6 @@ async function runPreflight() {
|
|
|
189
153
|
line();
|
|
190
154
|
return { ok: false };
|
|
191
155
|
}
|
|
192
|
-
// ── 3. Neo4j container ───────────────────────────────────────────────────
|
|
193
156
|
const neo4jUp = await isNeo4jContainerRunning();
|
|
194
157
|
if (neo4jUp) {
|
|
195
158
|
ok(`Neo4j container running (${CONTAINER_NAME})`);
|
|
@@ -214,11 +177,10 @@ async function runPreflight() {
|
|
|
214
177
|
return { ok: false };
|
|
215
178
|
}
|
|
216
179
|
}
|
|
217
|
-
// ── 4. Wait for Neo4j to be ready ────────────────────────────────────────
|
|
218
180
|
info(`Neo4j ports: Bolt :${NEO4J_BOLT_PORT} Browser :${NEO4J_HTTP_PORT}`);
|
|
219
181
|
try {
|
|
220
182
|
await waitForNeo4j(120000);
|
|
221
|
-
process.stdout.write('\n');
|
|
183
|
+
process.stdout.write('\n');
|
|
222
184
|
ok('Neo4j is ready');
|
|
223
185
|
}
|
|
224
186
|
catch (err) {
|
|
@@ -234,7 +196,6 @@ async function runPreflight() {
|
|
|
234
196
|
line();
|
|
235
197
|
return { ok: true };
|
|
236
198
|
}
|
|
237
|
-
// ─── Utility ─────────────────────────────────────────────────────────────────
|
|
238
199
|
function sleep(ms) {
|
|
239
200
|
return new Promise((r) => setTimeout(r, ms));
|
|
240
201
|
}
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -1,16 +1,2 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Kubernetes Attack Path Visualizer — CLI Entry Point
|
|
4
|
-
*
|
|
5
|
-
* Commands:
|
|
6
|
-
* scan — fetch + transform + enrich → write cluster-graph.json (no Neo4j)
|
|
7
|
-
* ingest — full pipeline: scan → load Neo4j → re-project GDS
|
|
8
|
-
* report — generate + print attack report from Neo4j
|
|
9
|
-
*
|
|
10
|
-
* Usage:
|
|
11
|
-
* npx ts-node src/cli/index.ts scan --mock
|
|
12
|
-
* npx ts-node src/cli/index.ts ingest --source mock
|
|
13
|
-
* npx ts-node src/cli/index.ts report --format text
|
|
14
|
-
*/
|
|
15
1
|
import 'dotenv/config';
|
|
16
2
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/cli/index.js
CHANGED
|
@@ -1,18 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
-
/**
|
|
4
|
-
* Kubernetes Attack Path Visualizer — CLI Entry Point
|
|
5
|
-
*
|
|
6
|
-
* Commands:
|
|
7
|
-
* scan — fetch + transform + enrich → write cluster-graph.json (no Neo4j)
|
|
8
|
-
* ingest — full pipeline: scan → load Neo4j → re-project GDS
|
|
9
|
-
* report — generate + print attack report from Neo4j
|
|
10
|
-
*
|
|
11
|
-
* Usage:
|
|
12
|
-
* npx ts-node src/cli/index.ts scan --mock
|
|
13
|
-
* npx ts-node src/cli/index.ts ingest --source mock
|
|
14
|
-
* npx ts-node src/cli/index.ts report --format text
|
|
15
|
-
*/
|
|
16
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
4
|
require("dotenv/config");
|
|
18
5
|
const commander_1 = require("commander");
|
|
@@ -32,9 +19,6 @@ program
|
|
|
32
19
|
'Ingests cluster data, builds an RBAC graph, enriches with CVEs,\n' +
|
|
33
20
|
'detects attack paths from entry points to crown jewels.')
|
|
34
21
|
.version('1.0.0', '-v, --version');
|
|
35
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
36
|
-
// scan — local pipeline only (no Neo4j)
|
|
37
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
38
22
|
program
|
|
39
23
|
.command('scan')
|
|
40
24
|
.description('Scan a Kubernetes cluster and output an attack-path graph (no Neo4j)')
|
|
@@ -52,10 +36,6 @@ program
|
|
|
52
36
|
process.exit(1);
|
|
53
37
|
}
|
|
54
38
|
});
|
|
55
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
56
|
-
// ingest — full pipeline: scan → Neo4j → GDS
|
|
57
|
-
// Reuses same services as POST /api/ingest
|
|
58
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
59
39
|
program
|
|
60
40
|
.command('ingest')
|
|
61
41
|
.description('Full ingestion: fetch cluster data → load Neo4j → re-project GDS')
|
|
@@ -68,22 +48,17 @@ program
|
|
|
68
48
|
console.log('\n' + '═'.repeat(60));
|
|
69
49
|
console.log(' 🔷 K8s Attack Path Visualizer — Ingest');
|
|
70
50
|
console.log('═'.repeat(60));
|
|
71
|
-
// ── Docker + Neo4j preflight ──────────────────────────────────────────
|
|
72
51
|
const preflight = await (0, docker_1.runPreflight)();
|
|
73
52
|
if (!preflight.ok)
|
|
74
53
|
process.exit(1);
|
|
75
|
-
// Verify Neo4j driver connection
|
|
76
54
|
console.log('\n Connecting to Neo4j...');
|
|
77
55
|
await (0, neo4j_client_1.verifyConnection)();
|
|
78
|
-
// ── Step 1: ingestCluster (Teammate 1) ───────────────────────────────
|
|
79
56
|
console.log('\n [1/3] Ingesting cluster data...');
|
|
80
57
|
const ingestResult = await (0, ingestion_service_1.ingestCluster)({ source, skipCve: opts.skipCve });
|
|
81
58
|
console.log(` ✔ Graph JSON written: ${ingestResult.nodes} nodes, ${ingestResult.edges} edges`);
|
|
82
|
-
// ── Step 2: loadGraph (Teammate 2) ───────────────────────────────────
|
|
83
59
|
console.log('\n [2/3] Loading graph into Neo4j...');
|
|
84
60
|
const stats = await (0, loader_1.loadGraph)(ingestResult.graphPath, opts.wipe);
|
|
85
61
|
console.log(` ✔ Neo4j: ${stats.nodesLoaded} nodes, ${stats.edgesLoaded} edges (${stats.durationMs}ms)`);
|
|
86
|
-
// ── Step 3: Re-project GDS ────────────────────────────────────────────
|
|
87
62
|
console.log('\n [3/3] Projecting GDS graph...');
|
|
88
63
|
await (0, queries_1.ensureProjection)(true);
|
|
89
64
|
console.log(' ✔ GDS projection ready');
|
|
@@ -96,10 +71,6 @@ program
|
|
|
96
71
|
process.exit(1);
|
|
97
72
|
}
|
|
98
73
|
});
|
|
99
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
100
|
-
// report — generate + print attack report
|
|
101
|
-
// Reuses same generator + formatter as GET /api/report
|
|
102
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
103
74
|
program
|
|
104
75
|
.command('report')
|
|
105
76
|
.description('Generate a full attack-path report from Neo4j data')
|
|
@@ -111,7 +82,6 @@ program
|
|
|
111
82
|
await (0, neo4j_client_1.verifyConnection)();
|
|
112
83
|
console.log(' Generating report...\n');
|
|
113
84
|
const data = await (0, generator_1.generateReport)();
|
|
114
|
-
// Same formatter used by the API — no duplication
|
|
115
85
|
const output = (0, formatter_1.formatReport)(data, format);
|
|
116
86
|
console.log(output);
|
|
117
87
|
process.exit(0);
|
|
@@ -121,9 +91,6 @@ program
|
|
|
121
91
|
process.exit(1);
|
|
122
92
|
}
|
|
123
93
|
});
|
|
124
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
125
|
-
// start — full system orchestrator: backend + ingest + UI + browser
|
|
126
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
127
94
|
program
|
|
128
95
|
.command('start')
|
|
129
96
|
.description('Start backend, load mock data, open the UI in your browser')
|
|
@@ -141,9 +108,6 @@ program
|
|
|
141
108
|
process.exit(1);
|
|
142
109
|
}
|
|
143
110
|
});
|
|
144
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
145
|
-
// Fallback
|
|
146
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
147
111
|
program.on('command:*', () => {
|
|
148
112
|
console.error(`Unknown command: ${program.args.join(' ')}`);
|
|
149
113
|
program.help();
|
package/dist/cli/scan.d.ts
CHANGED
|
@@ -1,16 +1,8 @@
|
|
|
1
1
|
export interface ScanOptions {
|
|
2
|
-
/** Load data from mock JSON instead of running kubectl */
|
|
3
2
|
mock: boolean;
|
|
4
|
-
/** Destination file path for the cluster-graph.json output */
|
|
5
3
|
output: string;
|
|
6
|
-
/** Skip CVE enrichment (faster runs, no network required) */
|
|
7
4
|
skipCve?: boolean;
|
|
8
|
-
/** Print verbose attack-path report including alternate routes */
|
|
9
5
|
verbose?: boolean;
|
|
10
6
|
}
|
|
11
|
-
/**
|
|
12
|
-
* Orchestrates the full ingestion → transformation → enrichment →
|
|
13
|
-
* validation → output pipeline.
|
|
14
|
-
*/
|
|
15
7
|
export declare function runScan(options: ScanOptions): Promise<void>;
|
|
16
8
|
//# sourceMappingURL=scan.d.ts.map
|
package/dist/cli/scan.js
CHANGED
|
@@ -41,28 +41,17 @@ const transformer_1 = require("../core/transformer");
|
|
|
41
41
|
const cve_enricher_1 = require("../core/cve-enricher");
|
|
42
42
|
const schema_1 = require("../core/schema");
|
|
43
43
|
const attack_path_1 = require("../core/attack-path");
|
|
44
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
45
|
-
// LOGGING HELPERS
|
|
46
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
47
44
|
function step(label) {
|
|
48
45
|
console.log(`\n✔ ${label}`);
|
|
49
46
|
}
|
|
50
47
|
function divider() {
|
|
51
48
|
console.log(' ' + '─'.repeat(60));
|
|
52
49
|
}
|
|
53
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
54
|
-
// MAIN SCAN PIPELINE
|
|
55
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
56
|
-
/**
|
|
57
|
-
* Orchestrates the full ingestion → transformation → enrichment →
|
|
58
|
-
* validation → output pipeline.
|
|
59
|
-
*/
|
|
60
50
|
async function runScan(options) {
|
|
61
51
|
console.log('\n' + '═'.repeat(62));
|
|
62
52
|
console.log(' 🔐 Kubernetes Attack Path Visualizer');
|
|
63
53
|
console.log(' RBAC Graph Ingestion & CVE Enrichment Pipeline');
|
|
64
54
|
console.log('═'.repeat(62));
|
|
65
|
-
// ── Step 1: Fetch ──────────────────────────────────────────────────────────
|
|
66
55
|
step('Fetching cluster data...');
|
|
67
56
|
const rawData = await (0, fetcher_1.fetchClusterData)(options.mock);
|
|
68
57
|
const podCount = (rawData.pods?.items ?? []).length;
|
|
@@ -73,14 +62,12 @@ async function runScan(options) {
|
|
|
73
62
|
(rawData.clusterRoleBindings?.items ?? []).length;
|
|
74
63
|
console.log(` → Pods: ${podCount} | ServiceAccounts: ${saCount} | ` +
|
|
75
64
|
`Roles: ${roleCount} | Bindings: ${bindingCount}`);
|
|
76
|
-
// ── Step 2: Transform ─────────────────────────────────────────────────────
|
|
77
65
|
step('Transforming RBAC graph...');
|
|
78
66
|
let graph = (0, transformer_1.transformToGraph)(rawData);
|
|
79
67
|
console.log(` → Built ${graph.nodes.length} nodes and ${graph.edges.length} edges`);
|
|
80
68
|
const entryPts = graph.nodes.filter((n) => n.isEntryPoint).length;
|
|
81
69
|
const crownJs = graph.nodes.filter((n) => n.isCrownJewel).length;
|
|
82
70
|
console.log(` → Entry points: ${entryPts} | Crown jewels: ${crownJs}`);
|
|
83
|
-
// ── Step 3: CVE Enrichment ────────────────────────────────────────────────
|
|
84
71
|
if (options.skipCve) {
|
|
85
72
|
console.log('\n✔ CVE enrichment skipped (--skip-cve)');
|
|
86
73
|
}
|
|
@@ -90,7 +77,6 @@ async function runScan(options) {
|
|
|
90
77
|
const enriched = graph.nodes.filter((n) => (n.cve?.length ?? 0) > 0).length;
|
|
91
78
|
console.log(` → ${enriched} pod(s) enriched with CVE data`);
|
|
92
79
|
}
|
|
93
|
-
// ── Step 4: Attack Path Detection ─────────────────────────────────────────
|
|
94
80
|
step('Detecting attack paths...');
|
|
95
81
|
let attackPaths;
|
|
96
82
|
if (options.verbose) {
|
|
@@ -112,7 +98,6 @@ async function runScan(options) {
|
|
|
112
98
|
}
|
|
113
99
|
}
|
|
114
100
|
graph = { ...graph, attackPaths };
|
|
115
|
-
// ── Step 5: Validation ────────────────────────────────────────────────────
|
|
116
101
|
step('Validating schema...');
|
|
117
102
|
const finalGraph = {
|
|
118
103
|
...graph,
|
|
@@ -127,7 +112,6 @@ async function runScan(options) {
|
|
|
127
112
|
},
|
|
128
113
|
};
|
|
129
114
|
const validated = (0, schema_1.validateGraph)(finalGraph);
|
|
130
|
-
// ── Step 6: Persist ───────────────────────────────────────────────────────
|
|
131
115
|
step('Saving graph...');
|
|
132
116
|
const outputPath = path.resolve(options.output);
|
|
133
117
|
const outputDir = path.dirname(outputPath);
|
|
@@ -136,7 +120,6 @@ async function runScan(options) {
|
|
|
136
120
|
}
|
|
137
121
|
fs.writeFileSync(outputPath, JSON.stringify(validated, null, 2), 'utf8');
|
|
138
122
|
console.log(` → Saved to: ${outputPath}`);
|
|
139
|
-
// ── Final Summary ─────────────────────────────────────────────────────────
|
|
140
123
|
divider();
|
|
141
124
|
console.log('\n 📊 Final Summary\n');
|
|
142
125
|
console.log(` Nodes : ${validated.nodes.length}`);
|
package/dist/cli/start.d.ts
CHANGED
|
@@ -1,17 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* start.ts — CLI orchestrator
|
|
3
|
-
*
|
|
4
|
-
* Sequence:
|
|
5
|
-
* 0. Docker + Neo4j preflight (skipped with --mock)
|
|
6
|
-
* 1. Spawn Express backend (ts-node or node dist/server/server.js)
|
|
7
|
-
* 2. Poll /health until ready (60s timeout)
|
|
8
|
-
* 3. POST /api/ingest (load cluster data)
|
|
9
|
-
* 4. Ensure UI deps installed (npm install inside ui/ if needed)
|
|
10
|
-
* 5. Spawn Vite frontend (npm run dev inside ui/)
|
|
11
|
-
* 6. Wait for Vite (poll localhost:5173, 30s timeout)
|
|
12
|
-
* 7. Open browser
|
|
13
|
-
* 8. Park — Ctrl+C kills both children cleanly
|
|
14
|
-
*/
|
|
15
1
|
export interface StartOptions {
|
|
16
2
|
skipBrowser: boolean;
|
|
17
3
|
source: 'mock' | 'live';
|
package/dist/cli/start.js
CHANGED
|
@@ -1,18 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* start.ts — CLI orchestrator
|
|
4
|
-
*
|
|
5
|
-
* Sequence:
|
|
6
|
-
* 0. Docker + Neo4j preflight (skipped with --mock)
|
|
7
|
-
* 1. Spawn Express backend (ts-node or node dist/server/server.js)
|
|
8
|
-
* 2. Poll /health until ready (60s timeout)
|
|
9
|
-
* 3. POST /api/ingest (load cluster data)
|
|
10
|
-
* 4. Ensure UI deps installed (npm install inside ui/ if needed)
|
|
11
|
-
* 5. Spawn Vite frontend (npm run dev inside ui/)
|
|
12
|
-
* 6. Wait for Vite (poll localhost:5173, 30s timeout)
|
|
13
|
-
* 7. Open browser
|
|
14
|
-
* 8. Park — Ctrl+C kills both children cleanly
|
|
15
|
-
*/
|
|
16
2
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
17
3
|
if (k2 === undefined) k2 = k;
|
|
18
4
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
@@ -55,17 +41,12 @@ const http = __importStar(require("http"));
|
|
|
55
41
|
const util = __importStar(require("util"));
|
|
56
42
|
const docker_1 = require("./docker");
|
|
57
43
|
const exec = util.promisify(child_process_1.exec);
|
|
58
|
-
// ─── Resolved paths ───────────────────────────────────────────────────────────
|
|
59
|
-
// __dirname is dist/cli/ at runtime (both ts-node and compiled).
|
|
60
|
-
// ROOT is always the package root (two levels up from dist/cli/).
|
|
61
44
|
const ROOT = path.resolve(__dirname, '..', '..');
|
|
62
45
|
const UI_DIR = path.join(ROOT, 'ui');
|
|
63
46
|
const BACKEND_URL = 'http://localhost:3001';
|
|
64
47
|
const FRONTEND_URL = 'http://localhost:5173';
|
|
65
|
-
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
66
48
|
const log = (msg) => console.log(` ${msg}`);
|
|
67
49
|
const warn = (msg) => console.log(` ⚠ ${msg}`);
|
|
68
|
-
/** Poll a URL until it responds < 400. Rejects after timeoutMs. */
|
|
69
50
|
function waitFor(url, timeoutMs = 30000) {
|
|
70
51
|
return new Promise((resolve, reject) => {
|
|
71
52
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -89,7 +70,6 @@ function waitFor(url, timeoutMs = 30000) {
|
|
|
89
70
|
attempt();
|
|
90
71
|
});
|
|
91
72
|
}
|
|
92
|
-
/** POST /api/ingest — load cluster data into Neo4j */
|
|
93
73
|
async function ingest(source) {
|
|
94
74
|
const res = await fetch(`${BACKEND_URL}/api/ingest`, {
|
|
95
75
|
method: 'POST',
|
|
@@ -101,18 +81,12 @@ async function ingest(source) {
|
|
|
101
81
|
throw new Error(`HTTP ${res.status}: ${text.slice(0, 200)}`);
|
|
102
82
|
}
|
|
103
83
|
}
|
|
104
|
-
/** Open the system default browser cross-platform */
|
|
105
84
|
function openBrowser(url) {
|
|
106
85
|
const [cmd, args] = process.platform === 'win32' ? ['cmd', ['/c', `start ${url}`]] :
|
|
107
86
|
process.platform === 'darwin' ? ['open', [url]] :
|
|
108
87
|
['xdg-open', [url]];
|
|
109
88
|
(0, child_process_1.spawn)(cmd, args, { shell: false, detached: true, stdio: 'ignore' }).unref();
|
|
110
89
|
}
|
|
111
|
-
/**
|
|
112
|
-
* Ensure the ui/ directory has its node_modules installed.
|
|
113
|
-
* Runs `npm install --omit=dev` inside ui/ if node_modules is absent.
|
|
114
|
-
* This handles the `npx k8s-av` case where ui/node_modules is not present.
|
|
115
|
-
*/
|
|
116
90
|
async function ensureUiDeps() {
|
|
117
91
|
const nmDir = path.join(UI_DIR, 'node_modules');
|
|
118
92
|
if (fs.existsSync(nmDir))
|
|
@@ -133,7 +107,6 @@ async function runStart(opts) {
|
|
|
133
107
|
console.log(' 🔐 Kubernetes Attack Path Visualizer');
|
|
134
108
|
console.log(' ' + (opts.source === 'mock' ? 'Demo mode (mock data)' : 'Live cluster mode'));
|
|
135
109
|
console.log('═'.repeat(62));
|
|
136
|
-
// ── 0. Docker preflight (skipped in mock mode) ────────────────────────────
|
|
137
110
|
if (opts.source === 'mock') {
|
|
138
111
|
console.log('\n ℹ Mock mode — skipping Docker & Neo4j preflight.');
|
|
139
112
|
}
|
|
@@ -143,9 +116,7 @@ async function runStart(opts) {
|
|
|
143
116
|
process.exit(1);
|
|
144
117
|
}
|
|
145
118
|
console.log();
|
|
146
|
-
// ── 1. Spawn backend ───────────────────────────────────────────────────────
|
|
147
119
|
log('✔ Starting backend...');
|
|
148
|
-
// Use compiled JS if available (npm package context), fall back to ts-node
|
|
149
120
|
const distServer = path.join(ROOT, 'dist', 'server', 'server.js');
|
|
150
121
|
const [backendCmd, backendArgs] = fs.existsSync(distServer)
|
|
151
122
|
? ['node', [distServer]]
|
|
@@ -153,7 +124,7 @@ async function runStart(opts) {
|
|
|
153
124
|
const backend = (0, child_process_1.spawn)(backendCmd, backendArgs, {
|
|
154
125
|
cwd: ROOT,
|
|
155
126
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
156
|
-
shell:
|
|
127
|
+
shell: false,
|
|
157
128
|
env: { ...process.env, CORS_ORIGIN: FRONTEND_URL },
|
|
158
129
|
});
|
|
159
130
|
let backendExited = false;
|
|
@@ -174,7 +145,6 @@ async function runStart(opts) {
|
|
|
174
145
|
process.exit(1);
|
|
175
146
|
}
|
|
176
147
|
});
|
|
177
|
-
// ── 2. Wait for backend ────────────────────────────────────────────────────
|
|
178
148
|
log('⏳ Waiting for backend...');
|
|
179
149
|
try {
|
|
180
150
|
await waitFor(`${BACKEND_URL}/health`, 60000);
|
|
@@ -189,7 +159,6 @@ async function runStart(opts) {
|
|
|
189
159
|
process.exit(1);
|
|
190
160
|
}
|
|
191
161
|
log('✔ Backend ready');
|
|
192
|
-
// ── 3. Ingest data ────────────────────────────────────────────────────────
|
|
193
162
|
log(`✔ Loading cluster data (source: ${opts.source})...`);
|
|
194
163
|
try {
|
|
195
164
|
await ingest(opts.source);
|
|
@@ -199,7 +168,6 @@ async function runStart(opts) {
|
|
|
199
168
|
warn(`Ingest warning: ${err.message}`);
|
|
200
169
|
warn(' UI will start in empty state — check Neo4j connection.');
|
|
201
170
|
}
|
|
202
|
-
// ── 4. Ensure UI dependencies ─────────────────────────────────────────────
|
|
203
171
|
try {
|
|
204
172
|
await ensureUiDeps();
|
|
205
173
|
}
|
|
@@ -208,12 +176,11 @@ async function runStart(opts) {
|
|
|
208
176
|
backend.kill();
|
|
209
177
|
process.exit(1);
|
|
210
178
|
}
|
|
211
|
-
// ── 5. Spawn frontend ─────────────────────────────────────────────────────
|
|
212
179
|
log('✔ Starting UI...');
|
|
213
180
|
const frontend = (0, child_process_1.spawn)('npm', ['run', 'dev'], {
|
|
214
181
|
cwd: UI_DIR,
|
|
215
182
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
216
|
-
shell:
|
|
183
|
+
shell: true,
|
|
217
184
|
env: process.env,
|
|
218
185
|
});
|
|
219
186
|
frontend.stdout?.on('data', (d) => {
|
|
@@ -226,26 +193,22 @@ async function runStart(opts) {
|
|
|
226
193
|
if (line)
|
|
227
194
|
process.stderr.write(` [ui] ${line}\n`);
|
|
228
195
|
});
|
|
229
|
-
// ── 6. Wait for Vite ──────────────────────────────────────────────────────
|
|
230
196
|
try {
|
|
231
197
|
await waitFor(FRONTEND_URL, 45000);
|
|
232
198
|
}
|
|
233
199
|
catch {
|
|
234
200
|
warn('Vite did not respond in time — opening browser anyway.');
|
|
235
201
|
}
|
|
236
|
-
// ── 7. Open browser ───────────────────────────────────────────────────────
|
|
237
202
|
if (!opts.skipBrowser) {
|
|
238
203
|
log('✔ Opening browser...');
|
|
239
204
|
openBrowser(FRONTEND_URL);
|
|
240
205
|
}
|
|
241
|
-
// ── 8. Summary ────────────────────────────────────────────────────────────
|
|
242
206
|
console.log('\n ' + '─'.repeat(58));
|
|
243
207
|
console.log(' ✔ System ready');
|
|
244
208
|
console.log(` Backend → ${BACKEND_URL}`);
|
|
245
209
|
console.log(` UI → ${FRONTEND_URL}`);
|
|
246
210
|
console.log(' ' + '─'.repeat(58));
|
|
247
211
|
console.log('\n Press Ctrl+C to stop.\n');
|
|
248
|
-
// ── Shutdown handler ──────────────────────────────────────────────────────
|
|
249
212
|
const shutdown = () => {
|
|
250
213
|
log('Shutting down...');
|
|
251
214
|
if (!backendExited)
|
|
@@ -255,7 +218,6 @@ async function runStart(opts) {
|
|
|
255
218
|
};
|
|
256
219
|
process.on('SIGINT', shutdown);
|
|
257
220
|
process.on('SIGTERM', shutdown);
|
|
258
|
-
// Park indefinitely until Ctrl+C
|
|
259
221
|
await new Promise(() => { });
|
|
260
222
|
}
|
|
261
223
|
//# sourceMappingURL=start.js.map
|
|
@@ -9,18 +9,7 @@ export interface AttackPathReport {
|
|
|
9
9
|
avgHops: number;
|
|
10
10
|
};
|
|
11
11
|
}
|
|
12
|
-
/**
|
|
13
|
-
* Detects all attack paths in the graph that lead from an entry-point node
|
|
14
|
-
* to a crown-jewel node using BFS (shortest path) per pair.
|
|
15
|
-
*
|
|
16
|
-
* Paths are sorted by descending riskScore.
|
|
17
|
-
*/
|
|
18
12
|
export declare function detectAttackPaths(graph: Graph): AttackPath[];
|
|
19
|
-
/**
|
|
20
|
-
* Same as `detectAttackPaths` but also finds ALTERNATE (non-shortest) paths
|
|
21
|
-
* for richer analysis. Returns a full report with statistics.
|
|
22
|
-
*/
|
|
23
13
|
export declare function generateFullAttackReport(graph: Graph): AttackPathReport;
|
|
24
|
-
/** Pretty-prints the top N attack paths to the console. */
|
|
25
14
|
export declare function printAttackPaths(paths: AttackPath[], limit?: number): void;
|
|
26
15
|
//# sourceMappingURL=attack-path.d.ts.map
|