6.2 KiB
ZeroTier Network Lifecycle
Overview
This document covers the full lifecycle of ZeroTier networks in Gatehouse — how networks are created, who can see them, how members request access, and how devices are activated and deactivated.
Organization Membership Roles
Every user belongs to an organization via an OrganizationMember record. Roles determine what a user can see and do:
| Role | Can list networks? | Can see invite-only networks? | Can create/update/delete networks? | Can approve access requests? |
|---|---|---|---|---|
owner |
Yes | Yes | Yes | Yes |
admin |
Yes | Yes | Yes | Yes |
member |
Yes | No | No | No |
guest |
Yes | No | No | No |
Role checks happen via the OrganizationMember.is_admin() method, which returns True for owner and admin.
Network Request Modes
Every PortalNetwork has a request_mode field that controls how users gain access:
| Mode | Value | Behavior |
|---|---|---|
open |
"open" |
Any org member can join directly without approval |
approval_required |
"approval_required" |
User requests access; a manager must approve |
invite_only |
"invite_only" |
Only managers can assign access; invisible to non-admins |
Network Listing Visibility
GET /organizations/{org_id}/networks
The listing endpoint applies two visibility filters:
- Soft-delete filter — networks with a non-null
deleted_atare always excluded. - Active filter — by default, only networks where
is_active = Trueare returned. Pass?include_inactive=trueto include disabled networks. - Invite-only filter — networks with
request_mode = "invite_only"are hidden from non-admin users (memberandguestroles). Admins and owners see all networks.
Filtering logic
The filtering happens in portal_network_service.list_networks():
# Non-admin users cannot see invite-only networks
if user_id is not None:
membership = OrganizationMember.query.filter(...).first()
is_admin = membership.is_admin() if membership else False
if not is_admin:
q = q.filter(PortalNetwork.request_mode != NetworkRequestMode.INVITE_ONLY)
Network CRUD
| Action | Endpoint | Required Role |
|---|---|---|
| List networks | GET /organizations/{id}/networks |
Any org member (visibility restricted as above) |
| Create network | POST /organizations/{id}/networks |
admin or owner |
| Update network | PUT /organizations/{id}/networks/{id} |
admin or owner |
| Delete network | DELETE /organizations/{id}/networks/{id} |
admin or owner |
Device Registration
Before a user can access a network, they must register a device:
POST /organizations/{org_id}/devices
A Device record ties a ZeroTier node (10-char node_id) to a user within an org.
| Field | Purpose |
|---|---|
node_id |
ZeroTier 10-char node identifier |
device_nickname |
Human-friendly label |
hostname |
Optional hostname for identification |
Network Access Request Lifecycle
The core model is NetworkAccessRequest (table: network_access_requests). Each row represents one user + one device + one network. See zerotier-device-membership.md for the full schema.
Flow by request mode
Open networks — user calls join_network_for_device() directly:
- Creates
NetworkAccessRequestwithstatus=APPROVED,active=False - Returns the request
Approval-required networks — user calls request_access():
- Creates
NetworkAccessRequestwithstatus=PENDING - Admin calls
approve_request()→ setsstatus=APPROVED - User calls
activate_membership()→ setsactive=True, createsActivationSession
Invite-only networks — only an admin can call assign_access():
- Admin creates
NetworkAccessRequestwithstatus=APPROVED,grant_type=ASSIGNED - User calls
activate_membership()→ setsactive=True, createsActivationSession
The active flag
status |
active |
Meaning |
|---|---|---|
approved |
false |
Has permission but not currently connected |
approved |
true |
Has permission and device is authorized on the controller |
pending |
false |
Awaiting approval |
rejected / revoked / suspended |
false |
Access denied or removed |
Activation and Deactivation
Activation creates an ActivationSession with a configurable TTL (default 8 hours). The session is tied to the active=True state.
activate_membership()— setsactive=True, creates session, authorizes on ZeroTier controllerdeactivate_membership()— setsactive=False, ends session, de-authorizes on controller- Activation sessions expire automatically via the reconciliation worker, which sets
active=False
Kill switch
Admins can trigger a kill switch to deactivate all active memberships on an organization or network:
POST /organizations/{id}/kill-switch— deactivates all memberships in the orgPOST /organizations/{id}/networks/{id}/kill-switch— deactivates all memberships on a specific network
Reconciliation Worker
A scheduled job (runs every 2 minutes) performs:
- Expired activation cleanup — finds expired
ActivationSessionrecords, de-authorizes in ZeroTier, setsactive=False - Drift detection — compares portal state against ZeroTier controller state, repairs mismatches
Key Source Files
| File | Purpose |
|---|---|
gatehouse_app/models/zerotier/portal_network.py |
PortalNetwork model (network definition + request_mode) |
gatehouse_app/models/zerotier/network_access_request.py |
NetworkAccessRequest model (per-device membership) |
gatehouse_app/models/zerotier/activation_session.py |
ActivationSession model (TTL-based sessions) |
gatehouse_app/models/zerotier/device.py |
Device model |
gatehouse_app/models/organization/organization_member.py |
OrganizationMember model (roles) |
gatehouse_app/services/portal_network_service.py |
Network CRUD + listing logic |
gatehouse_app/services/network_access_service.py |
Access request + activation logic |
gatehouse_app/services/zerotier_reconciliation_service.py |
Expired session + drift reconciliation |
gatehouse_app/api/v1/zerotier.py |
All ZeroTier API endpoints |
gatehouse_app/utils/constants.py |
Enums (OrganizationRole, NetworkRequestMode, etc.) |