Hardened systemd deployment contract for read-only Monero observer

This commit is contained in:
2026-10-22 10:08:15 -04:00
parent 158981e965
commit e835238d05
12 changed files with 170 additions and 7 deletions
+21
View File
@@ -28,3 +28,24 @@ test('database HBA requires TLS+SCRAM over TCP and provisioning refuses partial
assert.match(setup, /subjectAltName=DNS:database/);
assert.match(setup, /-checkend/);
});
test('standalone Monero observer service is non-root, filesystem-hardened and has no public listener', async () => {
const unit = await read('deploy/systemd/wikideck-monero-observer.service');
const guide = await read('deploy/systemd/README.md');
const observer = JSON.parse(await read('deploy/systemd/wallet-rpc.example.json')) as { network: string; url: string };
const mtls = JSON.parse(await read('deploy/systemd/mtls.example.json')) as { url: string; clientPrivateKeyFile: string };
assert.match(unit, /User=wikideck-observer/);
assert.match(unit, /ProtectSystem=strict/);
assert.match(unit, /ProtectHome=true/);
assert.match(unit, /NoNewPrivileges=true/);
assert.match(unit, /CapabilityBoundingSet=/);
assert.match(unit, /RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX/);
assert.match(unit, /WIKIDECK_MONERO_OBSERVER_WORKER=1/);
assert.doesNotMatch(unit, /^\s*(?:ListenStream|ListenDatagram|SocketUser)=/m);
assert.equal(observer.network, 'mainnet');
assert.equal(observer.url, mtls.url);
assert.equal(mtls.clientPrivateKeyFile, '/etc/wikideck/monero-observer/client.key');
assert.match(guide, /migrate-mainnet-schema/);
assert.match(guide, /does \*\*not\*\* issue customer deposit addresses/);
assert.doesNotMatch(`${unit}\n${guide}`, /(?:password|api[_-]?key)\s*[:=]\s*[^\s<]+/i);
});