Zum Inhalt springen

Funktionen

Überblick über das, was das Gateway tatsächlich kann. Bedienung dazu: Admin-UI; Einbau auf der Kundenseite: Formular einbinden.

Ein Mandant ist eine Kundenseite. Angelegt wird er im Admin-UI, alles Weitere hängt daran:

  • Site-Key (site_…) — die öffentliche Kennung im Client-JS. Kein Geheimnis, nur Zuordnung; die Sicherheit liegt komplett serverseitig.
  • Empfänger-Adresse — das Ziel der Formularmails. Kommt nie aus dem Formular, sondern immer aus der Mandanten-Konfiguration.
  • Origin-Allowlist — von welchen Domains Absendungen angenommen werden. Serverseitig geprüft, nicht bloß per CORS.
  • Eigene SMTP-Route oder geteilte Default-Route — jeder Mandant kann über den eigenen Mailserver versenden oder über den der Plattform.
  • Kill-Switch — ein Klick, und der Mandant lehnt sofort alles ab.

Die Formulardaten jedes Mandanten liegen in einem eigenen ORM++-Tenant — Submissions und Nonces sind dadurch hart getrennt.

Das Gateway kennt keine Feldliste. Jedes <input name="…"> wird übertragen und landet im Mailtext; ein neues Feld braucht nur HTML, keine Konfiguration im Gateway. Nur drei Feldnamen sind reserviert: _gw_reply (wird Reply-To), _gw_form (Formular-Kennung) und _gw_hp (Honeypot, setzt das Snippet selbst).

Details und Grenzen: Formular einbinden.

Die Prüf-Pipeline arbeitet „billig zuerst” — was am wenigsten kostet, läuft zuerst, damit ein Angriff früh abbricht:

#SchichtWirkung
1Rate-Limits (global, IP, Site-Key)in-memory, vor allem anderen
2Mandant aktiv? Origin erlaubt?Kill-Switch und Allowlist, serverseitig
3Challenge-TokenHMAC-signiert, TTL 10 min, an Site und Origin gebunden
4Mindest-AusfüllzeitFormular in unter n Sekunden ausgefüllt = Bot
5Honeypotunsichtbares Feld; Treffer täuscht Erfolg vor
6IP-/ASN-ReputationTor-Exits, iptoasn.com, eigene Blocklist
6bHerkunftssperreLänder-/Regionsregel des Mandanten (IPv4 und IPv6)
7Proof-of-Workpro Mandant konfigurierbare Bit-Schwelle
7bALTCHA (optional)sichtbares Widget statt unsichtbarer Rechenarbeit, geprüft über den eigenen ALTCHA-Dienst
8Nonceatomar eingelöst — ein Token gilt genau einmal (Replay-Schutz)
9Eingabe-HärtungFeld- und Längenlimits, CRLF-Schutz, Reply-To-Validierung
10E-Mail-BlockmusterSperrmuster gegen die Antwortadresse
11Tageskontingentpersistent, zählt wartende und gesendete Mails
12Spam-Scoreab Schwelle Quarantäne statt Versand

Das eingebaute Proof-of-Work arbeitet unsichtbar: Der Browser rechnet, der Besucher merkt nichts. Manche Formularseiten wollen stattdessen ein sichtbares, barrierefreies Widget — dafür lässt sich je Mandant ALTCHA zuschalten (altcha_enabled).

ALTCHA ist ein Captcha-Ersatz ohne Cookies und ohne fremde Anbieter. Geprüft wird nicht im Gateway, sondern über den eigenen ALTCHA-Dienst: Dort liegt der Signaturschlüssel, und dort wird auch verhindert, dass dieselbe Lösung zweimal zählt.

Einrichten: In der config.json den Dienst hinterlegen —

"altcha": {
"url": "https://altcha.techeve.de",
"token": "…",
"timeout_seconds": 5
}

— und beim Mandanten altcha_enabled setzen. Ohne url bleibt alles beim Proof-of-Work; Mandanten mit altcha_enabled werden dann abgewiesen statt ungeprüft durchgelassen.

Auf der Formularseite gehört das ALTCHA-Widget ins Formular. Es legt selbst ein verstecktes Feld namens altcha an, dessen Wert das Snippet mitschickt:

<script src="https://altcha.techeve.de/widget.js"></script>
<form data-gateway="…">
…
<altcha-widget challenge="https://altcha.techeve.de/challenge"
name="altcha" auto="onload"></altcha-widget>
<button type="submit">Absenden</button>
</form>

Das Widget platziert der Seitenbetreiber selbst — so bleibt ihm die Gestaltung überlassen.

Zwei Elemente tragen denselben Namen. Das <altcha-widget> selbst führt das name-Attribut, das eigentliche Ergebnis steht in einem <input type="hidden"> dahinter. Wer den Wert selbst ausliest, muss deshalb input[name="altcha"] schreiben — ein Selektor ohne input findet zuerst das Widget und hält das Formular fälschlich für ungelöst. Fehlt es, obwohl der Mandant ALTCHA verlangt, bricht das Snippet mit einer Meldung ab, statt eine Einreichung zu schicken, die ohnehin abgewiesen würde.

Ist der Dienst gestört, wird abgewiesen. Das ist Absicht: Ohne ihn hätte der Besucher überhaupt keine Aufgabe bekommen, und ein stiller Durchlass würde den Schutz bei jeder Störung aushebeln.

Wiederholt auffällige IPs kann das Gateway zusätzlich an eine CrowdSec LAPI melden — die vorgelagerte Firewall sperrt sie dann, bevor der nächste Request überhaupt ankommt (Sicherheit).

Nach außen bleibt das Gateway wortkarg: jede Ablehnung ist 400 {"ok":false,"reason":"rejected"}. Welche Schicht gegriffen hat, steht nur im Log und im Admin — ein Spammer bekommt kein Orakel, an dem er seine Versuche kalibrieren kann. Honeypot-Treffer und Quarantäne sehen für den Client sogar wie Erfolg aus.

Alle Zahlenwerte (PoW-Bits, Spam-Schwelle, Tageskontingent, Mindest-Ausfüllzeit) sind pro Mandant einstellbar und einzeln abschaltbar (0).

Was über der Spam-Schwelle liegt, wird nicht verworfen, sondern quarantänisiert. Im Mandanten-Detail lässt sich jede Absendung freigeben (geht dann in den Versand) oder verwerfen. Falsch positive Treffer kosten damit keine Nachricht.

  • SMTP-Routen mit STARTTLS (587), implizitem TLS (465) oder unverschlüsselt (nur für Relays auf derselben Maschine).
  • Fester Absender pro Route — kein frei wählbares From aus dem Formular. Das schützt die Reputation der Absenderdomain; die Antwortadresse des Einsenders reist als Reply-To.
  • Retry mit Backoff über eine Queue in der Datenbank; ein kurzzeitig nicht erreichbarer Mailserver verliert keine Nachricht.
  • Test-Versand-Button im Admin prüft Verbindung und Login, bevor echte Formulare darauf laufen.
  • Health-Anzeige je Route: letzter Fehler, letzter erfolgreicher Versand.

Optional pro Mandant: Wer das Formular abschickt, bekommt eine kurze Bestätigung an die Adresse, die er selbst eingetragen hat (Feld _gw_reply). Den Text pflegt der Admin als Freitext — er steht so in der Mail, wie er im Admin steht.

  • Aus per Default. Ohne eingeschaltete Option und ohne Antwortadresse im Formular passiert nichts.
  • Ohne die eingesendeten Daten. Die Bestätigung enthält bewusst nur den festen Text. Würden die Formularfelder mitgeschickt, ließe sich über das Formular beliebiger Text an eine beliebige fremde Adresse schicken — das Gateway wäre ein Verstärker.
  • Erst nach bestandener Prüfung. Quarantänisierte Einsendungen lösen keine Bestätigung aus; erst die Freigabe im Admin tut es. Ein mutmaßlicher Spammer erfährt so auch nicht, dass seine Adresse zugestellt werden kann.
  • Zählt auf das Tageskontingent, weil sie an eine vom Absender bestimmte Adresse geht — das begrenzt zugleich das Missbrauchspotenzial.
  • Antworten erreichen den Seitenbetreiber: Reply-To der Bestätigung ist die Empfänger-Adresse des Mandanten.

Pro Mandant lässt sich einschränken, aus welchen Ländern Formulare abgeschickt werden dürfen:

ModusBedeutung
offkeine Einschränkung (Standard)
allownur die gelisteten Länder dürfen absenden
denydie gelisteten Länder dürfen nicht absenden

Gelistet werden Ländercodes (DE, AT, US …) oder Gruppen: DACH (DE/AT/CH), EU (die 27 Mitgliedstaaten), EEA (EU + IS/LI/NO), EUROPE (geografisches Europa inkl. CH, GB, UA).

Die Zuordnung IP → Land kommt aus demselben iptoasn-Dump, der ohnehin täglich für die ASN-Prüfung geladen wird — kein zusätzlicher Dienst, keine Anfrage an Dritte pro Formular. IPv4 und IPv6 werden gleich behandelt; eine Sperre lässt sich also nicht durch einen IPv6-Anschluss umgehen.

Wichtig — unbekannte Herkunft blockt nicht. Lässt sich eine IP keinem Land zuordnen (nicht geroutet, Dump noch nicht geladen oder Download gescheitert), gilt sie als erlaubt — auch im allow-Modus. Andernfalls würde ein gescheiterter Download eines Fremdservers das Kontaktformular komplett stilllegen. Die übrigen Schichten (PoW, Honeypot, Spam-Score, Quarantäne) greifen weiter.

Jeder Geo-Treffer erscheint als blocked-Submission im Admin (mit Grund, z. B. „herkunftsland US gesperrt (modus allow)”) und zählt für die Angriffserkennung mit. Hämmert dieselbe IP dauerhaft dagegen, reißt sie die CrowdSec-Schwelle und wird von der vorgelagerten Firewall gesperrt — bewusst mit niedrigem Gewicht, damit ein einzelner Geschäftsreisender im Ausland nicht sofort in der Firewall landet.

Eigene Sperrliste für IP, CIDR, ASN, E-Mail-Adressen und Länder/Regionen, jeweils mit Notiz. Einträge wirken sofort — sie werden in den Reputations-Speicher gespiegelt und greifen in Schicht 6 der Pipeline. Ein Länder-Eintrag hier gilt global für alle Mandanten; die mandantenspezifische Regel steht im Mandanten-Formular.

Web-UI und API im selben Binary:

  • Benutzer mit argon2id-Passwörtern und Rollen (RBAC, statischer Katalog im Code).
  • API-Keys (fgw_…) für Prozesse ohne Browser — mit Ablaufdatum, Widerruf, read-Scope und eigenem Rate-Limit.
  • Statistiken pro Mandant und Submission-Historie mit Statusfilter.

Details: Sicherheit.

  • Selbst gehostet — die Formulardaten verlassen die eigene Infrastruktur nicht.
  • Client-IPs landen nur als gekürzter SHA-256-Hash in der Datenbank.
  • Keine Cookies, kein Tracking im Client-Snippet, keine Drittressourcen.
  • DSGVO-Export und -Purge pro Kundenseite über die ORM++-Tenant-Registry — Auskunft und Löschung sind eine Operation, kein SQL-Abenteuer.
  • Audit-Log durch Event-Sourcing: jede Submission trägt ihre Statushistorie (received → scored → sent/quarantined → released/discarded).

Dependency-freies Vanilla-JS, ~1,6 KB gzip, minifiziert vom Gateway ausgeliefert. Holt die Challenge, löst den Proof-of-Work in einem Web Worker (mit Fallback auf den Hauptthread), sendet ab und zeigt den Status inline an. Mehrere Formulare pro Seite werden automatisch verdrahtet; ohne JavaScript bleibt das <form> ein normales Formular.

  • Ein Binary — Go-Backend, Svelte-Admin-UI und Client-Snippet sind einkompiliert. Eine Installation besteht aus Binary, config.json und Datenbankdatei.
  • SQLite oder PostgreSQL, umgeschaltet durch eine Konfigurationszeile ohne Codeänderung.
  • CGO-frei ⇒ Cross-Compiling für Linux (amd64/arm64), Windows und macOS ohne Toolchains.
  • Docker-Image (Alpine, non-root, read-only, Healthcheck) und systemd-Betrieb sind dokumentiert.
  • Graceful Shutdown bei SIGINT/SIGTERM, inklusive Hintergrund-Worker.
  • Versions- und Build-Info über -version, GET /api/v1/system/info und den Footer der Web-App.