import { describe, expect, test } from 'bun:test'; import { OBSERVER_PLACEMENTS, createObserverTransport, normalizeObservers, observerEnv, vantageContainer, } from './observer'; import type { ExecResult, NetworkHandle } from './types'; import { ZONE_GATEWAYS } from './types'; /** A NetworkHandle whose `exec` records calls and returns scripted results. */ function fakeHandle(script: (container: string, cmd: string) => Partial) { const calls: { container: string; cmd: string }[] = []; const exec = (container: string, cmd: string): Promise => { calls.push({ container, cmd }); return Promise.resolve({ stdout: '', stderr: '', exitCode: 0, ...script(container, cmd) }); }; return { handle: { exec } as unknown as NetworkHandle, calls }; } describe('placement faithfulness (the load-bearing invariants)', () => { test('internalDevice routes via the home router, NOT fw-main, and has no inter-zone routes', () => { const p = OBSERVER_PLACEMENTS.internalDevice; expect(p.network).toBe('internal'); expect(p.interZoneRoutes).toBe(false); // The whole point: default route is the LAN's home router, never ZONE_GATEWAYS.internal // (fw-main), which would hand it a path into the segmented zones. expect(p.gateway).not.toBe(ZONE_GATEWAYS.internal); }); test('no injectable observer carries inter-zone routes (only the management trap would)', () => { for (const p of Object.values(OBSERVER_PLACEMENTS)) { expect(p.interZoneRoutes).toBe(false); } }); test('segmented-zone observer IPs derive from their zone gateway (no pinned subnets)', () => { expect(OBSERVER_PLACEMENTS.dmzSystem.gateway).toBe(ZONE_GATEWAYS.dmz); expect(OBSERVER_PLACEMENTS.dmzSystem.ip).toBe(ZONE_GATEWAYS.dmz.replace(/\.\d+$/, '.240')); expect(OBSERVER_PLACEMENTS.secureSystem.ip).toBe( ZONE_GATEWAYS.secure.replace(/\.\d+$/, '.240'), ); }); test('publicInternet uses only the public resolver (never the internal split-horizon view)', () => { expect(OBSERVER_PLACEMENTS.publicInternet.resolvers).toEqual(['203.0.113.1']); }); test('observerEnv serializes the routing profile for the setup script', () => { const env = observerEnv(OBSERVER_PLACEMENTS.internalDevice); expect(env.OBSERVER_INTERZONE).toBe('0'); expect(env.OBSERVER_GATEWAY).toBe(OBSERVER_PLACEMENTS.internalDevice.gateway); expect(env.OBSERVER_RESOLVERS).toContain('203.0.113.1'); }); }); describe('normalizeObservers + vantageContainer', () => { test('dedupes and rejects management as an injectable observer', () => { expect( normalizeObservers([{ vantage: 'internalDevice' }, { vantage: 'internalDevice' }]), ).toHaveLength(1); expect(() => normalizeObservers([{ vantage: 'management' }])).toThrow(/management/); }); test('management vantage maps to the real management container; others to observers', () => { expect(vantageContainer('management')).toBe('management'); expect(vantageContainer('internalDevice')).toBe(OBSERVER_PLACEMENTS.internalDevice.service); }); }); describe('observer transport — parsing + container targeting', () => { test('resolve(system) parses getent first column and targets the right observer', async () => { const { handle, calls } = fakeHandle((_c, cmd) => cmd.startsWith('getent') ? { stdout: '10.226.1.253 www.iamtheinternet.org' } : {}, ); const t = createObserverTransport(handle); const r = await t.resolve({ from: 'internalDevice', name: 'www.iamtheinternet.org', method: 'system', }); expect(r.ips).toEqual(['10.226.1.253']); expect(calls[0].container).toBe('observer-internal'); }); test('resolve(dns) with explicit server', async () => { const { handle, calls } = fakeHandle((_c, cmd) => cmd.includes('dig') ? { stdout: '10.226.10.10\n' } : {}, ); const t = createObserverTransport(handle); const r = await t.resolve({ from: 'dmzSystem', name: 'auth.x', method: 'dns', server: '10.226.1.10', }); expect(r.ips).toEqual(['10.226.10.10']); expect(calls[0].cmd).toContain('@10.226.1.10'); }); test('resolve(hostfiles) parses /etc/hosts first column', async () => { const { handle } = fakeHandle((_c, cmd) => cmd.startsWith('grep -F') ? { stdout: '10.226.10.10 auth.iamtheinternet.org' } : {}, ); const t = createObserverTransport(handle); const r = await t.resolve({ from: 'dmzSystem', name: 'auth.iamtheinternet.org', method: 'hostfiles', }); expect(r.ips).toEqual(['10.226.10.10']); }); test('tcpConnect maps the container exit code', async () => { const { handle } = fakeHandle((_c, cmd) => ({ exitCode: cmd.includes('443') ? 0 : 1 })); const t = createObserverTransport(handle); expect( (await t.tcpConnect({ from: 'internalDevice', host: '10.226.1.253', port: 443 })).connected, ).toBe(true); expect( (await t.tcpConnect({ from: 'internalDevice', host: '10.226.10.10', port: 22 })).connected, ).toBe(false); }); test('httpsRequest parses cert (CN/issuer/SAN/selfSigned) + status', async () => { const { handle } = fakeHandle((_c, cmd) => { if (cmd.includes('openssl s_client')) { return { stdout: 'subject=CN = deathstar.iamtheinternet.org\n' + 'issuer=CN = Pebble Intermediate CA 1a2b3c\n' + 'X509v3 Subject Alternative Name:\n DNS:deathstar.iamtheinternet.org\n', }; } if (cmd.includes('%{http_code}')) return { stdout: '200' }; return { stdout: 'Caddy reverse proxy is running' }; }); const t = createObserverTransport(handle); const r = await t.httpsRequest({ from: 'publicInternet', url: 'https://deathstar.iamtheinternet.org', }); expect(r.status).toBe(200); expect(r.cert.cn).toBe('deathstar.iamtheinternet.org'); expect(r.cert.issuer).toContain('Pebble'); expect(r.cert.subjectAltNames).toContain('deathstar.iamtheinternet.org'); expect(r.cert.selfSigned).toBe(false); }); test('httpsRequest flags a self-signed cert (subject == issuer)', async () => { const { handle } = fakeHandle((_c, cmd) => cmd.includes('openssl s_client') ? { stdout: 'subject=CN = sketchy.local\nissuer=CN = sketchy.local\n' } : cmd.includes('%{http_code}') ? { stdout: '200' } : {}, ); const t = createObserverTransport(handle); const r = await t.httpsRequest({ from: 'internalDevice', url: 'https://sketchy.local' }); expect(r.cert.selfSigned).toBe(true); }); }); describe('host-shell footgun guard', () => { test('no command contains $(...) or backticks (would be host-evaluated through the wrap)', async () => { const { handle, calls } = fakeHandle(() => ({ stdout: '200', exitCode: 0 })); const t = createObserverTransport(handle); await t.resolve({ from: 'internalDevice', name: 'www.x', method: 'system' }); await t.resolve({ from: 'internalDevice', name: 'www.x', method: 'dns', server: '10.226.1.10', }); await t.resolve({ from: 'internalDevice', name: 'www.x', method: 'hostfiles' }); await t.tcpConnect({ from: 'internalDevice', host: '10.226.10.10', port: 443 }); await t.ping({ from: 'internalDevice', target: '10.226.1.253' }); await t.httpsRequest({ from: 'internalDevice', url: 'https://www.x' }); await t.inspectResolution({ from: 'internalDevice', name: 'www.x' }); expect(calls.length).toBeGreaterThan(0); for (const { cmd } of calls) { expect(cmd).not.toContain('$('); expect(cmd).not.toContain('`'); } }); });