Files
MikrotikManager/backend/scripts/test-database-writer-retained-tls.mjs
T

106 lines
6.4 KiB
JavaScript

import assert from 'node:assert/strict'
import {execFileSync} from 'node:child_process'
import {randomBytes, X509Certificate} from 'node:crypto'
import {mkdtemp, mkdir, cp, readFile, rm, chmod, access} from 'node:fs/promises'
import {createServer} from 'node:net'
import {tmpdir} from 'node:os'
import {dirname, join, resolve} from 'node:path'
import EmbeddedPostgres from 'embedded-postgres'
import pg from 'pg'
import {poolOptions, retainPublisherTlsPeer} from './writer-profile/operator-contract.mjs'
import {sha} from './writer-profile/operator-files.mjs'
const openssl = process.env.MMAPP_TLS_FIXTURE_OPENSSL || 'openssl'
const host = 'localhost'
const reservePort = async () => {
const listener = createServer()
await new Promise((ready, reject) => listener.once('error', reject).listen(0, '127.0.0.1', ready))
return listener
}
const releasePort = listener => new Promise((ready, reject) => listener.close(error => error ? reject(error) : ready()))
const sourcePort = await reservePort()
const clonePort = await reservePort()
const port = sourcePort.address().port, clonePortNumber = clonePort.address().port
await Promise.all([releasePort(sourcePort), releasePort(clonePort)])
const transport = {host, port, user: 'mcp_test', password: randomBytes(24).toString('hex'), database: 'postgres'}
const certFile = 'server.pem', keyFile = 'server-key.pem'
const root = await mkdtemp(join(tmpdir(), 'mm-writer-tls-owned-'))
const databaseDir = join(root, 'pg'), cloneDir = join(root, 'pg-clone')
const sourceTlsDir = join(root, 'source-tls'), cloneTlsDir = join(root, 'clone-tls')
const cluster = (databaseDir, port, tlsDir) => new EmbeddedPostgres({databaseDir, port, user: transport.user, password: transport.password,
persistent: true, createPostgresUser: false, postgresFlags: ['-h', '127.0.0.1', '-c', 'ssl=on', '-c', `ssl_cert_file=${join(tlsDir, certFile)}`, '-c', `ssl_key_file=${join(tlsDir, keyFile)}`],
onLog: () => {}, onError: () => {}})
const postgres = cluster(databaseDir, port, sourceTlsDir), clone = cluster(cloneDir, clonePortNumber, cloneTlsDir)
let sourceStarted = false, cloneStarted = false
const fingerprint = ca => sha(new X509Certificate(ca).publicKey.export({format:'der', type:'spki'}))
const issue = async tlsDir => {
await mkdir(tlsDir, {mode:0o700})
execFileSync(openssl, ['req', '-new', '-newkey', 'rsa:2048', '-x509', '-nodes', '-days', '1', '-subj', '/CN=localhost',
'-addext', 'subjectAltName=DNS:localhost', '-keyout', keyFile, '-out', certFile], {cwd: tlsDir, stdio: 'ignore', windowsHide: true})
await chmod(join(tlsDir, keyFile), 0o600)
return readFile(join(tlsDir, certFile))
}
const withClient = async (config, work) => {
const pool = new pg.Pool({connectionTimeoutMillis:5000, ...config})
let client
try { client = await pool.connect(); return await work(client) }
finally { client?.release(true); await pool.end() }
}
const query = "SELECT (pg_control_system()).system_identifier::text AS system, oid::text AS oid FROM pg_database WHERE datname=current_database()"
try {
await postgres.initialise()
const first = await issue(sourceTlsDir)
await postgres.start(); sourceStarted = true
const pin = {serverName:host, caSha256:sha(first), spkiSha256:fingerprint(first)}
const request = {tls:pin, roles:{publisher:{name:transport.user}}, source:{databaseName:transport.database}}
const baseline = await withClient(poolOptions(transport, request, first), async client => {
const retained = retainPublisherTlsPeer(client, pin)
retained()
const {rows:[source]} = await client.query(query)
const {rows:[status]} = await client.query('SELECT ssl FROM pg_stat_ssl WHERE pid=pg_backend_pid()')
assert.equal(status.ssl, true)
await client.query("CREATE TABLE owned_tls_fixture (note text PRIMARY KEY); INSERT INTO owned_tls_fixture VALUES ('original')")
const connection = client.connection, socket = connection.stream
connection.stream = undefined
try { assert.throws(retained, /retained TLS peer unavailable/) } finally { connection.stream = socket }
return source
})
await withClient({...transport, ssl:false}, async client => assert.throws(() => retainPublisherTlsPeer(client,pin), /retained TLS peer unavailable/))
await postgres.stop(); sourceStarted = false
await cp(databaseDir, cloneDir, {recursive:true, force:false, errorOnExist:true})
await assert.rejects(access(join(cloneDir, keyFile)), {code:'ENOENT'})
const second = await issue(cloneTlsDir)
assert.notEqual(fingerprint(second), pin.spkiSha256)
await postgres.start(); sourceStarted = true
await clone.start(); cloneStarted = true
const cloneTransport = {...transport, port:clonePortNumber}
const bothTrusted = Buffer.concat([first,second])
await assert.rejects(withClient(poolOptions(cloneTransport, {...request,tls:{...pin,caSha256:sha(bothTrusted)}}, bothTrusted),
() => assert.fail('Wrong physical cluster must not reach application SQL')), /TLS SPKI refused/)
await withClient({...cloneTransport, ssl:{ca:bothTrusted, servername:host, rejectUnauthorized:true}}, async client => {
assert.equal(client.connection.stream.authorized, true)
const {rows:[source]} = await client.query(query)
assert.deepEqual(source, baseline)
assert.throws(() => retainPublisherTlsPeer(client,pin), /TLS SPKI refused/)
retainPublisherTlsPeer(client,{...pin,spkiSha256:fingerprint(second)})()
assert.deepEqual((await client.query('SELECT note FROM owned_tls_fixture')).rows, [{note:'original'}])
await client.query("INSERT INTO owned_tls_fixture VALUES ('clone-only')")
})
await withClient(poolOptions(transport, request, first), async client => {
retainPublisherTlsPeer(client,pin)()
assert.deepEqual((await client.query('SELECT note FROM owned_tls_fixture')).rows, [{note:'original'}])
})
console.log('Genuine PostgreSQL TLS: original accepted; independently writable physical clone with identical DB coordinates and a different CA-valid TLS key refused')
} finally {
const stops = await Promise.allSettled([
cloneStarted ? clone.stop() : undefined,
sourceStarted ? postgres.stop() : undefined,
])
assert(stops.every(result => result.status === 'fulfilled'), 'Owned cluster stop unconfirmed')
assert.equal(dirname(resolve(root)), resolve(tmpdir()))
assert.match(root, /mm-writer-tls-owned-/)
await rm(root, {recursive:true, force:true, maxRetries:20, retryDelay:100})
await assert.rejects(access(root), {code:'ENOENT'})
console.log('Owned PostgreSQL and temporary TLS material removed')
}