feat: Protocol v1.1.0 theta-agent C2 integration, agent install wizard, OpenBao 403 fix, nmap discovery fix, and restored documentation

This commit is contained in:
2026-08-03 02:18:09 -04:00
parent 4945dec9c2
commit b7aac2d2ba
35 changed files with 3003 additions and 49 deletions
+100
View File
@@ -0,0 +1,100 @@
'use strict';
const crypto = require('crypto');
const agentManager = require('../utils/agent_manager');
describe('AgentManager PROTOCOL.md v1.1.0 Compliance', () => {
let mockWs;
let sentMessages;
beforeEach(() => {
sentMessages = [];
mockWs = {
readyState: 1, // OPEN
send: jest.fn((msg) => sentMessages.push(JSON.parse(msg))),
close: jest.fn()
};
});
test('registers agent and tracks initial connection state', () => {
const record = agentManager.registerAgent('test-token-123', mockWs, '192.168.1.100');
expect(record.token).toBe('test-token-123');
expect(record.ipAddress).toBe('192.168.1.100');
const agents = agentManager.getConnectedAgents();
const found = agents.find(a => a.token === 'test-token-123');
expect(found).toBeDefined();
expect(found.isOnline).toBe(true);
});
test('processes discovery payload per PROTOCOL.md v1.1.0 Section 3.1', () => {
agentManager.registerAgent('test-token-123', mockWs, '192.168.1.100');
const discoveryPayload = {
hostname: 'node-01.local',
ip_addresses: ['192.168.1.100', '10.0.0.5'],
os: 'Ubuntu 24.04 LTS',
kernel: '6.8.0-31-generic',
cpu: 'AMD EPYC 7763',
ram_total_gb: 32.0,
disk_total_gb: 500.0,
location: 'dc-chicago-rack-4'
};
agentManager.handleDiscovery('test-token-123', discoveryPayload);
const agents = agentManager.getConnectedAgents();
const agent = agents.find(a => a.token === 'test-token-123');
expect(agent.hostname).toBe('node-01.local');
expect(agent.discovery.os).toBe('Ubuntu 24.04 LTS');
expect(agent.discovery.ip_addresses).toEqual(['192.168.1.100', '10.0.0.5']);
});
test('processes telemetry payload per PROTOCOL.md v1.1.0 Section 3.2', () => {
agentManager.registerAgent('test-token-123', mockWs, '192.168.1.100');
const telemetryPayload = {
cpu_usage_percent: 14.5,
ram_usage_percent: 42.1,
disk_usage_percent: 68.0,
zfs_health: 'ONLINE',
gpu_usage_percent: -1.0,
timestamp: new Date().toISOString()
};
agentManager.handleTelemetry('test-token-123', telemetryPayload);
const agents = agentManager.getConnectedAgents();
const agent = agents.find(a => a.token === 'test-token-123');
expect(agent.telemetry.cpu_usage_percent).toBe(14.5);
expect(agent.telemetry.zfs_health).toBe('ONLINE');
});
test('responds to heartbeat with heartbeat_ack per Section 3.3', () => {
agentManager.registerAgent('test-token-123', mockWs, '192.168.1.100');
agentManager.handleHeartbeat('test-token-123', { timestamp: new Date().toISOString() }, mockWs);
expect(mockWs.send).toHaveBeenCalled();
const lastMsg = sentMessages[sentMessages.length - 1];
expect(lastMsg.type).toBe('heartbeat_ack');
expect(lastMsg.payload.timestamp).toBeDefined();
});
test('canonicalizes payload and signs high-risk commands using Ed25519 per Section 5', () => {
agentManager.registerAgent('test-token-123', mockWs, '192.168.1.100');
const rawPayload = { script: 'uptime', location: 'datacenter' };
const msg = agentManager.sendCommand('test-token-123', 'arbitrary_bash', rawPayload, true);
expect(msg.type).toBe('arbitrary_bash');
expect(msg.payload.signature).toBeDefined();
expect(typeof msg.payload.signature).toBe('string');
// Verify signature with public key
const signatureBuffer = Buffer.from(msg.payload.signature, 'base64');
const canonicalStr = agentManager.canonicalize(rawPayload);
const isValid = crypto.verify(null, Buffer.from(canonicalStr, 'utf8'), agentManager.publicKeyPem, signatureBuffer);
expect(isValid).toBe(true);
});
});
+46
View File
@@ -0,0 +1,46 @@
'use strict';
const nmapPlugin = require('../plugins/discovery/nmap');
jest.mock('node-nmap', () => {
const EventEmitter = require('events');
class MockNmapScan extends EventEmitter {
constructor(targetRange, customFlags) {
super();
this.targetRange = targetRange;
this.customFlags = customFlags;
this.command = ['-oX', '-', ...(customFlags || []), targetRange];
}
startScan() {
setImmediate(() => {
this.emit('complete', [
{ ip: '192.168.1.10', hostname: 'host-10', openPorts: [{ port: 80, protocol: 'tcp', service: 'http' }] }
]);
});
}
}
return {
NmapScan: MockNmapScan,
nmapLocation: 'nmap'
};
});
describe('nmap discovery plugin', () => {
test('discover passes custom flags (-Pn, -sT, -F, --min-rate) to constructor', async () => {
const logs = [];
const result = await nmapPlugin.discover({
targetRange: '192.168.1.0/24',
log: (msg) => { logs.push(msg); }
});
const startLog = logs.find(l => l.startsWith('Starting nmap scan'));
expect(startLog).toBeDefined();
expect(startLog).toContain('-Pn');
expect(startLog).toContain('-sT');
expect(startLog).toContain('-F');
expect(startLog).toContain('--min-rate 100');
expect(result.resources).toHaveLength(2); // host + service
expect(result.resources[0].name).toBe('host-10');
expect(result.edges).toHaveLength(1);
});
});
+51
View File
@@ -0,0 +1,51 @@
'use strict';
jest.mock('@simpleworkjs/bao-conf', () => ({
get: jest.fn(),
set: jest.fn(),
request: jest.fn(),
}));
jest.mock('redis', () => ({
createClient: () => ({
on: jest.fn(),
connect: jest.fn().mockResolvedValue(),
get: jest.fn().mockResolvedValue(null),
set: jest.fn().mockResolvedValue(),
})
}));
const baoConf = require('@simpleworkjs/bao-conf');
const vaultBroker = require('../utils/vault_broker');
describe('vault_broker admin policy', () => {
beforeEach(() => {
baoConf.request.mockReset();
});
test('getOrCreateAdminToken ensures sso-admin policy with list capabilities on metadata', async () => {
baoConf.request.mockImplementation(async (method, path, body) => {
if (method === 'GET' && path === 'sys/policies/acl/sso-admin') {
return { status: 404, text: async () => '' };
}
if (method === 'PUT' && path === 'sys/policies/acl/sso-admin') {
expect(body.policy).toContain('path "secret/metadata" { capabilities = ["list", "read", "delete"] }');
expect(body.policy).toContain('path "secret/metadata/" { capabilities = ["list", "read", "delete"] }');
return { status: 204, ok: true };
}
if (method === 'POST' && path === 'auth/token/create/sso-broker') {
return {
ok: true,
json: async () => ({ auth: { client_token: 'test-admin-token', lease_duration: 3600 } })
};
}
return { status: 200, ok: true, json: async () => ({}) };
});
const token = await vaultBroker.getOrCreateAdminToken('adminuser');
expect(token).toBe('test-admin-token');
expect(baoConf.request).toHaveBeenCalledWith('PUT', 'sys/policies/acl/sso-admin', expect.objectContaining({
policy: expect.stringContaining('path "secret/metadata/"')
}));
});
});