feat(runtime): add production control-plane foundation
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
import { EventEmitter } from 'node:events';
|
||||
import { lstat, mkdtemp, mkdir, rm, symlink, writeFile } from 'node:fs/promises';
|
||||
import { createServer } from 'node:http';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join, resolve } from 'node:path';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { createRuntimeConfig, GATEWAY_AUTH_HEADER } from './runtime-config.js';
|
||||
import { buildApp } from './app.js';
|
||||
import { createCanaryGateway, type CanaryGateway } from './canary-gateway.js';
|
||||
import { createProductionReadiness } from './production-readiness.js';
|
||||
import { defaultKeychainMetadataCheck } from './production-readiness.js';
|
||||
import { readCanaryRuntimeOptions } from './canary-runtime.js';
|
||||
import { installBoundedShutdown } from './shutdown.js';
|
||||
import { secureTokenEqual } from './app.js';
|
||||
import { openDatabase } from './infrastructure/database/database.js';
|
||||
import { migrateDatabase } from './infrastructure/database/migrations.js';
|
||||
|
||||
const cleanup: Array<() => Promise<void>> = [];
|
||||
afterEach(async () => {
|
||||
await Promise.all(cleanup.splice(0).map((fn) => fn()));
|
||||
});
|
||||
const token = 'runtime-gateway-token-with-at-least-32-bytes';
|
||||
|
||||
describe('production runtime foundation', () => {
|
||||
it('creates one deeply immutable fixed-listener config and validates before side effects', () => {
|
||||
const root = resolve('/tmp/multi-simadmin-data');
|
||||
const config = createRuntimeConfig({
|
||||
MULTI_SIMADMIN_DATA_ROOT: root,
|
||||
MULTI_SIMADMIN_DATABASE_PATH: join(root, 'db.sqlite'),
|
||||
MULTI_SIMADMIN_GATEWAY_TOKEN: token,
|
||||
API_HOST: '127.0.0.1',
|
||||
API_PORT: '8790',
|
||||
});
|
||||
expect(config).toEqual({
|
||||
api: { host: '127.0.0.1', port: 8790 },
|
||||
dataRoot: root,
|
||||
databasePath: join(root, 'db.sqlite'),
|
||||
gatewayToken: token,
|
||||
});
|
||||
expect(Object.isFrozen(config)).toBe(true);
|
||||
expect(Object.isFrozen(config.api)).toBe(true);
|
||||
expect(() =>
|
||||
createRuntimeConfig({
|
||||
MULTI_SIMADMIN_DATA_ROOT: root,
|
||||
MULTI_SIMADMIN_DATABASE_PATH: '/tmp/escape.sqlite',
|
||||
MULTI_SIMADMIN_GATEWAY_TOKEN: token,
|
||||
}),
|
||||
).toThrow(/data root/i);
|
||||
expect(() =>
|
||||
createRuntimeConfig({
|
||||
MULTI_SIMADMIN_DATA_ROOT: root,
|
||||
MULTI_SIMADMIN_DATABASE_PATH: join(root, 'db.sqlite'),
|
||||
MULTI_SIMADMIN_GATEWAY_TOKEN: 'weak',
|
||||
}),
|
||||
).toThrow(/token/i);
|
||||
});
|
||||
|
||||
it('rejects a database path whose existing symlink ancestor escapes before creating a DB', async () => {
|
||||
const directory = await mkdtemp(join(tmpdir(), 'runtime-containment-'));
|
||||
cleanup.push(() => rm(directory, { recursive: true, force: true }));
|
||||
const root = join(directory, 'data');
|
||||
const outside = join(directory, 'outside');
|
||||
await mkdir(root);
|
||||
await mkdir(outside);
|
||||
await symlink(outside, join(root, 'escape'));
|
||||
const databasePath = join(root, 'escape', 'db.sqlite');
|
||||
|
||||
expect(() =>
|
||||
createRuntimeConfig({
|
||||
MULTI_SIMADMIN_DATA_ROOT: root,
|
||||
MULTI_SIMADMIN_DATABASE_PATH: databasePath,
|
||||
MULTI_SIMADMIN_GATEWAY_TOKEN: token,
|
||||
}),
|
||||
).toThrow(/data root|symlink/i);
|
||||
await expect(lstat(join(outside, 'db.sqlite'))).rejects.toMatchObject({ code: 'ENOENT' });
|
||||
});
|
||||
|
||||
it('reads and validates the canary gateway token from its environment', () => {
|
||||
expect(
|
||||
readCanaryRuntimeOptions({
|
||||
MULTI_SIMADMIN_GATEWAY_TOKEN: token,
|
||||
CANARY_DIST_DIR: '/tmp/dist',
|
||||
CANARY_PORT: '8789',
|
||||
CANARY_UPSTREAM_PORT: '8790',
|
||||
}),
|
||||
).toMatchObject({ gatewayToken: token, port: 8789, upstreamPort: 8790 });
|
||||
expect(() => readCanaryRuntimeOptions({ CANARY_DIST_DIR: '/tmp/dist' })).toThrow(/token/i);
|
||||
expect(() =>
|
||||
readCanaryRuntimeOptions({
|
||||
CANARY_DIST_DIR: '/tmp/dist',
|
||||
MULTI_SIMADMIN_GATEWAY_TOKEN: 'weak',
|
||||
}),
|
||||
).toThrow(/token/i);
|
||||
});
|
||||
|
||||
it('always compares fixed-size token digests with timingSafeEqual', () => {
|
||||
const compare = vi.fn(() => false);
|
||||
expect(secureTokenEqual('x', token, compare)).toBe(false);
|
||||
expect(compare).toHaveBeenCalledOnce();
|
||||
const [actual, expected] = compare.mock.calls[0] as unknown as [Buffer, Buffer];
|
||||
expect(actual).toHaveLength(32);
|
||||
expect(expected).toHaveLength(32);
|
||||
});
|
||||
|
||||
it('forces pluggable termination when graceful shutdown exceeds its deadline', async () => {
|
||||
vi.useFakeTimers();
|
||||
const target = new EventEmitter();
|
||||
const terminate = vi.fn();
|
||||
const stop = installBoundedShutdown(
|
||||
target,
|
||||
{ close: () => new Promise(() => undefined) },
|
||||
{ timeoutMs: 25, terminate },
|
||||
);
|
||||
const stopping = stop();
|
||||
await vi.advanceTimersByTimeAsync(25);
|
||||
await stopping;
|
||||
expect(terminate).toHaveBeenCalledOnce();
|
||||
vi.useRealTimers();
|
||||
});
|
||||
|
||||
it('default Keychain readiness checks only Darwin security executable metadata', async () => {
|
||||
const stat = vi.fn(async () => ({ isFile: () => true, mode: 0o100755 }));
|
||||
await expect(defaultKeychainMetadataCheck('darwin', stat)).resolves.toBe(true);
|
||||
expect(stat).toHaveBeenCalledExactlyOnceWith('/usr/bin/security');
|
||||
stat.mockClear();
|
||||
await expect(defaultKeychainMetadataCheck('linux', stat)).resolves.toBe(false);
|
||||
expect(stat).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('authenticates internal requests without reflecting credentials', async () => {
|
||||
const app = buildApp({ gatewayToken: token });
|
||||
cleanup.push(() => app.close());
|
||||
expect((await app.inject({ url: '/healthz' })).statusCode).toBe(401);
|
||||
expect(
|
||||
(
|
||||
await app.inject({
|
||||
url: '/healthz',
|
||||
headers: { [GATEWAY_AUTH_HEADER]: 'wrong-but-same-length-xxxxxxxxxxxxxxxxx' },
|
||||
})
|
||||
).statusCode,
|
||||
).toBe(401);
|
||||
const ok = await app.inject({ url: '/healthz', headers: { [GATEWAY_AUTH_HEADER]: token } });
|
||||
expect(ok.statusCode).toBe(200);
|
||||
expect(ok.payload).not.toContain(token);
|
||||
});
|
||||
|
||||
it('readiness checks a live migrated DB and auth configuration without reading secrets', async () => {
|
||||
const directory = await mkdtemp(join(tmpdir(), 'runtime-ready-'));
|
||||
cleanup.push(() => rm(directory, { recursive: true, force: true }));
|
||||
const db = openDatabase(join(directory, 'db.sqlite'));
|
||||
migrateDatabase(db);
|
||||
const keychainMetadataCheck = vi.fn(async () => true);
|
||||
const readiness = createProductionReadiness({ db, gatewayToken: token, keychainMetadataCheck });
|
||||
expect(await readiness()).toEqual({
|
||||
ready: true,
|
||||
checks: { database: 'ready', migrations: 'ready', gatewayAuth: 'ready', keychain: 'ready' },
|
||||
});
|
||||
expect(keychainMetadataCheck).toHaveBeenCalledOnce();
|
||||
db.close();
|
||||
expect((await readiness()).ready).toBe(false);
|
||||
});
|
||||
|
||||
it('gateway replaces forged auth and only falls back for extensionless routes', async () => {
|
||||
const directory = await mkdtemp(join(tmpdir(), 'runtime-gateway-'));
|
||||
await mkdir(join(directory, 'assets'));
|
||||
await writeFile(join(directory, 'index.html'), '<main>SPA</main>');
|
||||
cleanup.push(() => rm(directory, { recursive: true, force: true }));
|
||||
let seen: string | string[] | undefined;
|
||||
const upstream = createServer((request, response) => {
|
||||
seen = request.headers[GATEWAY_AUTH_HEADER];
|
||||
response.end('ok');
|
||||
});
|
||||
await new Promise<void>((done) => upstream.listen(0, '127.0.0.1', done));
|
||||
cleanup.push(() => new Promise<void>((done) => upstream.close(() => done())));
|
||||
const address = upstream.address();
|
||||
if (!address || typeof address === 'string') throw new Error('address');
|
||||
const gateway: CanaryGateway = createCanaryGateway({
|
||||
distDir: directory,
|
||||
port: 0,
|
||||
upstreamPort: address.port,
|
||||
gatewayToken: token,
|
||||
});
|
||||
await gateway.start();
|
||||
cleanup.push(() => gateway.stop());
|
||||
await fetch(`${gateway.origin}/healthz`, { headers: { [GATEWAY_AUTH_HEADER]: 'forged' } });
|
||||
expect(seen).toBe(token);
|
||||
expect((await fetch(`${gateway.origin}/assets/missing.js`)).status).toBe(404);
|
||||
expect((await fetch(`${gateway.origin}/missing.css`)).status).toBe(404);
|
||||
expect((await fetch(`${gateway.origin}/instances/one`)).status).toBe(200);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user