Files
nexarch/connect/docs/API-03-PRUEFPROTOKOLL.md
patrickandClaude Sonnet 5 0fef62b2a3
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Canceled after 0s
CI / Web (dms-browser) (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Canceled after 0s
CI / Web (shl) (pull_request) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Canceled after 0s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
API-03: 03 externe webhook abonnements nutzt core api 07
Connect-seitige Webhook-Abonnements ueber Core API-07 (zentrale
Webhook-Registry): connect/internal/webhookclient als schlanker
HTTP-Client gegen die Registry (Subscribe/Unsubscribe), connect/internal/
webhooksub mit lokaler, verschluesselter Abonnementverwaltung und
HTTP-Zustellempfaenger inkl. HMAC-Signaturpruefung. Core API-07 exponiert
bislang keinen HTTP-Wrapper fuer physisch getrennte Module (anders als
z.B. AUD-08) -- Tests laufen deshalb gegen einen den dokumentierten
Core-Vertrag nachbildenden Stub, echte Postgres-Testdatenbank, echte
HTTP-Verbindungen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-16 13:18:10 +00:00

8.8 KiB
Raw Permalink Blame History

API-03 – Prüfprotokoll: Externe Webhook-Abonnements (nutzt Core API-07)

Datum: 2026-10-16. Host: 204 (Wegwerf-Postgres, TEST_TENANT_DSN, isoliertes Schema pro Testlauf über connect/internal/testdb).

Ausgangslage: Schnittstelle zu Core API-07

Core API-07 (internal/webhook, Modul gitea.perlbach24.de/scripte/nexarch) implementiert Registrierung (Store.Subscribe), Zustellung mit Retry/Backoff (Dispatcher) und HMAC-Signatur (Sign/VerifySignature) als reines Go-Paket. Recherche im Core-Modul (gleicher Checkout, internal/webhook/*.go, internal/contracttest/contracttest_test.go) ergab: es existiert aktuell kein HTTP-Handler/cmd/*-Dienst, der Store.Subscribe für physisch getrennte Module (Connect, DMS, Mail, Archive) über HTTP erreichbar macht — anders als z. B. AUD-08 (internal/audit/ingest_handler.go + cmd/auditlog-devserver), das exakt dieses Muster für den Audit-Log bereits umsetzt. Core-seitig fehlt zusätzlich eine Unsubscribe-Methode auf Store völlig.

Das ist eine Lücke auf Core-Seite (core-kanban), nicht Gegenstand dieser Kachel (Connect ist laut Auftrag physisch getrennt und darf Cores internal/-Pakete nicht importieren — nur über HTTP koppeln). Diese Kachel erlaubt für genau diesen Fall ausdrücklich einen „minimal gestubten Core-Webhook-Registry-Endpunkt" für den End-zu-Ende-Test. Umgesetzt wurde daher:

  • Der HTTP-Vertrag, den ein künftiger Core-HTTP-Wrapper für API-07 erfüllen müsste (POST /webhooks/subscriptions, DELETE /webhooks/subscriptions/{id}, X-Service-Token-Auth, Felder tenant_slug/event_type/target_url/secret), dokumentiert als Vertrag in connect/internal/webhookclient.
  • Alle Tests laufen über einen echten httptest.Server, der genau diesen Vertrag nachbildet (echte HTTP-Verbindung über Loopback-TCP, keine Mock-Schnittstelle des Clients selbst) — dasselbe Muster wie connect/internal/policyclient/connect/internal/auditclient bereits für andere Core-Schnittstellen verwenden.
  • HMAC-Signaturvertrag (X-Nexarch-Signature-256, hex-kodiertes HMAC-SHA256) 1:1 aus internal/webhook.SignatureHeader/VerifySignature übernommen, da Connect das Core-Paket nicht importieren kann (physische Modultrennung).

Umsetzung

  • connect/internal/webhookclient — schlanker HTTP-Client gegen Core API-07 (Subscribe, Unsubscribe), Muster aus connect/internal/auditclient/dms/internal/retentionclient.
  • connect/internal/webhooksub — Connect-seitige Verwaltung:
    • Manager.RegisterAction(connectorName, eventType, action): verdrahtet prozesslokal, welche Connector-Aktion ein Ereignis auslöst (Muster wie registry.Registry).
    • Manager.Subscribe: registriert bei Core (AK1), verschlüsselt das Zustellgeheimnis per connect/internal/crypto (Envelope-Verschlüsselung, gleiches Muster wie credential.Store) und speichert erst danach lokal. Schlägt die Core-Registrierung fehl, bleibt nichts lokal zurück.
    • Manager.Unsubscribe: bestellt zuerst bei Core ab, löscht die lokale Zeile erst danach vollständig (AK3). Schlägt die Core-Abbestellung fehl, bleibt die lokale Zeile für einen Wiederholungsversuch erhalten statt ein verwaistes Core-Abonnement zu hinterlassen.
    • Manager.RegisterRoutes/handleDelivery (delivery.go): HTTP-Endpunkt POST /connector-webhooks/deliveries/{id}, an den Core laut target_url zustellt. Prüft die HMAC-Signatur zeitkonstant (crypto/subtle.ConstantTimeCompare) gegen das entschlüsselte Geheimnis, löst bei Erfolg die registrierte Connector-Aktion aus (AK2).
  • Migration 0013_connector_webhook_subscriptions — Tabelle connector_webhook_subscriptions (lokale id = Zustellpfad-Segment, core_subscription_id, verschlüsseltes Geheimnis, UNIQUE(tenant_slug, connector_name, event_type)).
  • connect/scripts/check-security-test-coverage.sh — internal/webhookclient/internal/webhooksub ergänzt (beide verarbeiten tenant-gebundene Geheimnisse/Core-Authentifizierung, gleiche Kategorie wie credential/auditclient).

Akzeptanzkriterien

  1. Connector kann ein Webhook-Ereignis über die Core-Webhook-Registry abonnieren — erfüllt: Manager.Subscribe ruft webhookclient.Subscribe (POST an die Registry) auf und verlangt vorab eine registrierte Connector-Aktion.
  2. Zugestelltes Ereignis löst die zugeordnete Connector-Aktion aus — erfüllt: handleDelivery prüft Signatur und ruft die per RegisterAction hinterlegte Funktion mit dem zugestellten Payload auf.
  3. Abbestellen entfernt das Abonnement vollständig — erfüllt: Manager.Unsubscribe entfernt sowohl bei Core (webhookclient.Unsubscribe) als auch die lokale Zeile; eine erneute Zustellung an die alte URL schlägt danach mit 404 fehl.

Prüfungen vor der Abnahme

# Prüfung Ergebnis
1 Testabonnement über Core-Webhook-Registry im Test erfolgreich registriert Bestanden. TestSubscribe_RegistersAtCoreAndPersistsLocally: echter HTTP-POST an den (gestubten) Core-Endpunkt, lokale Zeile mit core_subscription_id entsteht, Core-Server bestätigt das Abonnement. Negativ abgesichert durch TestSubscribe_WithoutRegisteredAction_Fails und TestSubscribe_CoreFailure_PersistsNothingLocally (kein verwaister lokaler Datensatz bei Core-Ausfall).
2 Testereignis löst im Test die erwartete Connector-Aktion aus Bestanden. TestHandleDelivery_ValidSignature_TriggersConnectorAction: echter HTTP-POST (eigener httptest.Server, echte TCP-Verbindung) mit gültiger HMAC-Signatur an die beim Subscribe erzeugte Zustell-URL löst die registrierte Aktion mit dem exakten Payload aus. Negativ abgesichert durch TestHandleDelivery_InvalidSignature_Rejected (401, Aktion bleibt aus) und TestHandleDelivery_UnknownSubscription_NotFound.
3 Abbestellung im Test vollständig entfernt Bestanden. TestUnsubscribe_RemovesAtCoreAndLocally_Completely: nach Unsubscribe ist die lokale Zeile weg und der Core-Stub bestätigt das entfernte Abonnement. Zusätzlich TestHandleDelivery_AfterUnsubscribe_NotFound (Zustellung an die alte URL schlägt danach mit 404 fehl) und TestUnsubscribe_CoreFailure_KeepsLocalRowForRetry (kein Datenverlust bei Core-Ausfall während der Abbestellung).

Testlauf (real, Host 204, gegen echte Postgres-Testdatenbank)

go build ./...                                                   → keine Ausgabe (sauber)
go vet ./...                                                      → keine Ausgabe (sauber)
gofmt -l internal/webhookclient internal/webhooksub                → keine Ausgabe (bereits formatiert)

go test ./internal/webhookclient/... -v
  PASS  TestSubscribe_SendsRequestFields_ReturnsSubscription
  PASS  TestSubscribe_UnexpectedStatus_ReturnsError
  PASS  TestSubscribe_TransportError_ReturnsError
  PASS  TestUnsubscribe_SendsServiceTokenAndTenant_Succeeds
  PASS  TestUnsubscribe_UnexpectedStatus_ReturnsError
  PASS  TestUnsubscribe_TransportError_ReturnsError
ok    connect/internal/webhookclient   0.007s

go test ./internal/webhooksub/... -v   (TEST_TENANT_DSN gesetzt, echte Postgres-DB)
  PASS  TestHandleDelivery_ValidSignature_TriggersConnectorAction
  PASS  TestHandleDelivery_InvalidSignature_Rejected
  PASS  TestHandleDelivery_UnknownSubscription_NotFound
  PASS  TestHandleDelivery_AfterUnsubscribe_NotFound
  PASS  TestSubscribe_RegistersAtCoreAndPersistsLocally
  PASS  TestSubscribe_WithoutRegisteredAction_Fails
  PASS  TestSubscribe_CoreFailure_PersistsNothingLocally
  PASS  TestUnsubscribe_RemovesAtCoreAndLocally_Completely
  PASS  TestUnsubscribe_UnknownSubscription_ReturnsNotFound
  PASS  TestUnsubscribe_CoreFailure_KeepsLocalRowForRetry
ok    connect/internal/webhooksub      1.572s–1.778s (mehrere Läufe)

go test ./... (gesamtes connect-Modul, TEST_TENANT_DSN gesetzt) → alle Pakete ok, kein FAIL

bash scripts/check-security-test-coverage.sh → "Merge-Gate bestanden: alle sicherheitsrelevanten Pakete haben Tests."

Keine Regressionen: alle zuvor bestehenden Pakete (u. a. credential, statusapi, syncjob, audittrail) bleiben unverändert grün.

Nicht Bestandteil dieser Kachel

  • QA-02 Prüfgate Connector-Engine — separates Folgeticket, nicht umgesetzt.
  • Core-seitiger HTTP-Wrapper für API-07 (Store.Subscribe/neue Unsubscribe-Methode über cmd/*-devserver, analog AUD-08) — Lücke auf Core-Seite, gehört ins core-kanban-Board, nicht in diese Kachel. Bis dahin bindet connect/internal/webhookclient an den hier dokumentierten, noch nicht Core-seitig existierenden Vertrag; Umstellung auf die reale Core-Basis-URL ist eine reine Konfigurationsänderung (NEXARCH_*_BASE_URL), keine Codeänderung.
  • Verdrahtung in connect-apps main.go (Produktions-KEKProvider gegen Core API-10) — bewusst unterlassen: auch connect/internal/credential (ältere, grundlegendere Kachel) ist dort noch nicht verdrahtet; ein produktiver KEKProvider für Connect existiert im Modul noch nirgends und wäre eine eigene, hier nicht beauftragte Erweiterung.