What We’re Testing
ACL rules control which machines can communicate with each other across your QuickZTNA mesh network. By default, the system follows a zero-trust model — all traffic is denied unless an explicit allow rule matches (see acl-evaluate.ts, line 92: let decision: 'allow' | 'deny' = 'deny').
Rules are stored in the acl_rules table with these key columns:
- name (TEXT, required) — human-readable label
- source (TEXT, required) — machine ID, tailnet IP,
tag:name,user:id,group:name, CIDR, or*(any) - destination (TEXT, required) — same selector format as source
- ports (TEXT, default
*) — comma-separated ports or ranges (e.g.,80,443,8000-9000), or*for all ports - protocol (TEXT, default
tcp) —tcp,udp,icmp, or*for any protocol - action (TEXT, default
allow) —allowordeny - priority (INTEGER, default 100) — lower number = higher priority; first match wins
- enabled (BOOLEAN, default true) — disabled rules are skipped during evaluation
ACL rules are created via:
- The dashboard at
https://login.quickztna.com/acls— usesPOST /api/db/acl_rules(generic CRUD) - The REST API directly — same CRUD endpoint
- The CLI —
ztna acl listreads rules; creation is dashboard/API only
The evaluation engine (POST /api/acl-evaluate) iterates rules in priority order (ASC) and returns the first match. If no rule matches, traffic is denied.
Your Test Setup
| Machine | Role |
|---|---|
| ⊞ Win-A | Browser for dashboard tests + curl for API tests |
| ⊞ Win-B | Target machine (destination for allow rules) |
| 🐧 Linux-C | Source machine for ztna ping verification |
Ensure all three machines are online (ztna status shows connected) and belong to the same organization.
ST1 — Create an Allow Rule via Dashboard
What it verifies: An admin can create an allow rule from the ACLs page, and it appears in the rule list with the correct fields.
Steps:
- On ⊞ Win-A , open
https://login.quickztna.com/aclsin a browser. Log in as an admin user. - Click the + Add Rule button (the Plus icon button).
- Fill in the rule form:
- Name:
Allow-ST1-Test - Source:
* - Destination:
* - Protocol:
tcp - Ports:
443 - Action:
allow(should be the default)
- Name:
- Click Save.
Expected behavior:
- A success toast: “Rule added”
- The rule appears in the list with:
- Name:
Allow-ST1-Test - Source:
*, Destination:* - Protocol: TCP, Ports:
443 - Action badge shows “allow” (default/blue badge)
- Priority: 100 (default)
- Enabled toggle is ON
- Name:
Pass: Rule created successfully and visible in the list with all fields matching the input.
Fail / Common issues:
- “Failed” toast — check browser console for the API error. Most likely missing org admin permissions.
- Rule not appearing — click the refresh button or reload the page. Check that
fetchRules()is being called after save.
ST2 — Create an Allow Rule via API
What it verifies: An allow rule can be created via the CRUD API endpoint, and the response contains the new rule ID.
Steps:
- On ⊞ Win-A , get your JWT token from the browser (Dev Tools, Application, Local Storage,
access_tokenkey). - Get your org ID from the URL bar (visible in dashboard requests) or from
ztna status. - Run:
curl -s -X POST "https://login.quickztna.com/api/db/acl_rules" `
-H "Authorization: Bearer YOUR_TOKEN" `
-H "Content-Type: application/json" `
-d '{
"org_id": "YOUR_ORG_ID",
"name": "Allow-SSH-LinuxC",
"source": "*",
"destination": "tag:server",
"ports": "22",
"protocol": "tcp",
"action": "allow",
"priority": 50
}'
Expected response:
{
"success": true,
"data": {
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"data": {
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
},
"error": null
}
Note: the insert response has a double-nested structure (data.data.id).
- Verify the rule exists by fetching the list:
curl -s "https://login.quickztna.com/api/db/acl_rules?org_id=YOUR_ORG_ID" `
-H "Authorization: Bearer YOUR_TOKEN" | python -m json.tool
Pass: Response returns success: true with a UUID for the new rule. The GET request shows the rule with all fields matching.
Fail / Common issues:
- 403 Forbidden — you must be an admin or owner of the organization. ACL rules are in
ADMIN_WRITE_TABLES. - 400 Bad Request — missing
org_idin the body. All org-scoped tables require it.
ST3 — Verify Allow Rule Permits Traffic (ztna ping)
What it verifies: After creating an allow rule, traffic between the matched source and destination is permitted.
Prerequisites: An allow rule from ST1 or ST2 must exist with source * and destination * (or matching the specific machines).
Steps:
- On 🐧 Linux-C , confirm the VPN tunnel is up:
ztna status
Should show Connected or Authenticated: true.
- Get the peer list to find ⊞ Win-B ‘s name:
ztna peers
- Ping ⊞ Win-B through the mesh:
ztna ping Win-B
Expected output:
Pong from Win-B (100.x.x.x): time=Xms
Pong from Win-B (100.x.x.x): time=Xms
- You can also test the evaluation API directly:
curl -s -X POST "https://login.quickztna.com/api/acl-evaluate" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"org_id": "YOUR_ORG_ID",
"source_machine_id": "LINUX_C_MACHINE_ID",
"destination_machine_id": "WIN_B_MACHINE_ID",
"port": 443,
"protocol": "tcp"
}'
Expected response includes:
{
"success": true,
"data": {
"allowed": true,
"decision": "allow",
"matched_rule": {
"id": "...",
"name": "Allow-ST1-Test",
"action": "allow",
"priority": 100
}
}
}
Pass: ztna ping succeeds. The evaluation API returns allowed: true with the correct matched rule.
Fail / Common issues:
ztna pingtimes out — check that both machines are online and the VPN tunnel is up on both sides.- Evaluation returns
allowed: false— check that the rule is enabled and the source/destination selectors match. Verify no higher-priority deny rule exists.
ST4 — List Rules via CLI (ztna acl list)
What it verifies: The ztna acl list command fetches and displays all ACL rules for the current organization.
Steps:
- On ⊞ Win-A (or any authenticated machine), run:
ztna acl list
Expected output:
ACL Rules (N):
[1] {
"id": "...",
"name": "Allow-SSH-LinuxC",
"source": "*",
"destination": "tag:server",
"ports": "22",
"protocol": "tcp",
"action": "allow",
"priority": 50,
"enabled": true
}
[2] {
"id": "...",
"name": "Allow-ST1-Test",
...
}
Rules are displayed as indented JSON, numbered sequentially. The count in the header (ACL Rules (N)) matches the number of rules listed.
- If no rules exist, the output is:
No ACL rules configured.
Add rules from the dashboard at https://login.quickztna.com/acls
Pass: All rules created in ST1 and ST2 appear in the list with correct fields. Rules are numbered and formatted as JSON.
Fail / Common issues:
- “not authenticated. Run ‘ztna login’ first” — the CLI session has expired. Run
ztna loginagain. - “error fetching ACLs” — network connectivity issue or the API is unreachable. Check
ztna status.
ST5 — Verify Rule Fields (Source Selectors)
What it verifies: The various source/destination selector formats work correctly in allow rules — wildcard, tag, machine ID, tailnet IP, CIDR, user, and group selectors.
Steps:
- Create rules with different selector types via the API. For each, call
POST /api/acl-evaluateto verify the match logic.
Test selector: tag:webserver
curl -s -X POST "https://login.quickztna.com/api/db/acl_rules" `
-H "Authorization: Bearer YOUR_TOKEN" `
-H "Content-Type: application/json" `
-d '{
"org_id": "YOUR_ORG_ID",
"name": "Allow-Tag-Test",
"source": "tag:webserver",
"destination": "*",
"ports": "80,443",
"protocol": "tcp",
"action": "allow",
"priority": 60
}'
- Evaluate with a source machine that has the
webservertag:
curl -s -X POST "https://login.quickztna.com/api/acl-evaluate" `
-H "Authorization: Bearer YOUR_TOKEN" `
-H "Content-Type: application/json" `
-d '{
"org_id": "YOUR_ORG_ID",
"source_machine_id": "TAGGED_MACHINE_ID",
"destination_machine_id": "ANY_MACHINE_ID",
"port": 80,
"protocol": "tcp"
}'
Expected: allowed: true, matched_rule.name is "Allow-Tag-Test".
- Evaluate with a source machine that does NOT have the
webservertag:
Expected: allowed: false (falls through to default deny, assuming no other allow rules match).
- Other selectors to test (same pattern — create rule, then evaluate):
*— matches any machine- Direct machine UUID — matches only that specific machine
- Tailnet IP (e.g.,
100.64.0.5) — matches by IP - CIDR (e.g.,
100.64.0.0/24) — matches machines in that subnet user:USER_UUID— matches machines owned by that usergroup:Engineering— matches machines in the named segmentation group
Pass: Each selector type correctly matches (or does not match) the expected machines. The evaluation API returns allowed: true only when the selector matches the source/destination machine.
Fail / Common issues:
- Tag selectors require the machine to have tags set (via
ztna set --tags webserveror the dashboard). Verify tags are actually stored on the machine record. - CIDR match uses IPv4 only. Ensure the machine’s
tailnet_ipfalls within the specified CIDR range. group:selectors require the machine to be a member of a segmentation group (configured under the Segmentation page).
Summary
| Sub-test | What it proves |
|---|---|
| ST1 | Allow rules can be created from the dashboard UI |
| ST2 | Allow rules can be created via the REST API with all fields |
| ST3 | Allow rules permit traffic — verified via ztna ping and the evaluation API |
| ST4 | ztna acl list displays all rules with correct formatting |
| ST5 | All selector types (wildcard, tag, machine ID, IP, CIDR, user, group) work in allow rules |