What We’re Testing
Remote shell session initiation is handled by handleRemoteShell in backend/src/handlers/remote-shell.ts, specifically the create_session action. The endpoint is:
POST https://login.quickztna.com/api/remote-shell
When action is create_session, the handler:
- Verifies the caller’s JWT via
getAuthedUser - Checks the
remote_managementfeature gate viarequireFeature(Workforce plan or higher required) - Confirms the caller is an org admin via
isOrgAdmin - Looks up the target machine in the
machinestable, assertingorg_idmatches andstatus = 'online' - Generates a one-time session token (two UUIDs joined by a hyphen)
- Writes an audit log entry
remote.session_createdto Loki - Returns the token along with
tailnet_ip,port: 2222,machine_name, andexpires_in: 300
The returned token is then passed as shell_token when opening the WebSocket at GET /api/remote-shell/ws. Without a successful create_session call there is no valid shell_token and the WebSocket handshake is rejected.
The frontend flow lives in RemoteTerminal.tsx (src/components/remote/RemoteTerminal.tsx): it calls create_session via api.functions.invoke("remote-shell", ...), stores the token, then calls buildWsUrl which constructs:
wss://login.quickztna.com/api/remote-shell/ws?token=JWT&org_id=ORG&machine_id=MACHINE&shell_token=TOKEN
Your Test Setup
| Machine | Role |
|---|---|
| ⊞ Win-A | API caller (browser dashboard or curl) |
| 🐧 Linux-C | Target machine for the shell session |
Prerequisites:
- The org must be on the Workforce plan (feature
remote_managementmust be enabled). - 🐧 Linux-C must be online (
status = 'online'in themachinestable). Confirm withztna statuson that machine. - The caller must be an org admin (owner or admin role).
- You need a valid JWT access token. Extract it from browser DevTools: Application tab, Local Storage, key
quickztna_session, fieldaccess_token.
ST1 — Successful Session Token Creation
What it verifies: create_session returns a well-formed token and connection details when the machine is online and the caller is an admin on a Workforce plan.
Steps:
- On ⊞ Win-A , run:
curl -s -X POST "https://login.quickztna.com/api/remote-shell" `
-H "Authorization: Bearer YOUR_TOKEN" `
-H "Content-Type: application/json" `
-d '{
"action": "create_session",
"org_id": "YOUR_ORG_ID",
"machine_id": "LINUX_C_MACHINE_ID"
}'
Expected response:
{
"success": true,
"data": {
"token": "UUID-UUID",
"tailnet_ip": "100.64.x.x",
"port": 2222,
"machine_name": "Linux-C",
"expires_in": 300
},
"error": null
}
- Verify the token format: it must be two UUIDs joined by a hyphen, e.g.
550e8400-e29b-41d4-a716-446655440000-f47ac10b-58cc-4372-a567-0e02b2c3d479. - Confirm
portis exactly2222andexpires_inis300.
Pass: HTTP 200, success: true, token present and double-UUID formatted, tailnet_ip matches the IP shown on the Machines page for Linux-C.
Fail / Common issues:
- HTTP 403 with code
FEATURE_NOT_AVAILABLE— the org is not on the Workforce plan. Upgrade via the Billing page. - HTTP 403 with code
FORBIDDEN— the caller is not an org admin. Check role on the Users page. - HTTP 404 with code
NOT_FOUND— wrongmachine_id. Fetch the correct UUID viaGET /api/db/machines?org_id=YOUR_ORG_ID.
ST2 — Rejection When Machine Is Offline
What it verifies: create_session returns MACHINE_OFFLINE when the target machine’s status is not online.
Steps:
- On 🐧 Linux-C , stop the VPN client:
ztna down
- Wait 90 seconds for the heartbeat timeout to mark the machine offline (or set it to offline directly via
machine-adminif you need immediate results). - On ⊞ Win-A , attempt session creation again:
curl -s -X POST "https://login.quickztna.com/api/remote-shell" `
-H "Authorization: Bearer YOUR_TOKEN" `
-H "Content-Type: application/json" `
-d '{
"action": "create_session",
"org_id": "YOUR_ORG_ID",
"machine_id": "LINUX_C_MACHINE_ID"
}'
Expected response:
{
"success": false,
"data": null,
"error": {
"code": "MACHINE_OFFLINE",
"message": "Machine must be online for remote shell"
}
}
- Bring Linux-C back online:
ztna up
Pass: HTTP 400, success: false, error code MACHINE_OFFLINE. After ztna up the same request succeeds.
Fail / Common issues:
- Still getting a token while machine is offline — the heartbeat window may not have elapsed. Wait the full 90 seconds or force-set
status = 'offline'via machine-admin for an immediate test.
ST3 — Rejection Without Authentication
What it verifies: Requests without a valid JWT are rejected before any feature gate or machine check.
Steps:
- On ⊞ Win-A , send the request with no Authorization header:
curl -s -X POST "https://login.quickztna.com/api/remote-shell" `
-H "Content-Type: application/json" `
-d '{
"action": "create_session",
"org_id": "YOUR_ORG_ID",
"machine_id": "LINUX_C_MACHINE_ID"
}'
Expected:
{
"success": false,
"data": null,
"error": {
"code": "UNAUTHORIZED",
"message": "..."
}
}
- Send with an expired or malformed token:
curl -s -X POST "https://login.quickztna.com/api/remote-shell" `
-H "Authorization: Bearer invalid.token.here" `
-H "Content-Type: application/json" `
-d '{
"action": "create_session",
"org_id": "YOUR_ORG_ID",
"machine_id": "LINUX_C_MACHINE_ID"
}'
Expected: Same 401 response.
Pass: Both cases return HTTP 401 with UNAUTHORIZED. No token is issued.
Fail / Common issues:
- Getting 400 instead of 401 — the request body may be missing
action. The handler validates auth before the action switch, so auth failures always return 401 first.
ST4 — Missing Required Fields Validation
What it verifies: The handler rejects requests missing action, org_id, or machine_id with clear error codes.
Steps:
- Missing
action:
curl -s -X POST "https://login.quickztna.com/api/remote-shell" `
-H "Authorization: Bearer YOUR_TOKEN" `
-H "Content-Type: application/json" `
-d '{"org_id": "YOUR_ORG_ID", "machine_id": "LINUX_C_MACHINE_ID"}'
Expected: HTTP 400, code MISSING_FIELDS, message "action required".
- Missing
org_id:
curl -s -X POST "https://login.quickztna.com/api/remote-shell" `
-H "Authorization: Bearer YOUR_TOKEN" `
-H "Content-Type: application/json" `
-d '{"action": "create_session", "machine_id": "LINUX_C_MACHINE_ID"}'
Expected: HTTP 400, code MISSING_FIELDS, message "org_id required".
- Missing
machine_id:
curl -s -X POST "https://login.quickztna.com/api/remote-shell" `
-H "Authorization: Bearer YOUR_TOKEN" `
-H "Content-Type: application/json" `
-d '{"action": "create_session", "org_id": "YOUR_ORG_ID"}'
Expected: HTTP 400, code MISSING_FIELDS, message "machine_id required".
- Invalid JSON:
curl -s -X POST "https://login.quickztna.com/api/remote-shell" `
-H "Authorization: Bearer YOUR_TOKEN" `
-H "Content-Type: application/json" `
-d 'not-json'
Expected: HTTP 400, code INVALID_JSON.
Pass: Each missing field produces a 400 with the correct error code and a message that identifies the missing field.
Fail / Common issues:
- Getting 401 even with a valid token — the token may have expired (24-hour lifetime). Refresh via the login page or browser auto-refresh.
ST5 — Audit Log Entry Written on Session Creation
What it verifies: A successful create_session call writes a remote.session_created audit event to Loki including the machine name and tailnet IP.
Steps:
-
On ⊞ Win-A , perform a successful
create_sessioncall (as in ST1). Note the current timestamp. -
Navigate to the dashboard: Audit Log page (
/audit-log). -
Filter by action type or search for
remote.session_created. -
Confirm an entry exists with:
action: "remote.session_created"resource_type: "machine"resource_idmatching the Linux-C machine UUIDactor_idmatching your user UUIDmetadata.machine_name: "Linux-C"metadata.tailnet_ipmatching the tailnet IP shown in the API response
-
Alternatively, query Loki directly from the monitoring VM:
ssh root@188.166.155.128
logcli query '{job="quickztna"} |= "remote.session_created"' --limit=5
Pass: The audit entry is present within a few seconds of the API call, with correct resource_id, actor_id, and metadata fields.
Fail / Common issues:
- Entry missing — Loki ingestion is async. Wait 10-15 seconds and refresh. If still absent, check Loki health at
http://188.166.155.128:3000. metadata.tailnet_ipis null — the machine was registered without a tailnet IP. This should have been caught by theNO_TAILNET_IPguard, so a missing tailnet IP in the audit entry means the guard was bypassed. Check themachinestable for thetailnet_ipcolumn value.
Summary
| Sub-test | What it proves |
|---|---|
| ST1 | Successful create_session returns a double-UUID token, tailnet IP, port 2222, and expires_in 300 |
| ST2 | Offline machines are rejected with MACHINE_OFFLINE before any token is issued |
| ST3 | Missing or invalid JWT returns 401 UNAUTHORIZED before reaching any business logic |
| ST4 | Missing action, org_id, or machine_id returns 400 MISSING_FIELDS with an accurate message |
| ST5 | Successful session creation writes a remote.session_created entry to Loki with correct metadata |