From 6a68aecafa1535e854f5bc06396f408f5765920e Mon Sep 17 00:00:00 2001 From: Christian Date: Sat, 16 May 2026 13:16:35 +0200 Subject: [PATCH] fix(telefoni): accept callbacks via db whitelist and internal fallback --- RELEASE_NOTES_v2.3.6.md | 6 +++++ app/modules/telefoni/backend/router.py | 23 ++++++++++++++++++- .../186_telefoni_ip_whitelist_setting.sql | 6 +++++ 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 RELEASE_NOTES_v2.3.6.md create mode 100644 migrations/186_telefoni_ip_whitelist_setting.sql diff --git a/RELEASE_NOTES_v2.3.6.md b/RELEASE_NOTES_v2.3.6.md new file mode 100644 index 0000000..ccc2af1 --- /dev/null +++ b/RELEASE_NOTES_v2.3.6.md @@ -0,0 +1,6 @@ +# Release Notes v2.3.6 — 16. maj 2026 + +## Telefoni Callback Fix +- telefoni callbacks now use both env and DB whitelist +- added internal 172.16.0.0/12 fallback acceptance +- added migration `migrations/186_telefoni_ip_whitelist_setting.sql` diff --git a/app/modules/telefoni/backend/router.py b/app/modules/telefoni/backend/router.py index 6c00a2e..f151078 100644 --- a/app/modules/telefoni/backend/router.py +++ b/app/modules/telefoni/backend/router.py @@ -122,11 +122,23 @@ def _is_internal_bmc_ip(client_ip: str) -> bool: return ip_obj in ipaddress.ip_network("172.16.31.0/24") +def _is_internal_bmc_supernet_ip(client_ip: str) -> bool: + if not client_ip: + return False + try: + ip_obj = ipaddress.ip_address(client_ip) + except ValueError: + return False + return ip_obj in ipaddress.ip_network("172.16.0.0/12") + + def _validate_yealink_request(request: Request, token: Optional[str]) -> None: env_secret = (getattr(settings, "TELEFONI_SHARED_SECRET", "") or "").strip() db_secret = (_get_setting_value("telefoni_shared_secret", "") or "").strip() accepted_tokens = {s for s in (env_secret, db_secret) if s} - whitelist = (getattr(settings, "TELEFONI_IP_WHITELIST", "") or "").strip() + env_whitelist = (getattr(settings, "TELEFONI_IP_WHITELIST", "") or "").strip() + db_whitelist = (_get_setting_value("telefoni_ip_whitelist", "") or "").strip() + whitelist = ",".join([part for part in (env_whitelist, db_whitelist) if part]) client_ip = _get_client_ip(request) path = request.url.path @@ -174,6 +186,15 @@ def _validate_yealink_request(request: Request, token: Optional[str]) -> None: else: logger.info("ℹ️ Telefoni callback whitelist not configured path=%s ip=%s", path, client_ip) + # Safety fallback: allow callbacks from internal BMC network range. + if _is_internal_bmc_supernet_ip(client_ip): + logger.warning( + "⚠️ Telefoni callback accepted via internal supernet fallback path=%s ip=%s", + path, + client_ip, + ) + return + logger.warning("❌ Telefoni callback forbidden path=%s ip=%s", path, client_ip) raise HTTPException(status_code=403, detail="Forbidden") diff --git a/migrations/186_telefoni_ip_whitelist_setting.sql b/migrations/186_telefoni_ip_whitelist_setting.sql new file mode 100644 index 0000000..e081af1 --- /dev/null +++ b/migrations/186_telefoni_ip_whitelist_setting.sql @@ -0,0 +1,6 @@ +-- Migration 186: Telefoni callback IP whitelist setting + +INSERT INTO settings (key, value, category, description, value_type, is_public) +VALUES + ('telefoni_ip_whitelist', '', 'telefoni', 'CSV med tilladte callback IP/CIDR til Yealink callbacks', 'string', false) +ON CONFLICT (key) DO NOTHING;