Gegenbuch als Docker-Setup, lokales Buch auf eigener Maschine

Betreiber-Entscheidung: Das Gegenbuch laeuft auf einer eigenen Maschine fuer
Prod und Staging; ein externes Git-Repository entfaellt, das Buch liegt lokal.
Die Trennung, auf die es ankommt, ist damit gegeben - wer OpenCRM uebernimmt,
kommt nicht ans Buch.

Richtung bewusst so herum: Das Gegenbuch holt ueber HTTPS mit einem Token, das
nur audit:read kann. OpenCRM kennt weder Adresse noch Schluessel des
Gegenbuchs. Kein SSH-Zugang zum CRM noetig.

tools/audit-notary/ enthaelt jetzt Dockerfile, entrypoint.sh,
docker-compose.yml und .env.example. Zwei Dienste (prod, staging) mit
getrennten Verzeichnissen und Schluesseln, gesteuert ueber COMPOSE_PROFILES -
dasselbe Muster wie beim Caddy-Profil im Hauptstack. Der Signaturschluessel
wird beim ersten Start auf der Gegenbuch-Maschine erzeugt.

Lokaler Betrieb ist jetzt ein vollwertiger Modus statt eines Testschalters. Die
Erfolgsmeldung benennt bei jedem Lauf, was abgedeckt ist und was nicht - statt
der frueheren pauschalen Formulierung "kein Manipulationsschutz", die im
Einsatz auf eigener Maschine schlicht falsch war.

Verifiziert mit echtem Docker-Build gegen eine CRM-Attrappe: Genesis ohne
Bestaetigung -> Code 4; mit Bestaetigung Normalbetrieb exit 0; Eintrag
veraendert -> Alarm exit 2; Eintraege geloescht -> Alarm exit 2; Siegel
verschwunden -> Alarm.

Behoben beim Bauen: useradd -u 1000 || true verschluckte, dass UID 1000 im
Node-Image vergeben ist - der Container startete gar nicht.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-22 18:51:45 +02:00
co-authored by Claude Opus 5
parent 773033936d
commit a62c51b7c7
7 changed files with 316 additions and 5 deletions
+18 -4
View File
@@ -221,14 +221,22 @@ async function hole(pfad) {
return j.data;
}
// Lokaler Betrieb ist ein vollwertiger Modus, wenn das Gegenbuch auf einem
// EIGENEN Rechner laeuft: Wer das CRM uebernimmt, kommt dann nicht an dieses
// Buch. Was fehlt, ist der Schutz gegen jemanden, der DIESEN Rechner
// uebernimmt – das gehoert benannt, aber es ist kein „kein Schutz“.
const OHNE_ANKER = () =>
NUR_LOKAL ? ' – ACHTUNG: nur lokal, kein externer Anker, also kein Manipulationsschutz.' : '';
NUR_LOKAL
? '\n Betriebsart: lokal. Geschützt ist damit der Fall „jemand verändert Daten im ' +
'CRM“.\n NICHT geschützt ist der Fall „jemand übernimmt diesen Rechner hier“ – ' +
'dagegen hülfe\n nur eine zusätzliche Ablage außerhalb.'
: '';
// Ohne bekannte Mindesthoehe bleibt ein Rewind auf einen aelteren, echt
// signierten Stand unerkennbar (R178-01). Das gehoert in die Erfolgsmeldung,
// sonst liest sich „OK“ als mehr Gewissheit, als vorhanden ist.
const REWIND_VORBEHALT = () =>
REWIND_GESCHUETZT || ffBelegt
REWIND_GESCHUETZT || ffBelegt || NUR_LOKAL
? ''
: '\n Offen: Ein Zurückspulen der Reihe auf einen älteren, ebenfalls signierten Stand ' +
'lässt sich hier nicht ausschließen.' +
@@ -251,6 +259,11 @@ const REWIND_VORBEHALT = () =>
// bewusst NICHT mit dem Manipulationsalarm (2) verschmolzen.
const abschliessendSicher = (anzahl) =>
REWIND_GESCHUETZT ||
// Im lokalen Betrieb gibt es keinen Remote, den jemand zurueckspulen koennte.
// Der Vergleich gegen den eigenen Beobachtungsspeicher ist hier der
// vollstaendige Nachweis – die Grenze liegt bei der Integritaet dieses
// Rechners und ist als solche benannt, nicht ungeklaert.
(NUR_LOKAL && ffBelegt) ||
// BEWUSST NICHT `ankerBelegt` (Pentest R180-01): Die Anker-Refs zeigen nur
// den HOECHSTEN NOCH VORHANDENEN Stand – nicht den hoechsten je erreichten.
// Wer die obersten Refs loescht, senkt damit den Vergleichswert selbst; ein
@@ -305,8 +318,9 @@ if (hatRemote) {
process.exit(3);
}
} else if (process.env.NOTARY_ALLOW_LOCAL === 'true') {
// Der Schalter darf nicht zu einem glatten „OK“ fuehren: Leckt er je ins
// Produktivumfeld, gaebe es gruenes Licht auf nicht notarisiertem Stand.
// Ausdruecklich gewaehlter lokaler Betrieb (Gegenbuch auf eigenem Rechner,
// ohne zusaetzliche Ablage ausserhalb). Die Erfolgsmeldung benennt, was das
// abdeckt und was nicht – ein glattes „OK“ ohne Einordnung gibt es nicht.
NUR_LOKAL = true;
} else {
console.error(