# ZeroTier Kill Switch ## Overview The kill-switch mechanism provides emergency deactivation of ZeroTier network access at three granularities: a single device membership, all memberships for a user, or all memberships on a network. All kill operations are **reversible** — they set `active=False` and (in most cases) `status=SUSPENDED` but do not delete records, so affected users can re-activate or re-authenticate. ## Three Kill Operations | Granularity | Endpoint | Admin-only | Behavior | |---|---|---|---| | **Device X on network Y** | `POST /orgs//memberships//deactivate` | No (owner can self-deactivate) | Sets `active=False`. Status stays APPROVED. | | **All devices for a user** | `POST /orgs//kill-switch` | Yes | Sets `active=False`, `status=SUSPENDED` for every active membership in the org (optionally filtered to specific networks). | | **All devices on a network** | `POST /orgs//networks//kill-switch` | Yes | Sets `active=False`, `status=SUSPENDED` for every active membership on the network, across all users. | ## Detailed Endpoint Reference ### 1. Kill a Single Membership (Device + Network) ``` POST /api/v1/organizations//memberships//deactivate ``` **Auth:** `@login_required`, `@full_access_required` **Admin override:** Admins can deactivate any membership; non-admins can only deactivate their own. **Request body:** None **Response (200):** ```json { "success": true, "data": { "request": { "id": "...", "active": false, "status": "approved", ... } }, "message": "Request deactivated successfully" } ``` **Behavior:** - Ends the active `ActivationSession` with reason `manual_revoke` - De-authorizes the device node in the ZeroTier controller - Sets `request.active = False` (status **unchanged** — stays `approved`) - Logs `zt.membership.deactivated` audit event **Re-activation:** The user can re-activate via `POST /memberships//activate` or `POST /memberships/activate-all`. --- ### 2. Kill All Devices for a User ``` POST /api/v1/organizations//kill-switch ``` **Auth:** `@login_required`, `@require_admin`, `@full_access_required` **Request body:** ```json { "target_user_id": "uuid-of-user-to-kill", "scope": "organization", "network_ids": ["uuid-of-network-1", "uuid-of-network-2"], "reason": "Security incident — force deactivation" } ``` | Field | Type | Required | Default | Description | |---|---|---|---|---| | `target_user_id` | string (UUID) | yes | — | The user to deactivate | | `scope` | string | no | `"organization"` | `"organization"` (all networks) or `"selected_networks"` | | `network_ids` | array of UUIDs | no | `null` | Required when scope is `selected_networks` | | `reason` | string | no | `null` | Max 500 chars | **Response (200):** ```json { "success": true, "data": { "affected_count": 3 }, "message": "Kill switch triggered successfully" } ``` **Behavior:** - Queries all active, non-deleted `NetworkAccessRequest` rows for the target user - For each: ends session (reason `kill_switch`), de-authorizes in ZT - Sets `active = False`, **and** sets `status = SUSPENDED` if currently `APPROVED` - Logs `zt.kill_switch.activated` audit event with `affected_count` and `scope` **Re-activation:** The user's memberships are in `SUSPENDED` state. An admin must explicitly re-approve (change status back to `APPROVED`) before the user can re-activate. --- ### 3. Kill All Devices on a Network ``` POST /api/v1/organizations//networks//kill-switch ``` **Auth:** `@login_required`, `@require_admin`, `@full_access_required` **Request body:** ```json { "reason": "Network compromised — emergency deactivation" } ``` | Field | Type | Required | Default | Description | |---|---|---|---|---| | `reason` | string | no | `null` | Max 500 chars | **Response (200):** ```json { "success": true, "data": { "affected_count": 12 }, "message": "Network kill switch triggered successfully" } ``` **Behavior:** - Queries all active, non-deleted `NetworkAccessRequest` rows for the network, **regardless of user** - For each: ends session (reason `kill_switch`), de-authorizes in ZT - Sets `active = False`, and `status = SUSPENDED` if currently `APPROVED` - Logs `zt.network_kill_switch.activated` audit event with `affected_count` **Re-activation:** Same as user kill switch — each affected membership is `SUSPENDED` and needs admin re-approval. ## Comparison: Deactivation vs. Deletion | Operation | active | status | deleted_at | DB row | Reversible? | |---|---|---|---|---|---| | `POST /memberships//deactivate` | `false` | unchanged | `null` | preserved | Yes — re-activate | | `POST /kill-switch` (user) | `false` | `suspended` | `null` | preserved | Yes — admin re-approve | | `POST /networks//kill-switch` | `false` | `suspended` | `null` | preserved | Yes — admin re-approve | | `DELETE /memberships/` (soft) | `false` | unchanged | set | preserved | Partial — depends on join logic | | `DELETE /admin/memberships/` | `false` | — | — | **hard-deleted** | No | ## Audit Events | Event | Trigger | |---|---| | `zt.membership.deactivated` | Single membership deactivated (endpoint #1) | | `zt.kill_switch.activated` | User kill switch triggered (endpoint #2) | | `zt.network_kill_switch.activated` | Network kill switch triggered (endpoint #3) | All audit entries include `organization_id`, `user_id` (the actor), `resource_type`, `resource_id`, and `metadata` (affected count, scope, network IDs). ## Key Source Files | File | Purpose | |---|---| | `gatehouse_app/api/v1/zerotier.py` | Route handlers for all three endpoints | | `gatehouse_app/services/network_access_service.py` | `deactivate_request()`, `kill_switch()`, `kill_switch_network()` | | `gatehouse_app/services/zerotier_api_service.py` | `deauthorize_member()` — ZT controller call | | `gatehouse_app/utils/constants.py` | `AuditAction` and `KillSwitchScope` enums | | `gatehouse_app/models/zerotier/network_access_request.py` | `NetworkAccessRequest` model | | `gatehouse_app/models/zerotier/activation_session.py` | `ActivationSession` model | | `gatehouse_app/models/zerotier/kill_switch_event.py` | `KillSwitchEvent` model | | `tests/integration/test_zerotier.py` | Integration tests in `TestZeroTierMembership` |