Persistent PostgreSQL sandbox deployment
This commit is contained in:
Executable
+50
@@ -0,0 +1,50 @@
|
||||
import { Pool } from 'pg';
|
||||
import { generateKeyPairSync, randomUUID, sign } from 'node:crypto';
|
||||
import { postgresConfig } from '../server/market/postgres-config.ts';
|
||||
import { auditPostgres } from '../server/market/postgres-custody.ts';
|
||||
import { custodyMessage, CUSTODY_POLICY, type CustodyCommand, type CustodyPage } from '../shared/custody.ts';
|
||||
import { address } from '../server/testnet/consensus.ts';
|
||||
|
||||
// Run inside the new sandbox application container. Optional explicit fictitious credit probe only.
|
||||
const credit = process.argv.slice(2);
|
||||
if (credit.length && (credit.length !== 1 || credit[0] !== '--credit')) throw new Error('Expected no arguments or --credit');
|
||||
const configuration = await postgresConfig();
|
||||
const pool = new Pool(configuration);
|
||||
try {
|
||||
// Verify server policy too: correct credentials must NOT authenticate over plaintext TCP.
|
||||
const plaintext = new Pool({ ...configuration, ssl: false, connectionTimeoutMillis: 3000 });
|
||||
try {
|
||||
let rejected = false;
|
||||
try { await plaintext.query('SELECT 1'); } catch { rejected = true; }
|
||||
if (!rejected) throw new Error('Database unexpectedly permits plaintext authentication');
|
||||
} finally { await plaintext.end(); }
|
||||
const c = await pool.connect();
|
||||
try {
|
||||
const tls = await c.query('SELECT ssl,version FROM pg_stat_ssl WHERE pid=pg_backend_pid()');
|
||||
if (tls.rows[0]?.ssl !== true) throw new Error('Database connection is not encrypted');
|
||||
const role = await c.query('SELECT current_user AS role');
|
||||
if (role.rows[0].role !== 'custody_runtime') throw new Error('Expected restricted sandbox runtime role');
|
||||
const page = await fetch('http://127.0.0.1:3001/api/custody', { signal: AbortSignal.timeout(10000) });
|
||||
if (!page.ok) throw new Error('Custody API unavailable');
|
||||
const data = await page.json() as CustodyPage;
|
||||
if (data.info.mode !== 'sandbox' || data.info.liveDeposits || data.info.liveWithdrawals || data.info.nftSettlement) throw new Error('Unexpected custody capabilities');
|
||||
if (credit.length) {
|
||||
const keys = generateKeyPairSync('ec', { namedCurve: 'prime256v1' });
|
||||
const publicKey = keys.publicKey.export({ type: 'spki', format: 'der' }).toString('base64');
|
||||
const terms: Omit<CustodyCommand, 'signature'> = { version: 1, mode: 'sandbox', policy: CUSTODY_POLICY,
|
||||
network: data.info.network, chainId: data.info.chainId, custodianId: data.info.custodianId,
|
||||
actor: address(publicKey), publicKey, nonce: randomUUID(), expiresAt: Date.now() + 60000, action: { type: 'mock-credit' } };
|
||||
const signature = sign('sha256', Buffer.from(custodyMessage(terms)), { key: keys.privateKey, dsaEncoding: 'ieee-p1363' }).toString('base64');
|
||||
const result = await fetch('http://127.0.0.1:3001/api/custody/command', { method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ...terms, signature }), signal: AbortSignal.timeout(10000) });
|
||||
if (!result.ok || (await result.json()).availableAtomicXmr !== '1000000000000') throw new Error('Fictitious credit probe failed');
|
||||
// Test key never printed/persisted. The resulting fictitious liability remains in the sandbox journal.
|
||||
}
|
||||
await c.query('BEGIN ISOLATION LEVEL REPEATABLE READ READ ONLY');
|
||||
const audit = await auditPostgres(c); await c.query('COMMIT');
|
||||
console.log(JSON.stringify({ custodianId: data.info.custodianId, tls: tls.rows[0].version,
|
||||
role: role.rows[0].role, sandbox: true, ...audit }));
|
||||
} finally { c.release(); }
|
||||
} catch {
|
||||
console.error('Sandbox deployment check failed; no connection secrets logged.'); process.exitCode = 1;
|
||||
} finally { await pool.end(); }
|
||||
Executable
+30
@@ -0,0 +1,30 @@
|
||||
import { Pool } from 'pg';
|
||||
import { postgresConfig } from '../server/market/postgres-config.ts';
|
||||
import { migrateCustody, grantCustodyRuntime } from '../server/market/postgres-schema.ts';
|
||||
import { IS_MAINNET } from '../shared/network.ts';
|
||||
|
||||
// Explicit one-shot service in the local persistent sandbox stack, NOT HTTP-startup migrations.
|
||||
if (IS_MAINNET || process.env.WIKIDECK_LOCAL_PG_BOOTSTRAP !== 'sandbox-only') throw new Error('Local sandbox bootstrap only');
|
||||
const ca = '/secrets/admin/ca.crt';
|
||||
const config = (role: string) => postgresConfig({ WIKIDECK_CUSTODY_PG_URL_FILE: `/secrets/${role}/url`, WIKIDECK_CUSTODY_PG_CA_FILE: ca });
|
||||
try {
|
||||
const admin = new Pool(await config('admin'));
|
||||
try {
|
||||
const ownerConfig = await config('owner'), runtimeConfig = await config('runtime');
|
||||
for (const [role, password] of [['custody_owner', ownerConfig.password], ['custody_runtime', runtimeConfig.password]] as const) {
|
||||
// Static role allowlist; password is a generated hex token. Values are never printed, even on error.
|
||||
if (typeof password !== 'string' || !/^[a-f0-9]{64}$/.test(password)) throw new Error('Invalid generated credential');
|
||||
const exists = await admin.query('SELECT 1 FROM pg_roles WHERE rolname=$1', [role]);
|
||||
if (!exists.rowCount) await admin.query(`CREATE ROLE ${role} LOGIN PASSWORD '${password}' NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS`);
|
||||
}
|
||||
await admin.query('REVOKE CREATE ON SCHEMA public FROM PUBLIC; REVOKE ALL ON DATABASE wikideck_custody FROM PUBLIC');
|
||||
await admin.query('GRANT CONNECT,CREATE ON DATABASE wikideck_custody TO custody_owner; GRANT CONNECT ON DATABASE wikideck_custody TO custody_runtime');
|
||||
} finally { await admin.end(); }
|
||||
const owner = new Pool(await config('owner'));
|
||||
try { await migrateCustody(owner); await grantCustodyRuntime(owner, 'custody_runtime'); }
|
||||
finally { await owner.end(); }
|
||||
console.log('Local PostgreSQL schema and restricted runtime grants ready. Sandbox payments only.');
|
||||
} catch {
|
||||
console.error('Local PostgreSQL bootstrap failed; inspect roles, secret volumes, TLS and schema privately. No reset or fallback performed.');
|
||||
process.exitCode = 1;
|
||||
}
|
||||
Reference in New Issue
Block a user