IP-Allowlist
IpAllowlist beschränkt einen Route-Teilbaum auf Clients, deren IP in
einem der konfigurierten CIDRs liegt — Defence-in-depth zusätzlich zu
BearerTokenAuth: selbst ein
geleaktes Token ist außerhalb eines allowgelisteten Netzes nutzlos.
import { IpAllowlist, IpAllowlistOptions, withMiddleware } from 'actor-ts/http';
const ipAllowlistOptions = IpAllowlistOptions.create().withAllow('10.0.0.0/8', '127.0.0.1/32');const allowlist = IpAllowlist(ipAllowlistOptions);
const managementRoutes = withMiddleware(allowlist, clusterRoutes);IPv4- und IPv6-CIDRs werden beide unterstützt ('::1/128',
'fd00::/8'), und IPv4-gemappte IPv6-Peers (::ffff:a.b.c.d) auf einem
Dual-Stack-Socket werden normalisiert, sodass ein einfaches
IPv4-CIDR trotzdem matcht. Eine nicht passende — oder fehlende —
Client-IP schlägt fehl-geschlossen mit 403.
Konfiguration
Abschnitt betitelt „Konfiguration“IpAllowlistOptions — Builder oder einfaches Objekt:
| Feld | Zweck |
|---|---|
allow | Nicht-leere Liste von CIDR-Strings; mindestens eines muss die Client-IP enthalten, sonst 403. Ungültige CIDR-Syntax wirft bei Konstruktion. |
trustedProxies | CIDRs der Reverse-Proxys vor dieser App. Ungesetzt (der Default) heißt: es wird überhaupt kein Forwarding-Header gelesen. Siehe unten. |
forwardedHeader | Header, der die Forwarded-Kette trägt. Default 'x-forwarded-for'; wird nur gelesen, wenn trustedProxies gesetzt ist. |
getClientIp | Ersetzt die IP-Extraktion komplett — die Notluke. Default liest request.remoteAddress (den Socket-Peer). null/undefined lehnt die Anfrage ab (fail-closed). Schließt sich mit trustedProxies gegenseitig aus. |
Hinter einem Reverse-Proxy
Abschnitt betitelt „Hinter einem Reverse-Proxy“Der Default liest den Socket-Peer — und der ist hinter einem Proxy der Proxy. Um die eigene Adresse des Clients zu bekommen, benennst du die Proxys; das ist die ganze Konfiguration:
const ipAllowlistOptions = IpAllowlistOptions.create() .withAllow('10.0.0.0/8') .withTrustedProxies('10.9.9.0/24');
const allowlist = IpAllowlist(ipAllowlistOptions);Die Middleware liest die Kette dann in Wire-Reihenfolge —
[...x-forwarded-for-Einträge, Socket-Peer], Client zuerst — und läuft
sie von rechts ab; sie nimmt die erste Adresse, die keiner deiner
Proxys ist.
Diese Richtung ist der ganze Punkt, denn jeder verbreitete Proxy
hängt an einen eingehenden X-Forwarded-For an, statt ihn zu
ersetzen:
| Proxy | Was er mit X-Forwarded-For macht |
|---|---|
| NGINX | $proxy_add_x_forwarded_for ist der eingehende Header, per Komma um $remote_addr erweitert |
| AWS ALB | hängt den verbindenden Peer an (xff_header_processing steht per Default auf append) |
| Cloudflare | hängt an und setzt zusätzlich CF-Connecting-IP |
Der linkeste Eintrag ist also das, was der Client geschrieben hat, und nur die von rechts hinzugefügten Einträge kommen von der Infrastruktur. Daraus folgen drei Dinge, und jedes schließt eine Lücke, die ein handgeschriebener Extractor offen lässt:
- Ein Client, der die App direkt erreicht, ist der Socket-Peer, und
der Peer ist keiner deiner Proxys — der Lauf endet dort, und der Header
wird nie gelesen. Ein gefälschter
x-forwarded-forentscheidet nichts. - Müll, den ein Client davorschreibt, sitzt links von seiner echten Adresse und wird nie erreicht.
- Ein Eintrag, der keine gültige Adresse ist, matcht kein CIDR, gilt als
nicht vertrauenswürdig, beendet den Lauf — und scheitert dann auch an
allow.
Wo dein Proxy einen Single-Value-Header setzt statt anzuhängen,
richte denselben Lauf darauf aus. Bei Cloudflare ist das
CF-Connecting-IP:
const ipAllowlistOptions = IpAllowlistOptions.create() .withAllow('10.0.0.0/8') .withTrustedProxies('173.245.48.0/20', '103.21.244.0/22') // …die veröffentlichten Cloudflare-Ranges .withForwardedHeader('cf-connecting-ip');Wie weiter
Abschnitt betitelt „Wie weiter“- Bearer-Token-Auth — der Token-Schutz, den du darüberlegst.
- Sicherheit — wo die Allowlist im Stack sitzt.
- Management-Endpoints — die Routen, die du am häufigsten per Netz beschränkst.
