Skip to content
Snippets Groups Projects
Commit a17cc893 authored by David Schwarzmann's avatar David Schwarzmann
Browse files

Merge branch 'mr-webhook-security' into 'main'

Sicherheitsvorgaben für Webhooks

See merge request !33
parents 399f1500 291d2a93
No related branches found
No related tags found
1 merge request!33Sicherheitsvorgaben für Webhooks
---
title: Verwendung von Callbacks
---
import ApiLink from '@site/src/components/ApiLink'
Der FIT-Connect Zustelldienst informiert sendende und empfangende Systeme (API-Clients) aktiv über [neue Einreichungen](../getting-started/receiving/query.mdx) oder [Statusupdates](../getting-started/sending/query-status.mdx).
Hierzu werden HTTP-Callbacks genutzt, die auch als [Webhooks](https://de.wikipedia.org/wiki/WebHooks) bezeichnet werden.
Webhooks ermöglichen es, API-Clients aktiv über diese Ereignisse zu informieren ohne dass eine regelmäßige Abfrage ([Polling](https://de.wikipedia.org/wiki/Polling_(Informatik))) nötig wäre.
Technisch werden Webhooks als HTTP-POST-Request realisiert.
Im Folgenden verwenden wir die Begriffe *Callback* und *Webhook* synonym.
## Callback-URL
API-Clients stellen zum Empfang von Callbacks einen HTTP-Endpunkt bereit, an den der Zustelldienst einen HTTP-POST-Request übermitteln kann.
API-Clients müssen auf eingehende Callbacks mit einer HTTP-Response mit Status Code `200 OK` antworten.
Die URL dieses HTTP-Endpunkts bezeichnen wir als Callback-URL (`callbackUrl`).
Sie wird von dem an FIT-Connect angebundenen System festgelegt.
Eine solche Callback-URL kann z.B. wie folgt aussehen:
```
https://fachverfahren.beispielstadt.example.org/callbacks/fit-connect
```
Die Callback-URL **DARF NUR** über HTTPS erreichbar sein.
Der Zustelldienst wird Callbacks nur über eine via HTTPS verschlüsselte Verbindung auslösen.
## Konfiguration von Callbacks
Eine Konfiguration von Callbacks ist [über das Self-Service-Portal](../getting-started/receiving/destination.mdx) und über die API-Endpunkte <ApiLink to="/destinations/{destinationId}" withMethod="put" /> bzw. <ApiLink to="/destinations/{destinationId}" withMethod="patch" /> möglich.
Bei der Konfiguration werden die *Callback-URL* und ein *Callback-Secret* vom API-Client festgelegt.
Das *Callback-Secret* dient der Überprüfung der Echtheit (Authentizität) von eingehenden Callbacks (siehe nächster Abschnitt).
Das angegebene *Callback-Secret* kann über die API nur geschrieben und aktualisiert, aber nicht gelesen werden und **DARF NICHT** an Dritte weitergegeben werden.
Ein sicheres *Callback-Secret* kann über die folgenden Aufrufe erzeugt werden:
- Python: `python -c 'import secrets; print(secrets.token_urlsafe(32))'`
- Ruby: `ruby -rsecurerandom -e 'puts SecureRandom.hex(32)'`
- pwgen: `pwgen --secure 64 1`
Die Einrichtung von Callbacks im Self-Service-Portal wird im Artikel `TODO: Link einfügen` näher beschrieben.
### Konfiguration von Callbacks für Zustellpunkte
Über die API können Callbacks für Zustellpunkte wie folgt konfiguriert werden:
<Tabs
defaultValue="curl"
values={[
{label: 'curl', value: 'curl',},
{label: 'Java', value: 'java',},
{label: 'JavaScript', value: 'javascript',},
]}>
<TabItem value="curl" label="curl">
```bash
$ SERVICE_URL=...
$ JWT_TOKEN=...
$ DESTINATION_ID=...
$ CALLBACK_URL=https://fachverfahren.beispielstadt.example.org/callbacks/fit-connect
$ CALLBACK_SECRET=insecure_unsafe_qHScgrg_kP-R31jHUwp3GkVkGJolvBchz65b74Lzue0
$ curl -X PATCH \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $JWT_TOKEN" \
--data "{ \"callback\": { \"url\": \"$CALLBACK_URL\", \"secret\": \"$CALLBACK_SECRET\" }}" \
"$SERVICE_URL/destinations/$DESTINATION_ID"
```
</TabItem>
<TabItem value="java" label="Java">
:::caution TODO
TODO: Codebeispiel ergänzen
:::
</TabItem>
<TabItem value="JavaScript" label="JavaScript">
:::caution TODO
TODO: Codebeispiel ergänzen
:::
</TabItem>
</Tabs>
## Prüfung von Callbacks
API-Clients, die Callbacks empfangen, **MÜSSEN** zwingend sicherstellen, dass ausgelöste Callbacks von einem vertrauenswürdigen Zustelldienst stammen.
Hierzu enthalten Callbacks einen Message Authentication Code (HMAC) gemäß [RFC 2104](https://datatracker.ietf.org/doc/html/rfc2104) auf Basis des angegebenen *Callback-Secrets*.
Ein Message Athentication Code kann als „symmetrische Signatur“ verstanden werden und ermöglicht die Prüfung der Herkunft und Integrität eines eingehende Callbacks.
Der Message Authentication Code wird im HTTP-Header `callback-authentication` übertragen.
Um [Replay-Angriffe](https://de.wikipedia.org/wiki/Replay-Angriff) zu vermeiden, enthält der Message Authentication Code einen aktuellen Timestamp.
Dieser Timestamp wird im HTTP-Header `callback-timestamp` übertragen.
Bei der Prüfung der Echtheit des ausgelösten Callbacks **MÜSSEN** API-Clients prüfen, dass der angegebene Timestamp nicht älter als **5 Minuten** ist.
Das folgende Beispiel zeigt die Verwendung der HTTP-Header `callback-authentication` und `callback-timestamp`:
```http
POST /callbacks/fit-connect
callback-authentication: f4eig0ht6hdlsfz6DVqGjXi1j3RAombIQ7vjG1M2TFZx1fGurzg1nOEh00lPfLEulhio1RyTOav1e1aMi69SRg==
callback-timestamp: 1672527599
{"type":"https://schema.fitko.de/fit-connect/callbacks/new-submissions","submissionIds":["f39ab143-d91a-474a-b69f-b00f1a1873c2"]}
```
Der HMAC wird gebildet aus dem im HTTP-Header `callback-timestamp` übertragenen Zeitstempel und dem im HTTP-Body übertragenen Payload, getrennt durch das Zeichen `.` (Punkt), jeweils UTF-8-kodiert.
Als Hash-Algorithmus wird SHA-512 verwendet.
```
callback-authentication = BASE64(HMAC(key={callback-secret}, message={timestamp}.{http-body}))
```
Um den Message Authentication Code (HMAC) zu verifizieren, bildet der API-Client mit Hilfe des *Callback Secret* den HMAC nach und vergleicht diesen mit dem im HTTP-Header `callback-authentication` übertragenen HMAC.
Bei der Prüfung **MÜSSEN** die folgenden Implementierungshinweise zwingend zu beachten:
- Das Callback Secret **MUSS** in API-Clients konfigurierbar sein und **DARF NICHT** fest im Quellcode eines API-Clients einprogrammiert sein.
- Dies kann beispielsweise durch die Konfiguration des Callback Secret in einer Konfigurationsdatei oder über eine Umgebungsvariable (`$ export CALLBACK_SECRET=your_secret`) erreicht werden.
- Bei der Erzeugung des HMAC **MUSS** der Hash-Algorithmus `SHA-512` verwendet werden.
- Es **MUSS** geprüft werden, dass der angegebene Zeitstempel nicht älter als **5 Minuten** ist.
- Beim Vergleich des übertragenen HMAC und des vom API-Client gebildeteten HMAC **MUSS** ein zeitlich konstanter Zeichenfolgenvergleich (*constant time string comparison*) verwendet werden.
- In Python kann dies über die Verwendung der Methode [`hmac.compare_digest`](https://docs.python.org/2/library/hmac.html#hmac.compare_digest) erreicht werden.
- In Ruby kann dies über die Verwendung der Methode [`secure_compare`](https://rubydoc.info/github/rack/rack/master/Rack/Utils:secure_compare) erreicht werden.
- Callbacks mit ungültigem Message Authentication Code **MÜSSEN** von API-Clients ignoriert/verworfen werden.
Dabei ist zunächst der Zeitstempel (`callback-timestamp`-Header) und anschließend der HMAC (`callback-authentication`-Header) zu prüfen:
```python
# 1. Timestamp überprüfen
current_time_epoch = int(time.time())
seconds_five_minutes = 60 * 5
if current_time_epoch - request['headers']['callback-timestamp'] > seconds_five_minutes:
print('Error: timestamp too old')
sys.exit(1)
else:
print('timestamp ok')
# 2. HMAC berechnen
payload = str(request['headers']['callback-timestamp']) + '.' + request['body']
expected_hmac = hmac.digest(CALLBACK_SECRET.encode("utf-8"), payload.encode("utf-8"), digest=sha512)
expected_hmac_base64 = base64.b64encode(expected_hmac).decode()
print('hmac', expected_hmac_base64)
# 3. Berechneten HMAC mit HMAC aus HTTP-Header vergleichen
if not hmac.compare_digest(request['headers']['callback-authentication'], expected_hmac_base64):
print('Error: invalid hmac')
sys.exit(2)
else:
print('hmac ok')
```
Das dargestellte Script findet sich auch zur freien Verwendung im [FIT-Connect-Tools-Repository](https://git.fitko.de/fit-connect/fit-connect-tools/-/blob/main/validate-callback.py).
......@@ -5,31 +5,27 @@ title: Vorhandensein neuer Einreichungen prüfen
import ApiLink from '@site/src/components/ApiLink'
Neue Einreichungen können über zwei Wege erhalten werden. Entweder können neue Einreichungen über Polling abgefragt werden oder der Zustellpunkt wird über einen Callback vom Zustelldienst benachrichtigt.
Letzteres wäre die präferierte Variante, da dadurch unnötige Anfragen vermieden werden können und die Kommunikation so auf ein notwendiges Mindestmaß reduziert wäre.
Empfangende Systeme können sich über den Eingang neuer Einreichungen entweder durch periodische Anfragen informieren (Polling) oder vom Zustelldienst über Callbacks aktiv benachrichtigen lassen.
Letzteres stellt die empfohle Umsetzungsvariante dar, da hierdurch unnötige Anfragen vermieden werden können, was zu einer insgesamt besseren Performance und zu einer schnelleren Benachrichtung empfangnder Systeme führt.
## Callback
Wenn eine Callback-URL im Zustellpunkt hinterlegt ist, kann der Zustelldienst den Zustellpunkt aktiv via Callback über neue Einriechungen informieren.
Das Format, in welchem der Callback übertragen wird, ist im Endpunkt <ApiLink to="/destinations/{destinationId}" withMethod="put" /> definiert.
Die Konfiguration und Prüfung von Callbacks ist im Aritkel [Verwendung von Callbacks](../../details/callbacks.mdx) beschrieben.
Wenn eine neue Einreichung im Zustelldienst bereitsteht und eine Callback-URL im Zustellpunkt hinterlegt ist, kann der Zustelldienst den Zustellpunkt direkt informieren.
Das Format, in welchem der Callback übertragen wird, im Endpunkt <ApiLink to="/destinations" withMethod="post" /> beschrieben.
Im Endeffekt, ist es jedoch ein Array mit UUIDs für jede bereitstehende Einreichung, wie im Beispiel unten.
Callbacks zur Benachrichtigung über neue Einriechungen haben das folgende Format:
```json
[
"17b64151-fc80-4000-8fb7-6ddd08115780"
]
```
:::caution Hinweis
Der Mechanismus zum Konfigurieren und Entgegennehmen von Callbacks wird sich bis zur Veröffentlichung der finalen API-Spezifikation noch einmal ändern.
:::
```http
POST /callbacks/fit-connect
callback-authentication: f4eig0ht6hdlsfz6DVqGjXi1j3RAombIQ7vjG1M2TFZx1fGurzg1nOEh00lPfLEulhio1RyTOav1e1aMi69SRg==
callback-timestamp: 1672527599
{"type":"https://schema.fitko.de/fit-connect/callbacks/new-submissions","submissionIds":["f39ab143-d91a-474a-b69f-b00f1a1873c2"]}
```
Wenn der Zustellpunkt benachrichtigt wird, dann kann dieser über mehrere Befehle die Einreichung mit allen verbundenen Ressourcen herunterladen.
Mehrere Befehle dahingehend, da erst alle allgemeinen Informationen zur Einreichung heruntergeladen werden müssen und dann, falls notwendig alle mit ihr verbundenen Anlagen.
Wenn das empfangende System über den Eingang neuer Einreichungen benachrichtigt wurde, dann kann es diese wie im Artikel [Einreichung herunterladen](./download-submission.mdx) beschrieben herunterladen.
## Polling
Über Polling, d.h. regelmäßiges Abfragen des Endpuntes <ApiLink to="/submissions" /> , können alle Einreichungen abgefragt werden, die zum Abholen bereitstehen.
Dies wird aus Performancegründen jedoch nicht empfohlen.
......
......@@ -79,6 +79,7 @@ module.exports = {
'details/crypto',
'details/destination-management',
'details/jwk-creation',
'details/callbacks',
'details/schema-reference',
'details/pgp-export',
{
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment