feat: connectivity redesign phase 3+4 — per-connection health, per-peer fallback, connection CRUD API
Unit Tests / test (push) Successful in 13m15s
Unit Tests / test (push) Successful in 13m15s
Health probes (probe_health/refresh_health) are type-aware: WireGuard checks the last WG handshake timestamp, OpenVPN checks the tun/tap interface, Tor checks the control-port GETINFO, and sshuttle/proxy types do a TCP reachability probe to the remote endpoint. Results are persisted via set_connection_status and wired into the health_monitor_loop so the UI always has a current health snapshot without polling. Per-peer fail-open semantics: VPN, SSH, and proxy connections default to fail-closed (kill-switch stays active even when the tunnel is down). Tor defaults to fail-open. The default can be overridden per-peer via set_peer_failopen/effective_failopen. apply_routes skips the fwmark and kill-switch rules for any fail-open peer whose connection health is not "working", letting traffic fall back to direct routing transparently. New generic admin-only connection CRUD endpoints (GET/POST/PUT/DELETE /api/connectivity/connections, GET /<id>/health, PUT /api/connectivity/peers/<peer>/failopen) are guarded by the existing admin role check. connection.create, connection.update, connection.delete, and peer.failopen are all registered in ROUTE_ACTION_MAP for the audit hook so every change is recorded in the owner-visible change log. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -110,6 +110,39 @@ def test_unmapped_mutating_endpoint_gets_generic_action(auth_mgr, audit_mgr):
|
||||
assert match[0]['target_type'] == 'unknown'
|
||||
|
||||
|
||||
# ── connectivity v2 connection routes are audited ─────────────────────────────
|
||||
|
||||
def test_connection_create_audited(auth_mgr, audit_mgr):
|
||||
with _client(auth_mgr, audit_mgr, login_as='admin') as c:
|
||||
with patch('app.connectivity_manager') as cm:
|
||||
cm.create_connection.return_value = {'ok': True, 'connection': {'id': 'c'}}
|
||||
c.post('/api/connectivity/connections',
|
||||
json={'type': 'tor', 'name': 'T'})
|
||||
res = audit_mgr.query({'action': 'connection.create'})
|
||||
assert res['total'] >= 1
|
||||
assert res['entries'][0]['target_type'] == 'connection'
|
||||
|
||||
|
||||
def test_connection_delete_audited_with_id(auth_mgr, audit_mgr):
|
||||
with _client(auth_mgr, audit_mgr, login_as='admin') as c:
|
||||
with patch('app.connectivity_manager') as cm:
|
||||
cm.delete_connection.return_value = {'ok': True}
|
||||
c.delete('/api/connectivity/connections/conn_abc')
|
||||
res = audit_mgr.query({'action': 'connection.delete'})
|
||||
assert res['total'] >= 1
|
||||
assert res['entries'][0]['target_id'] == 'conn_abc'
|
||||
|
||||
|
||||
def test_peer_failopen_audited(auth_mgr, audit_mgr):
|
||||
with _client(auth_mgr, audit_mgr, login_as='admin') as c:
|
||||
with patch('app.connectivity_manager') as cm:
|
||||
cm.set_peer_failopen.return_value = {'ok': True, 'peer': 'bob'}
|
||||
c.put('/api/connectivity/peers/bob/failopen', json={'failopen': True})
|
||||
res = audit_mgr.query({'action': 'peer.failopen'})
|
||||
assert res['total'] >= 1
|
||||
assert res['entries'][0]['target_id'] == 'bob'
|
||||
|
||||
|
||||
# ── auth routes: never write password ─────────────────────────────────────────
|
||||
|
||||
def test_change_password_audited_without_value(auth_mgr, audit_mgr):
|
||||
|
||||
Reference in New Issue
Block a user