36 lines
3.2 KiB
JavaScript
36 lines
3.2 KiB
JavaScript
import assert from 'node:assert/strict'
|
|
import {X509Certificate} from 'node:crypto'
|
|
import {checkServerIdentity} from 'node:tls'
|
|
import {sha} from './operator-files.mjs'
|
|
export const roleKeys=Object.freeze(['runtime','operator','capture','publisher','control'])
|
|
export const STARTUP_OPTIONS='-c statement_timeout=10000 -c lock_timeout=5000 -c client_encoding=UTF8'
|
|
export function assertJsDriverEnvironment(environment){assert(!Object.hasOwn(environment,'NODE_PG_FORCE_NATIVE'),'Native pg driver selector refused')}
|
|
const hex=v=>typeof v==='string'&&/^[a-f0-9]{64}$/.test(v)
|
|
const exact=(v,keys)=>v&&typeof v==='object'&&!Array.isArray(v)&&Object.keys(v).length===keys.length&&keys.every(k=>Object.hasOwn(v,k))
|
|
function parsedApproval(value){
|
|
assert(exact(value,['format','manifestSha256','definitionSha256','catalogSha256','expectedHeadSha256','validityMs','expiresAt']))
|
|
assert(value.format==='application-writer-publication-approval-v1'&&['manifestSha256','definitionSha256','catalogSha256'].every(k=>hex(value[k]))&&(value.expectedHeadSha256===null||hex(value.expectedHeadSha256)))
|
|
assert(Number.isSafeInteger(value.validityMs)&&value.validityMs>0&&value.validityMs<=86400000&&Number.isSafeInteger(value.expiresAt)&&value.expiresAt>0)
|
|
return structuredClone(value)
|
|
}
|
|
export function approval(value){
|
|
const pinned=parsedApproval(value);assert(pinned.expiresAt>Date.now());return pinned
|
|
}
|
|
// Pinned historical inspection data only; never publication authorization.
|
|
export function reconciliationApproval(value){return parsedApproval(value)}
|
|
export function poolOptions(transport,request,ca){
|
|
assert(exact(transport,['host','port','user','password','database']))
|
|
assert(typeof transport.host==='string'&&transport.host===request.tls.serverName&&Number.isSafeInteger(transport.port)&&transport.port>0&&transport.port<=65535&&transport.user===request.roles.publisher.name&&typeof transport.password==='string'&&transport.password.length>0&&transport.password.length<=4096&&transport.database===request.source.databaseName)
|
|
assert(sha(ca)===request.tls.caSha256);new X509Certificate(ca)
|
|
return {host:transport.host,port:transport.port,user:transport.user,password:transport.password,database:transport.database,max:1,connectionTimeoutMillis:5000,idleTimeoutMillis:5000,statement_timeout:10000,lock_timeout:5000,options:STARTUP_OPTIONS,application_name:'mmapp-writer-publisher',client_encoding:'UTF8',
|
|
ssl:{ca,servername:request.tls.serverName,rejectUnauthorized:true,checkServerIdentity(host,cert){
|
|
const failed=checkServerIdentity(request.tls.serverName,cert);if(failed)return failed
|
|
try{if(host!==request.tls.serverName||sha(new X509Certificate(cert.raw).publicKey.export({format:'der',type:'spki'}))!==request.tls.spkiSha256)return Error('Publisher TLS SPKI refused')}catch{return Error('Publisher TLS certificate refused')}
|
|
}}}
|
|
}
|
|
export function assertPublicationApproval(current,catalogue,structure,pinned,manifestSha256){
|
|
assert(pinned.expiresAt>Date.now()&&pinned.manifestSha256===manifestSha256&&pinned.definitionSha256===catalogue.definitionSha256)
|
|
assert(sha(JSON.stringify({base:catalogue.catalogSha256,security:structure.sha256}))===pinned.catalogSha256)
|
|
assert((current?.headSha256??null)===pinned.expectedHeadSha256)
|
|
}
|