What We’re Testing
Machines can enter pending status when the org has posture policies enabled or when manual approval is configured. A pending machine cannot heartbeat successfully — the backend returns PENDING_APPROVAL (403) until an admin approves it.
The admin actions are handled by POST /api/machine-admin with these relevant actions:
approve— transitionspendingtoonline(requires admin, setsapproved_byandapproved_at)enable— sets status toonline, clearsadmin_disabledflag (admin or machine owner)disable— sets status tooffline, setsadmin_disabled=TRUE(admin or machine owner)quarantine— sets status toquarantined(requires admin)
Machine status state machine:
pending ──[approve]──→ online ←──[enable]──→ offline
│ ↑
└──[disable]───────────┘
│
└──[quarantine]──→ quarantined
A quarantined or admin-disabled machine’s heartbeat is rejected — the status is preserved server-side regardless of what the client sends.
Your Test Setup
| Machine | Role |
|---|---|
| ⊞ Win-A | Admin dashboard — perform approve/disable/enable actions |
| 🐧 Linux-C | Target machine — observe status transitions |
ST1 — Approve a Pending Machine
What it verifies: An admin can approve a pending machine, transitioning it to online status.
Steps:
- Register 🐧 Linux-C so it enters
pendingstatus. If your org has posture policies enabled, a fresh registration viaztna upwill land inpending. If not, you can simulate this via the API:
# On Win-A, using admin token:
TOKEN="YOUR_ADMIN_TOKEN"
MACHINE_ID="LINUX_C_MACHINE_ID"
# First, check current status
curl -s "https://login.quickztna.com/api/db/machines?id=eq.$MACHINE_ID&select=id,name,status" \
-H "Authorization: Bearer $TOKEN" | python3 -m json.tool
- On ⊞ Win-A , open the dashboard → Machines page.
- Look for a Pending Approval tab or filter. Find Linux-C in the pending list.
- Click Approve.
Alternative via API:
curl -s -X POST https://login.quickztna.com/api/machine-admin \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"action\":\"approve\",\"machine_id\":\"$MACHINE_ID\"}" | python3 -m json.tool
Expected response:
{
"success": true,
"data": {
"machine_id": "uuid",
"status": "online"
}
}
- On 🐧 Linux-C , check status:
ztna status
Expected: Within 60 seconds (next heartbeat cycle), the machine should show as connected.
Pass: Machine transitions from pending to online. Dashboard shows green status indicator. CLI connects successfully.
Fail / Common issues:
INVALID_STATE(400) — the machine is not inpendingstatus. It may already be approved.FORBIDDEN(403) — you must be an org admin to approve machines. Check your role on the dashboard.NOT_FOUND(404) — wrong machine ID. List machines to get the correct ID.
ST2 — Disable a Machine (Admin Action)
What it verifies: An admin can disable a machine, forcing it offline and preventing it from heartbeating back to online.
Steps:
-
Ensure 🐧 Linux-C is
onlineand running. -
On ⊞ Win-A , disable it via API:
curl -s -X POST https://login.quickztna.com/api/machine-admin \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"action\":\"disable\",\"machine_id\":\"$MACHINE_ID\"}" | python3 -m json.tool
Expected response:
{
"success": true,
"data": {
"machine_id": "uuid",
"status": "offline"
}
}
- On 🐧 Linux-C , check status after the next heartbeat (up to 60 seconds):
ztna status
-
Check the dashboard — the machine should show
offline. -
Even though
ztna upis still running on Linux-C, the backend will reject its heartbeat and keep it offline. The machine’s heartbeat response will haveadmin_disabled: true.
Pass: Machine forced to offline. Dashboard confirms offline. The admin_disabled flag prevents the machine from returning to online via heartbeat.
Fail / Common issues:
- Machine comes back online — the
admin_disabledflag may not be set. Verify via API:GET /api/db/machines?id=eq.MACHINE_ID&select=admin_disabled.
ST3 — Re-enable a Disabled Machine
What it verifies: The enable action restores a disabled machine to online status and clears the admin_disabled flag.
Steps:
- With 🐧 Linux-C still disabled from ST2:
curl -s -X POST https://login.quickztna.com/api/machine-admin \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"action\":\"enable\",\"machine_id\":\"$MACHINE_ID\"}" | python3 -m json.tool
Expected response:
{
"success": true,
"data": {
"machine_id": "uuid",
"status": "online"
}
}
- On 🐧 Linux-C , verify:
ztna status
Expected: Machine shows as connected. The next heartbeat succeeds normally.
- On ⊞ Win-A , verify on dashboard: machine shows
onlinewith a green indicator.
Pass: Machine returns to online after enable. Heartbeat resumes normally.
ST4 — Quarantine a Machine
What it verifies: The quarantine action locks a machine out — it cannot heartbeat back to online until an admin intervenes.
Steps:
- On ⊞ Win-A , quarantine Linux-C:
curl -s -X POST https://login.quickztna.com/api/machine-admin \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"action\":\"quarantine\",\"machine_id\":\"$MACHINE_ID\"}" | python3 -m json.tool
Expected response:
{
"success": true,
"data": {
"machine_id": "uuid",
"status": "quarantined"
}
}
-
On 🐧 Linux-C , the VPN is still running but the backend will return
quarantinedstatus on the heartbeat response. The machine is effectively isolated from the tailnet. -
Try pinging from ⊞ Win-A to Linux-C:
ztna ping 100.64.0.3 --count 3
Expected: Pings may fail or the machine may not appear in the peer list (quarantined machines are excluded from peer distribution).
- Restore the machine:
curl -s -X POST https://login.quickztna.com/api/machine-admin \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"action\":\"enable\",\"machine_id\":\"$MACHINE_ID\"}" | python3 -m json.tool
Pass: Machine enters quarantined status. Cannot connect to other peers. Admin enable action restores it.
Fail / Common issues:
FORBIDDEN— only org admins can quarantine machines.- Machine still reachable — peer lists may be cached for up to 60 seconds. Wait for the next heartbeat cycle.
ST5 — Pending Machine Heartbeat Rejection
What it verifies: A machine in pending status receives a PENDING_APPROVAL error on heartbeat, blocking it from joining the network.
Steps:
-
This test requires a machine in
pendingstatus. If your org has posture policies:- On 🐧 Linux-C , logout and re-register:
ztna down ztna logout ztna login --interactive ztna upThe machine should register as
pending. -
On 🐧 Linux-C , the CLI output will show:
Machine registered but requires admin approval.
Ask your org admin to approve this machine in the dashboard:
Machines page → Pending Approval tab → Approve
Run 'ztna up' again after approval.
- On ⊞ Win-A , verify the machine is pending:
curl -s "https://login.quickztna.com/api/db/machines?org_id=YOUR_ORG_ID&status=eq.pending&select=id,name,status" \
-H "Authorization: Bearer $TOKEN" | python3 -m json.tool
Expected: Linux-C appears with "status": "pending".
- The backend returns HTTP 403 with error code
PENDING_APPROVALon every heartbeat attempt from this machine.
Pass: Machine stuck in pending. CLI shows the approval message. Heartbeat returns PENDING_APPROVAL. Machine does not appear in peer lists of other machines.
Fail / Common issues:
- Machine goes directly to
online— your org may not have posture policies enabled. Auth-key registrations always go toonlineregardless of posture policies. - No pending approval tab in dashboard — the UI may show pending machines inline with a different status badge.
Cleanup: Approve the machine from the dashboard to restore normal operation.
Summary
| Sub-test | What it proves | Pass condition |
|---|---|---|
| ST1 | Admin approval | pending to online via approve action |
| ST2 | Admin disable | Machine forced offline, admin_disabled=TRUE |
| ST3 | Admin re-enable | Machine restored to online, heartbeat resumes |
| ST4 | Quarantine | Machine isolated, excluded from peer lists |
| ST5 | Pending heartbeat rejection | PENDING_APPROVAL (403) until approved |