feat: CALDAV_ALLOWED_CIDRS Whitelist für interne CalDAV/Nextcloud-Server

Interne Nextcloud-Instanzen im LAN können jetzt per .env-Variable
von der SSRF-Blockliste ausgenommen werden.

Beispiel in .env:
CALDAV_ALLOWED_CIDRS=192.168.1.0/24,10.10.5.50/32

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-24 12:53:22 +02:00
parent 30828c69e9
commit 7e19311d2a
3 changed files with 47 additions and 2 deletions
+6
View File
@@ -33,6 +33,12 @@ class Settings(BaseSettings):
first_superadmin_email: str = ""
first_superadmin_password: str = ""
# CalDAV / outbound HTTP
# Kommaseparierte CIDR-Whitelist für interne CalDAV-Server (z.B. Nextcloud im LAN).
# Diese CIDRs sind vom SSRF-Schutz ausgenommen.
# Beispiel: CALDAV_ALLOWED_CIDRS=192.168.1.0/24,10.10.5.50/32
caldav_allowed_cidrs: list[str] = []
@model_validator(mode='after')
def validate_secret_key(self):
if self.app_env == 'production' and self.secret_key == 'change-me-in-production':
+27 -2
View File
@@ -168,13 +168,38 @@ def _validate_caldav_url(url: str) -> None:
raise ValueError(str(exc)) from exc
def _get_allowed_networks() -> list[ipaddress.IPv4Network | ipaddress.IPv6Network]:
"""Gibt die konfigurierten Whitelist-Netzwerke zurück (CALDAV_ALLOWED_CIDRS)."""
from app.core.config import settings
networks = []
for cidr in settings.caldav_allowed_cidrs:
cidr = cidr.strip()
if not cidr:
continue
try:
networks.append(ipaddress.ip_network(cidr, strict=False))
except ValueError:
log.warning("Ungültiger CIDR in CALDAV_ALLOWED_CIDRS ignoriert: %r", cidr)
return networks
def _check_ip_blocked(ip: ipaddress.IPv4Address | ipaddress.IPv6Address, hostname: str) -> None:
"""Wirft ValueError wenn die IP in einem blockierten Netz liegt."""
"""Wirft ValueError wenn die IP in einem blockierten Netz liegt.
Whitelist (CALDAV_ALLOWED_CIDRS) schlägt die Blockliste
damit können interne Nextcloud-Server im LAN trotzdem genutzt werden.
"""
# Whitelist-Check zuerst: explizit erlaubte CIDRs überschreiben die Blockliste
for allowed in _get_allowed_networks():
if ip in allowed:
return # explizit erlaubt → kein Fehler
for network in _BLOCKED_NETWORKS:
if ip in network:
raise ValueError(
f"Host '{hostname}' ({ip}) liegt in einem privaten/reservierten "
f"Adressbereich ({network}) und darf nicht als CalDAV-Server genutzt werden."
f"Adressbereich ({network}) und darf nicht als CalDAV-Server genutzt werden. "
f"Tipp: Interne Server können per CALDAV_ALLOWED_CIDRS freigegeben werden."
)