feat: connectivity redesign phase 3+4 — per-connection health, per-peer fallback, connection CRUD API
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:
2026-06-10 21:50:45 -04:00
parent 8b50fb1036
commit d39c091cec
6 changed files with 1249 additions and 2 deletions
+9
View File
@@ -381,6 +381,15 @@ export const connectivityAPI = {
applyRoutes: () => api.post('/api/connectivity/exits/apply'),
getPeerExits: () => api.get('/api/connectivity/peers'),
setPeerExit: (peer_name, connection_id) => api.put(`/api/connectivity/peers/${peer_name}/exit`, { connection_id }),
// Connectivity v2 — generic connection CRUD + health + per-peer fallback
listConnections: () => api.get('/api/connectivity/connections'),
createConnection: (type, name, config = {}, secrets = {}) =>
api.post('/api/connectivity/connections', { type, name, config, secrets }),
updateConnection: (id, fields) => api.put(`/api/connectivity/connections/${id}`, fields),
deleteConnection: (id) => api.delete(`/api/connectivity/connections/${id}`),
probeConnectionHealth: (id) => api.get(`/api/connectivity/connections/${id}/health`),
setPeerFailopen: (peer_name, failopen) =>
api.put(`/api/connectivity/peers/${peer_name}/failopen`, { failopen }),
};
// Container Management API