From e376262aba5a25aa7001e447020f5c2b0a46cd86 Mon Sep 17 00:00:00 2001 From: moloch-- <875022+moloch--@users.noreply.github.com> Date: Sun, 1 Mar 2026 19:16:57 -0800 Subject: [PATCH] Fix tests --- e2e/beacon.ts | 150 ++++++++++++++++++++++++++++++++++++++------------ e2e/run.mjs | 9 ++- 2 files changed, 122 insertions(+), 37 deletions(-) diff --git a/e2e/beacon.ts b/e2e/beacon.ts index 68770d7..ae1a71e 100644 --- a/e2e/beacon.ts +++ b/e2e/beacon.ts @@ -59,8 +59,7 @@ function loadLocalSliverScript(repoRoot: string): SliverScriptModule { const REPO_ROOT = guessRepoRoot(); const CONFIG_PATH = process.env.SLIVER_CONFIG_FILE ?? path.join(REPO_ROOT, "localhost.cfg"); const DEFAULT_HTTP_C2_PROFILE = "default"; -const BEACON_INTERVAL_SECONDS = 20; -const BEACON_INTERVAL_NS = String(BEACON_INTERVAL_SECONDS * 1_000_000_000); +const DEFAULT_BEACON_INTERVAL_SECONDS = 5; const BEACON_TIMEOUT_SECONDS = 300; const BEACON_TASK_POLL_RPC_TIMEOUT_SECONDS = 20; const BEACON_TASK_CONTENT_RPC_TIMEOUT_SECONDS = 45; @@ -79,6 +78,19 @@ function parsePort(rawValue: string, name: string): number { return parsed; } +function parsePositiveInt(rawValue: string, name: string, max: number): number { + const parsed = Number.parseInt(rawValue, 10); + assert(Number.isInteger(parsed) && parsed > 0 && parsed <= max, `Invalid ${name}: ${rawValue}`); + return parsed; +} + +const BEACON_INTERVAL_SECONDS = parsePositiveInt( + process.env.SLIVER_E2E_BEACON_INTERVAL_SECONDS ?? String(DEFAULT_BEACON_INTERVAL_SECONDS), + "SLIVER_E2E_BEACON_INTERVAL_SECONDS", + 300, +); +const BEACON_INTERVAL_NS = String(BEACON_INTERVAL_SECONDS * 1_000_000_000); + function nodePlatformToGoOS(platform: NodeJS.Platform): string { switch (platform) { case "darwin": @@ -202,17 +214,59 @@ async function decodeBeaconTaskResponse { const taskID = getQueuedTaskID(queued, label); - await waitForBeaconTaskComplete(client, beaconID, taskID, timeoutSeconds); - const content = await withRpcTimeout( - BEACON_TASK_CONTENT_RPC_TIMEOUT_SECONDS, - `getBeaconTaskContent(${taskID})`, - (signal) => client.rpc.getBeaconTaskContent({ ID: taskID }, { signal }), - ); - assert(content.Response.length > 0, `${label} task returned no response bytes`); - const decoded = decoder(content.Response); - const err = decoded.Response?.Err?.trim(); - assert(!err, `${label} execution error: ${err}`); - return decoded; + const deadline = Date.now() + timeoutSeconds * 1000; + let lastObservedState: string | undefined; + let lastContentError: unknown; + + while (Date.now() < deadline) { + try { + const content = await withRpcTimeout( + BEACON_TASK_CONTENT_RPC_TIMEOUT_SECONDS, + `getBeaconTaskContent(${taskID})`, + (signal) => client.rpc.getBeaconTaskContent({ ID: taskID }, { signal }), + ); + if (content.Response.length > 0) { + const decoded = decoder(content.Response); + const err = decoded.Response?.Err?.trim(); + assert(!err, `${label} execution error: ${err}`); + return decoded; + } + } catch (err) { + lastContentError = err; + } + + try { + const tasks = await withRpcTimeout( + BEACON_TASK_POLL_RPC_TIMEOUT_SECONDS, + `getBeaconTasks(${beaconID})`, + (signal) => client.rpc.getBeaconTasks({ ID: beaconID }, { signal }), + ); + const task = tasks.Tasks.find((candidate) => candidate.ID === taskID); + if (task) { + const state = task.State.toLowerCase(); + lastObservedState = state; + if (state === "failed" || state === "canceled" || state === "cancelled") { + throw new Error(`Beacon task ${taskID} ${state}: ${task.Description}`); + } + } + } catch (err) { + if (err instanceof Error && err.message.includes(`Beacon task ${taskID}`)) { + throw err; + } + } + + await sleep(1000); + } + + const debugParts = []; + if (lastObservedState) { + debugParts.push(`lastState=${lastObservedState}`); + } + if (lastContentError instanceof Error && lastContentError.message.trim().length > 0) { + debugParts.push(`lastContentError=${lastContentError.message}`); + } + const debugSuffix = debugParts.length > 0 ? ` (${debugParts.join(", ")})` : ""; + throw new Error(`Timed out waiting for beacon task response (${timeoutSeconds}s): ${taskID}${debugSuffix}`); } async function decodeDownloadData(download: { Encoder: string; Data: Buffer }): Promise { @@ -511,7 +565,28 @@ async function runBeaconTransportChecks( ); assert(pwd.Path.trim().length > 0, "beacon pwd returned an empty path"); - const ls = await interactiveBeacon.ls(".", 180); + const lsQueued = await withRpcTimeout(BEACON_DOWNLOAD_RPC_TIMEOUT_SECONDS, `beacon ls queue (${transport.name})`, (signal) => + client.rpc.ls( + { + Path: ".", + Request: { + Async: true, + Timeout: "180", + BeaconID: beacon.ID, + SessionID: "", + }, + }, + { signal }, + ), + ); + const ls = await decodeBeaconTaskResponse( + client, + beacon.ID, + lsQueued, + sliver.sliverpb.Ls.decode, + `beacon ls (${transport.name})`, + 180, + ); assert(ls.Exists, `beacon ls reported missing path: ${ls.Path}`); assert(ls.Files.length > 0, `beacon ls returned no files for path: ${ls.Path}`); @@ -539,29 +614,33 @@ async function runBeaconTransportChecks( ); assert(ifconfig.NetInterfaces.length > 0, "beacon ifconfig returned no interfaces"); - const lsFromPwd = await interactiveBeacon.ls(pwd.Path, 180); + const lsFromPwdQueued = await withRpcTimeout( + BEACON_DOWNLOAD_RPC_TIMEOUT_SECONDS, + `beacon ls queue (pwd path, ${transport.name})`, + (signal) => + client.rpc.ls( + { + Path: pwd.Path, + Request: { + Async: true, + Timeout: "180", + BeaconID: beacon.ID, + SessionID: "", + }, + }, + { signal }, + ), + ); + const lsFromPwd = await decodeBeaconTaskResponse( + client, + beacon.ID, + lsFromPwdQueued, + sliver.sliverpb.Ls.decode, + `beacon ls pwd path (${transport.name})`, + 180, + ); assert(lsFromPwd.Exists, `beacon ls on pwd path reported missing path: ${lsFromPwd.Path}`); - let netstatEntries: number | undefined; - try { - const netstatQueued = await interactiveBeacon.netstat(30); - const netstat = await decodeBeaconTaskResponse( - client, - beacon.ID, - netstatQueued, - sliver.sliverpb.Netstat.decode, - `beacon netstat (${transport.name})`, - 60, - ); - assert(Array.isArray(netstat.Entries), "beacon netstat entries were not returned as an array"); - if (netstat.Entries.length > 0) { - assert(netstat.Entries[0].Protocol.trim().length > 0, "beacon netstat entry missing protocol"); - } - netstatEntries = netstat.Entries.length; - } catch (err) { - console.warn("beacon netstat check skipped", err); - } - console.log("beacon command checks", { transport: transport.name, pwd: pwd.Path, @@ -575,7 +654,6 @@ async function runBeaconTransportChecks( executable: selfProcess.Executable, }, ifaces: ifconfig.NetInterfaces.map((iface) => iface.Name), - netstatEntries, }); const transferSourceDir = path.join(tempDir, "transfer-source"); diff --git a/e2e/run.mjs b/e2e/run.mjs index 575a2ef..dd83415 100644 --- a/e2e/run.mjs +++ b/e2e/run.mjs @@ -351,7 +351,7 @@ async function main() { ].map((dirPath) => mkdir(dirPath, { recursive: true })), ); - const daemonHost = process.env.SLIVER_E2E_DAEMON_HOST || "127.0.0.1"; + const daemonHost = process.env.SLIVER_E2E_DAEMON_HOST || "localhost"; const requestedOperatorHost = process.env.SLIVER_E2E_LHOST || "localhost"; const operatorHost = isIpLiteral(requestedOperatorHost) ? "localhost" : requestedOperatorHost; if (requestedOperatorHost !== operatorHost) { @@ -402,6 +402,11 @@ async function main() { const wgPort = await resolvePort("SLIVER_E2E_WG_PORT", process.env.SLIVER_E2E_WG_PORT, wgBindHost); const wgNPort = await resolvePort("SLIVER_E2E_WG_NPORT", process.env.SLIVER_E2E_WG_NPORT, wgBindHost); const wgKeyPort = await resolvePort("SLIVER_E2E_WG_KEYPORT", process.env.SLIVER_E2E_WG_KEYPORT, wgBindHost); + const beaconIntervalSecondsRaw = process.env.SLIVER_E2E_BEACON_INTERVAL_SECONDS || "5"; + const beaconIntervalSeconds = Number.parseInt(beaconIntervalSecondsRaw, 10); + if (!Number.isInteger(beaconIntervalSeconds) || beaconIntervalSeconds <= 0 || beaconIntervalSeconds > 300) { + throw new Error(`Invalid SLIVER_E2E_BEACON_INTERVAL_SECONDS value: ${beaconIntervalSecondsRaw}`); + } const operatorName = process.env.SLIVER_E2E_OPERATOR || "e2e"; @@ -437,6 +442,7 @@ async function main() { log(`Using implant HTTP endpoint: ${httpHost}:${httpPort}`); log(`Using WireGuard listener bind: ${wgBindHost}:${wgPort}`); log(`Using implant WireGuard endpoint: ${wgHost}:${wgPort} (nport=${wgNPort}, keyport=${wgKeyPort})`); + log(`Using beacon interval: ${beaconIntervalSeconds}s`); await ensureSliverServerBuilt(sharedEnv); @@ -519,6 +525,7 @@ async function main() { SLIVER_E2E_WG_PORT: String(wgPort), SLIVER_E2E_WG_NPORT: String(wgNPort), SLIVER_E2E_WG_KEYPORT: String(wgKeyPort), + SLIVER_E2E_BEACON_INTERVAL_SECONDS: String(beaconIntervalSeconds), }; if (process.env.SLIVER_WAIT_TASKS) { testEnv.SLIVER_WAIT_TASKS = process.env.SLIVER_WAIT_TASKS;