fix: whitelist peer-sync endpoint from session auth + CSRF

/api/cells/peer-sync/permissions is called over the WireGuard tunnel
by remote cells — they have no session cookie and cannot produce a CSRF
token. The endpoint authenticates via source IP (must be in the remote
cell's vpn_subnet) and WireGuard public key instead.

Without this, the global enforce_auth hook returns 401 before the route
handler runs, so all cross-cell permission pushes fail even when the
WG tunnel and iptables rules are correct.

Also adds a test verifying the route can be reached without a session.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-01 14:59:57 -04:00
parent 4a9c4cc58b
commit 59927b6ad7
2 changed files with 23 additions and 0 deletions
+6
View File
@@ -171,6 +171,9 @@ def enforce_auth():
# Always allow non-API paths and auth namespace
if not path.startswith('/api/') or path.startswith('/api/auth/'):
return None
# Cell peer-sync endpoints authenticate via source IP + WG pubkey — not session
if path.startswith('/api/cells/peer-sync/'):
return None
# Only enforce when auth_manager has been properly initialised and seeded.
# When the user store is empty (file missing or unreadable — typical in
# unit tests and fresh installs), bypass enforcement so pre-auth test
@@ -225,6 +228,9 @@ def check_csrf():
path = request.path
if not path.startswith('/api/') or path.startswith('/api/auth/'):
return None
# peer-sync uses IP+pubkey auth — no session, no CSRF token possible
if path.startswith('/api/cells/peer-sync/'):
return None
token_session = session.get('csrf_token')
if not token_session:
# Session predates CSRF tokens (existing login) — issue a token now so
+17
View File
@@ -205,3 +205,20 @@ def test_anon_can_reach_auth_namespace(anon_client):
# 401 is expected here but it must originate from the route, not a redirect/block
# on a non-auth path. The response should be JSON, not a redirect (3xx).
assert r.status_code not in (301, 302, 403)
def test_anon_can_reach_peer_sync_endpoint(anon_client):
"""POST /api/cells/peer-sync/* must not be blocked by session auth.
The peer-sync endpoint authenticates via source IP + WireGuard pubkey.
It must not return 401/403 from the global enforce_auth hook — the route
handler itself produces any rejection.
"""
r = anon_client.post(
'/api/cells/peer-sync/permissions',
json={},
content_type='application/json',
)
# 400 (bad payload) or 403 (IP/pubkey rejected) are acceptable — 401 from
# the global auth hook is NOT acceptable because the route has its own guard.
assert r.status_code != 401