import { afterEach, beforeEach, describe, expect, it } from 'bun:test'; import { mkdtempSync, rmSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import type { DbClient } from '../db/client'; import { systemConfig } from '../db/schema'; import { loadTrustedSubnets } from '../hooks/capability-loader'; import { setupTestDatabaseAt } from '../test-utils/database'; import { resetTestDbPath } from '../test-utils/db-path'; import { buildTrustedSourceStore, composeTrustedSubnets, listAllTrustedSources, listTrustedSourcesFor, parseOperatorTrustedSubnets, } from './trusted-sources'; const FW = '192.168.0.254'; const VPN = '10.255.255.0/24'; const CONTROL_PLANE = '192.168.0.0/24'; describe('trusted-source store', () => { let dir: string; let db: DbClient; beforeEach(async () => { dir = mkdtempSync(join(tmpdir(), 'ts-')); const dbPath = join(dir, 'celilo.db'); process.env.CELILO_DB_PATH = dbPath; db = await setupTestDatabaseAt(dbPath); }); afterEach(() => { db.$client.close(); resetTestDbPath(); try { rmSync(dir, { recursive: true, force: true }); } catch { /* ignore */ } }); it('stamps registeredBy from the binding, not the caller', () => { const store = buildTrustedSourceStore(db, 'wireguard'); store.replace(FW, { subnets: [VPN], description: 'admin VPN clients' }); expect(store.list(FW)).toEqual([ { subnet: VPN, description: 'admin VPN clients', registeredBy: 'wireguard' }, ]); }); it('replace is idempotent — declaring the same set twice yields one row', () => { const store = buildTrustedSourceStore(db, 'wireguard'); store.replace(FW, { subnets: [VPN], description: 'first' }); store.replace(FW, { subnets: [VPN], description: 'second' }); expect(store.list(FW)).toHaveLength(1); expect(store.list(FW)[0].description).toBe('second'); }); // D5b: the set is DECLARED, so changing an admin VPN's client subnet revokes // the old one's reach. It used to keep reaching every zone forever. it('a subnet left out of a later declaration loses its reach', () => { const store = buildTrustedSourceStore(db, 'wireguard'); store.replace(FW, { subnets: [VPN, '10.9.9.0/24'], description: 'vpn' }); store.replace(FW, { subnets: [VPN], description: 'vpn' }); expect(store.list(FW).map((s) => s.subnet)).toEqual([VPN]); }); it('an empty declaration withdraws the consumer’s whole set', () => { const store = buildTrustedSourceStore(db, 'wireguard'); store.replace(FW, { subnets: [VPN], description: 'vpn' }); store.replace(FW, { subnets: [], description: 'vpn' }); expect(store.list(FW)).toEqual([]); }); // D5a, the refcount case: the owner is in the unique index, so two modules // trusting the same subnet are two rows and one withdrawing does not revoke // the other's reach. it('two consumers trusting the same subnet are two rows; one withdrawing leaves the other', () => { buildTrustedSourceStore(db, 'wireguard').replace(FW, { subnets: [VPN], description: 'vpn' }); buildTrustedSourceStore(db, 'other').replace(FW, { subnets: [VPN], description: 'also vpn' }); expect(listTrustedSourcesFor(db, FW)).toHaveLength(2); buildTrustedSourceStore(db, 'wireguard').replace(FW, { subnets: [], description: 'vpn' }); expect(listTrustedSourcesFor(db, FW)).toEqual([ { subnet: VPN, description: 'also vpn', registeredBy: 'other' }, ]); }); it('withdrawing on one firewall leaves other firewalls alone', () => { buildTrustedSourceStore(db, 'wireguard').replace(FW, { subnets: [VPN], description: 'vpn' }); buildTrustedSourceStore(db, 'other').replace('10.0.0.1', { subnets: [VPN], description: 'elsewhere', }); buildTrustedSourceStore(db, 'wireguard').replace(FW, { subnets: [], description: 'vpn' }); expect(buildTrustedSourceStore(db, 'wireguard').list(FW)).toEqual([]); expect(buildTrustedSourceStore(db, 'other').list('10.0.0.1')).toHaveLength(1); }); it('reads one firewall’s registrations without a module binding', () => { // Trust registered against one firewall is not trust granted by another — // the read is scoped, and the reader needs no identity to look. buildTrustedSourceStore(db, 'wireguard').replace(FW, { subnets: [VPN], description: 'vpn' }); buildTrustedSourceStore(db, 'other').replace('10.0.0.1', { subnets: ['172.16.9.0/24'], description: 'elsewhere', }); expect(listTrustedSourcesFor(db, FW).map((s) => s.subnet)).toEqual([VPN]); expect(listTrustedSourcesFor(db, '10.0.0.1').map((s) => s.subnet)).toEqual(['172.16.9.0/24']); }); it('reports every registration across firewalls, with who registered it', () => { buildTrustedSourceStore(db, 'wireguard').replace(FW, { subnets: [VPN], description: 'vpn' }); expect(listAllTrustedSources(db)).toEqual([ { firewallIp: FW, subnet: VPN, description: 'vpn', registeredBy: 'wireguard' }, ]); }); }); // Caught by e2e, and it is the whole reason the render input and the reporting // view are different calls. `on_uninstall` withdrew the VPN's trusted source, // logged success — and the reach rules were re-rendered anyway, because the // snapshot the loader had injected into the provider's config still listed the // subnet, and the provider unions that snapshot with the live registry. The set // could grow but never shrink. One value, two sources of truth. describe('the render input excludes registrations; the reporting view includes them', () => { let dir: string; let db: DbClient; beforeEach(async () => { dir = mkdtempSync(join(tmpdir(), 'ts-render-')); const dbPath = join(dir, 'celilo.db'); process.env.CELILO_DB_PATH = dbPath; db = await setupTestDatabaseAt(dbPath); db.insert(systemConfig).values({ key: 'network.internal.subnet', value: CONTROL_PLANE }).run(); buildTrustedSourceStore(db, 'wireguard').replace(FW, { subnets: [VPN], description: 'admin VPN', }); }); afterEach(() => { db.$client.close(); resetTestDbPath(); try { rmSync(dir, { recursive: true, force: true }); } catch { /* ignore */ } }); it('omits registrations when no firewall is named (the render input)', () => { // The provider unions the LIVE registry itself; folding a snapshot in here // too is what stopped a withdrawal from ever taking effect. expect(loadTrustedSubnets(db).map((e) => e.subnet)).toEqual([CONTROL_PLANE]); }); it('includes them when a firewall IS named (the reporting view)', () => { const composed = loadTrustedSubnets(db, FW); expect(composed.map((e) => e.subnet)).toEqual([CONTROL_PLANE, VPN]); expect(composed[1].registeredBy).toBe('wireguard'); }); }); describe('composing the trusted-subnet set', () => { // The property that makes composition safe to ship AHEAD of any module that // registers: a fleet with no contributors renders exactly what it rendered // when the set was derived from the control plane alone. it('with no registrations and no override, yields the derived subnet alone', () => { expect( composeTrustedSubnets({ controlPlaneSubnet: CONTROL_PLANE, registered: [], operatorOverride: [], }), ).toEqual([{ subnet: CONTROL_PLANE, origin: 'derived-control-plane' }]); }); it('with no control plane and no contributors, yields nothing', () => { expect( composeTrustedSubnets({ controlPlaneSubnet: undefined, registered: [], operatorOverride: [], }), ).toEqual([]); }); it('merges all three origins', () => { const composed = composeTrustedSubnets({ controlPlaneSubnet: CONTROL_PLANE, registered: [{ subnet: VPN, description: 'admin VPN', registeredBy: 'wireguard' }], operatorOverride: ['172.16.5.0/24'], }); expect(composed.map((e) => e.subnet)).toEqual([CONTROL_PLANE, VPN, '172.16.5.0/24']); expect(composed.map((e) => e.origin)).toEqual([ 'derived-control-plane', 'registered', 'operator-override', ]); }); it('names WHO holds zone-wide reach, so it is never anonymous', () => { const composed = composeTrustedSubnets({ controlPlaneSubnet: CONTROL_PLANE, registered: [{ subnet: VPN, description: 'admin VPN', registeredBy: 'wireguard' }], operatorOverride: [], }); expect(composed[1]).toEqual({ subnet: VPN, origin: 'registered', registeredBy: 'wireguard', description: 'admin VPN', }); }); it('deduplicates an overlapping subnet, keeping the strongest claim', () => { const composed = composeTrustedSubnets({ controlPlaneSubnet: CONTROL_PLANE, registered: [{ subnet: CONTROL_PLANE, description: 'dup', registeredBy: 'wireguard' }], operatorOverride: [CONTROL_PLANE], }); expect(composed).toEqual([{ subnet: CONTROL_PLANE, origin: 'derived-control-plane' }]); }); it('an operator override adds to the derived set rather than replacing it', () => { // Replacing would let an operator strand celilo's own control plane, which // blocks the SSH every hook and converge runs over. const composed = composeTrustedSubnets({ controlPlaneSubnet: CONTROL_PLANE, registered: [], operatorOverride: ['172.16.5.0/24'], }); expect(composed.map((e) => e.subnet)).toContain(CONTROL_PLANE); }); }); describe('parsing the operator override', () => { it('splits, trims, and drops empties', () => { expect(parseOperatorTrustedSubnets(' 10.1.0.0/24 , 10.2.0.0/24 ,, ')).toEqual([ '10.1.0.0/24', '10.2.0.0/24', ]); }); it('an unset key contributes nothing', () => { expect(parseOperatorTrustedSubnets(undefined)).toEqual([]); expect(parseOperatorTrustedSubnets('')).toEqual([]); expect(parseOperatorTrustedSubnets(' ')).toEqual([]); }); it('parses the JSON-array form every sibling config key uses', () => { expect(parseOperatorTrustedSubnets('["10.226.120.0/24"]')).toEqual(['10.226.120.0/24']); expect(parseOperatorTrustedSubnets('[ "10.1.0.0/24" , "10.2.0.0/24" ]')).toEqual([ '10.1.0.0/24', '10.2.0.0/24', ]); }); it('refuses a JSON-looking value that does not parse to an array of strings', () => { // The exact e2e failure (ce-yaqa): a JSON array read as ONE subnet named // ["10.226.120.0/24"], rendered verbatim into rules.v4, and // iptables-restore failed the converge with "Bad argument `]'". expect(() => parseOperatorTrustedSubnets('{"a":1}')).toThrow(/JSON array of CIDR strings/); expect(() => parseOperatorTrustedSubnets('["10.0.0.0/24", 5]')).toThrow( /JSON array of CIDR strings/, ); expect(() => parseOperatorTrustedSubnets('[10.0.0.0/24')).toThrow( /looks like JSON but does not parse/, ); }); it('refuses a token that is not an IPv4 CIDR, naming it', () => { expect(() => parseOperatorTrustedSubnets('10.0.0.1')).toThrow(/10\.0\.0\.1/); expect(() => parseOperatorTrustedSubnets('10.0.0.1')).toThrow(/not an IPv4 CIDR/); expect(() => parseOperatorTrustedSubnets('10.0.0.0/24, hostname.local')).toThrow( /hostname\.local/, ); }); });