What We’re Testing
The ACL evaluation endpoint (POST /api/acl-evaluate, handler: acl-evaluate.ts) is the core decision engine that determines whether traffic between two machines is allowed or denied. It accepts these fields:
{
"org_id": "UUID (required)",
"source_machine_id": "UUID (required)",
"destination_machine_id": "UUID (required)",
"port": number (optional, defaults to 0),
"protocol": "string (optional, defaults to 'tcp')"
}
The evaluation pipeline (in order):
- Authentication — via JWT token or
node_key(machine-to-machine) - Machine lookup — fetches source and destination from the
machinestable - Status check — machines with status
quarantinedorpendingare auto-denied - Lockdown check — organizations in
lockdown_modedeny all traffic - ACL rule matching — iterates enabled rules ordered by priority ASC; first match wins
- Posture compliance — if allowed, checks the source machine’s latest posture report
- ABAC policies — evaluates attribute-based conditions (time, OS, tags, country)
- Threat intelligence — checks for recent threat blocks on the source machine
The response always includes:
allowed(boolean),decision(“allow” or “deny”)matched_rule(the rule or policy that determined the outcome)posture_compliant(boolean),threat_blocked(boolean)sourceanddestinationobjects withid,name, andip
The CLI equivalent is ztna acl test --src NAME --dst NAME --port PORT --proto PROTO, which resolves machine names to IDs and calls the same endpoint.
Your Test Setup
| Machine | Role |
|---|---|
| ⊞ Win-A | API caller (browser or curl) |
| ⊞ Win-B | Destination machine in evaluations |
| 🐧 Linux-C | Source machine in evaluations |
Prerequisite: Note down the machine UUIDs for Win-B and Linux-C. You can find them via GET /api/db/machines?org_id=YOUR_ORG_ID or on the Machines page in the dashboard.
ST1 — Evaluate Allowed Traffic
What it verifies: The evaluation endpoint correctly returns allowed: true when an allow rule matches the source, destination, port, and protocol.
Prerequisites: An allow rule exists: source *, destination *, ports 443, protocol tcp, action allow, priority 100.
Steps:
- On ⊞ Win-A , run:
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:
{
"success": true,
"data": {
"allowed": true,
"decision": "allow",
"matched_rule": {
"id": "RULE_UUID",
"name": "Allow-All-HTTP",
"action": "allow",
"priority": 100
},
"posture_compliant": true,
"threat_blocked": false,
"source": {
"id": "LINUX_C_MACHINE_ID",
"name": "Linux-C",
"ip": "100.64.x.x"
},
"destination": {
"id": "WIN_B_MACHINE_ID",
"name": "Win-B",
"ip": "100.64.x.x"
}
},
"error": null
}
- Verify the same result via CLI on 🐧 Linux-C :
ztna acl test --src Linux-C --dst Win-B --port 443 --proto tcp
Expected:
ALLOWED: Linux-C -> Win-B:443/tcp
Matched rule: Allow-All-HTTP
Pass: allowed: true, decision: "allow", matched rule name and priority are correct. CLI output confirms the allow decision.
Fail / Common issues:
allowed: falsewithmatched_rule: null— no allow rule matches. The default is deny (zero-trust). Create an appropriate allow rule.matched_rule.nameis"Machine \"Linux-C\" is pending"— the source machine’s status is notonline. Approve it from the Machines page.
ST2 — Evaluate Denied Traffic
What it verifies: The evaluation endpoint returns allowed: false when a deny rule matches, or when no rule matches at all (default deny).
Steps:
- Test with a port that has no matching allow rule (assuming rules only allow specific ports):
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": 9999,
"protocol": "tcp"
}'
Expected (no rule matches — default deny):
{
"success": true,
"data": {
"allowed": false,
"decision": "deny",
"matched_rule": null,
"posture_compliant": true,
"threat_blocked": false
}
}
Note: when no rule matches, matched_rule is null and the default deny applies.
- Test with an explicit deny rule (create one if needed with priority 10, ports
*):
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": 80,
"protocol": "tcp"
}'
Expected (explicit deny rule matches):
{
"data": {
"allowed": false,
"decision": "deny",
"matched_rule": {
"id": "...",
"name": "Deny-HTTP-Override",
"action": "deny",
"priority": 10
}
}
}
- CLI equivalent:
ztna acl test --src Linux-C --dst Win-B --port 9999 --proto tcp
Expected: DENIED: Linux-C -> Win-B:9999/tcp
Pass: Default deny returns allowed: false with matched_rule: null. Explicit deny rule returns allowed: false with the correct rule details.
Fail / Common issues:
- Unexpectedly allowed — a wildcard allow rule (ports
*, source*, destination*) may exist. Checkztna acl listfor overly permissive rules.
ST3 — Port-Specific Evaluation
What it verifies: The evaluation engine correctly matches port specifications including single ports, comma-separated lists, and port ranges.
Steps:
- Create a rule with a port range:
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-HighPorts",
"source": "*",
"destination": "*",
"ports": "8000-8100",
"protocol": "tcp",
"action": "allow",
"priority": 50
}'
-
Delete or disable any wildcard allow rules that might match first.
-
Evaluate a port inside the range:
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": 8080,
"protocol": "tcp"
}'
Expected: allowed: true, matched rule is Allow-HighPorts.
- Evaluate a port outside the range:
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": 9000,
"protocol": "tcp"
}'
Expected: allowed: false (port 9000 is not in range 8000-8100).
-
Test comma-separated ports. Create a rule with
ports: "22,80,443"and verify:- Port 80: allowed
- Port 81: denied
-
Test port
0(the default when no port is specified). Whenportis 0 or omitted, the evaluation skips port matching entirely — the rule matches regardless of itsportsfield (seeacl-evaluate.tsline 110:if (rule.ports !== '*' && requestPort > 0)).
Important note on port ranges: The engine caps range expansion to 1000 ports per range segment to prevent denial-of-service. A range like 1-65535 will only expand to ports 1-1000. This is by design (acl-evaluate.ts line 254: const cappedEnd = Math.min(end, start + 999)).
Pass: Ports inside the specified range/list are allowed. Ports outside are denied. Port 0 skips port matching.
Fail / Common issues:
- Port range not working — ensure the format is
START-ENDwith no spaces (e.g.,8000-8100, not8000 - 8100). - Large ranges only matching first 1000 ports — this is the intentional cap to prevent DoS.
ST4 — Protocol Wildcard Evaluation
What it verifies: Rules with protocol "*" match traffic regardless of the requested protocol, while protocol-specific rules only match their protocol.
Steps:
- Create a rule with protocol wildcard:
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-All-Protocols",
"source": "*",
"destination": "*",
"ports": "53",
"protocol": "*",
"action": "allow",
"priority": 40
}'
- Evaluate with TCP:
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": 53,
"protocol": "tcp"
}'
Expected: allowed: true, matched rule Allow-All-Protocols.
- Evaluate with UDP:
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": 53,
"protocol": "udp"
}'
Expected: allowed: true, same matched rule.
- Evaluate with ICMP:
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": 53,
"protocol": "icmp"
}'
Expected: allowed: true, same matched rule (protocol "*" matches everything).
- Now test with the CLI. The
--protoflag defaults to*if not specified:
ztna acl test --src Linux-C --dst Win-B --port 53 --proto udp
Expected: ALLOWED
- Create a TCP-only rule and verify it does NOT match UDP:
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-TCP-Only",
"source": "*",
"destination": "*",
"ports": "5432",
"protocol": "tcp",
"action": "allow",
"priority": 45
}'
Evaluate with UDP on port 5432 (no other matching rules):
Expected: allowed: false — the TCP rule does not match UDP traffic.
Pass: Protocol "*" matches all protocols (tcp, udp, icmp). Protocol-specific rules only match their exact protocol.
Fail / Common issues:
- If the protocol field is omitted from the evaluation request, it defaults to
"tcp"(line 73:const requestProtocol = protocol || 'tcp'). - The CLI
--protoflag defaults to*(all protocols) when not specified.
ST5 — Missing Fields Validation
What it verifies: The evaluation endpoint returns clear error responses when required fields are missing or the request is malformed.
Steps:
- Missing
org_id:
curl -s -X POST "https://login.quickztna.com/api/acl-evaluate" `
-H "Authorization: Bearer YOUR_TOKEN" `
-H "Content-Type: application/json" `
-d '{
"source_machine_id": "LINUX_C_MACHINE_ID",
"destination_machine_id": "WIN_B_MACHINE_ID"
}'
Expected: 400 status, error code MISSING_FIELDS, message: "org_id, source_machine_id, destination_machine_id required".
- Missing
source_machine_id:
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",
"destination_machine_id": "WIN_B_MACHINE_ID"
}'
Expected: Same 400 error with MISSING_FIELDS.
- Missing
destination_machine_id:
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"
}'
Expected: Same 400 error with MISSING_FIELDS.
- Invalid JSON body:
curl -s -X POST "https://login.quickztna.com/api/acl-evaluate" `
-H "Authorization: Bearer YOUR_TOKEN" `
-H "Content-Type: application/json" `
-d 'not-json'
Expected: 400 status, error code INVALID_JSON, message: "Request body must be valid JSON".
- Non-existent machine ID:
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": "00000000-0000-0000-0000-000000000000",
"destination_machine_id": "WIN_B_MACHINE_ID"
}'
Expected: 404 status, error code NOT_FOUND, message: "Source or destination machine not found".
- No authorization header:
curl -s -X POST "https://login.quickztna.com/api/acl-evaluate" `
-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"
}'
Expected: 401 status, error code UNAUTHORIZED.
Pass: Each missing/invalid field produces the correct HTTP status code and error code. The error messages clearly indicate what is wrong.
Fail / Common issues:
- Getting 200 instead of 400 — the endpoint wraps errors in the standard envelope. Check the
successfield: it should befalsefor validation errors. - Getting 401 when token is provided — the JWT may have expired. Refresh it from the browser’s local storage.
Summary
| Sub-test | What it proves |
|---|---|
| ST1 | Evaluation API returns allowed: true with correct matched rule for allowed traffic |
| ST2 | Evaluation returns allowed: false for both default deny (no match) and explicit deny rules |
| ST3 | Port specifications (ranges, comma-separated, wildcard) are evaluated correctly |
| ST4 | Protocol wildcard "*" matches all protocols; specific protocols only match their own |
| ST5 | Missing/invalid fields produce correct error codes: MISSING_FIELDS, INVALID_JSON, NOT_FOUND, UNAUTHORIZED |