What We’re Testing
ACL rules are validated at two levels:
-
CRUD layer (
db-crud.ts) — enforces writable columns via a whitelist. Foracl_rules, the allowed columns are:name,source,destination,ports,protocol,action,priority,enabled,updated_at,created_by,expires_at,time_start,time_end. Any column not in this list is silently dropped. -
Dry-run validation (
POST /api/acl-validate, handler:acl-validate.ts) — acceptsproposed_rulesand validates syntax without applying changes. It checks:sourceis present (required)destinationis present (required)actionis"allow"or"deny"(required)priorityis a non-negative number (if provided)
-
Database constraints — the
acl_rulestable has a CHECK constraint:action IN ('allow', 'deny'). Thenamecolumn isNOT NULL. Theorg_idcolumn isNOT NULLwith a foreign key toorganizations.
The acl-validate endpoint also performs impact analysis: it simulates the proposed rules against all machine pairs and reports which connections would change from allow-to-deny or deny-to-allow.
Your Test Setup
| Machine | Role |
|---|---|
| ⊞ Win-A | Browser + curl for validation tests |
All tests use the API directly since validation is a backend concern.
ST1 — Invalid Port Range
What it verifies: The evaluation engine gracefully handles malformed port specifications in rules, and the port parser skips invalid entries.
Steps:
- Create a rule with an invalid port range (start greater than end):
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": "Bad-Port-Range",
"source": "*",
"destination": "*",
"ports": "9000-8000",
"protocol": "tcp",
"action": "allow",
"priority": 200
}'
The CRUD layer will accept this (it does not validate port syntax). The rule is stored in the database.
- Evaluate traffic on port 8500 against this rule:
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": "SOURCE_ID",
"destination_machine_id": "DEST_ID",
"port": 8500,
"protocol": "tcp"
}'
Expected: The port parser in acl-evaluate.ts (line 250-252) checks start > end and skips the entry with continue. So port 8500 will NOT match this rule. The evaluation falls through to the next rule or default deny.
- Test with out-of-range ports (above 65535):
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": "Out-Of-Range-Port",
"source": "*",
"destination": "*",
"ports": "70000",
"protocol": "tcp",
"action": "allow",
"priority": 201
}'
- Evaluate on port 70000:
Expected: The port parser checks p >= 1 && p <= 65535 (line 258) and skips invalid ports. The rule will never match any traffic.
- Test with non-numeric port value:
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": "Non-Numeric-Port",
"source": "*",
"destination": "*",
"ports": "http",
"protocol": "tcp",
"action": "allow",
"priority": 202
}'
Expected: The rule is stored (CRUD does not validate port syntax), but parseInt("http") returns NaN, which fails the !isNaN(p) check. The rule never matches.
Pass: Rules with invalid port specifications are stored but never match traffic. The evaluation engine does not crash on malformed ports.
Fail / Common issues:
- If the rule unexpectedly matches, verify no other wildcard rule with ports
*exists at a higher priority.
ST2 — Malformed Source/Destination Selectors
What it verifies: Rules with malformed CIDR notation or invalid selectors are stored but do not match any machines during evaluation.
Steps:
- Create a rule with a malformed CIDR:
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": "Bad-CIDR",
"source": "999.999.999.0/24",
"destination": "*",
"ports": "*",
"protocol": "*",
"action": "allow",
"priority": 300
}'
- Evaluate:
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": "SOURCE_ID",
"destination_machine_id": "DEST_ID"
}'
Expected: The rule does not match. The CIDR matcher in acl-evaluate.ts converts IP octets with parseInt and checks octet < 0 || octet > 255 (line 239). An IP like 999.999.999.0 returns null from ipToNumber(), causing ipInCidr() to return false.
- Test with an invalid prefix length:
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": "Bad-Prefix",
"source": "100.64.0.0/99",
"destination": "*",
"ports": "*",
"protocol": "*",
"action": "allow",
"priority": 301
}'
Expected: The prefix check prefix < 0 || prefix > 32 (line 224) rejects it. The rule never matches.
- Test with an unresolvable selector (not a UUID, not an IP, not a tag/user/group prefix, not a CIDR):
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": "Unknown-Selector",
"source": "not-a-valid-selector",
"destination": "*",
"ports": "*",
"protocol": "*",
"action": "allow",
"priority": 302
}'
Expected: The matchesTarget() function checks each selector pattern in order (*, machine ID, IP, tag:, user:, group:, CIDR) and returns false if none match. The rule is stored but inert.
Pass: Malformed CIDRs, invalid prefixes, and unrecognized selectors are accepted by the CRUD layer but never match during evaluation. No server errors or crashes.
Fail / Common issues:
- If the evaluation endpoint returns a 500 error, that would indicate a bug in the parser. Report it. The current implementation handles all edge cases gracefully.
ST3 — Missing Required Fields (CRUD Insert)
What it verifies: The CRUD endpoint and database constraints reject ACL rules that are missing required fields.
Steps:
- Missing
name(NOT NULL in the database):
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",
"source": "*",
"destination": "*",
"action": "allow"
}'
Expected: 500 or 400 error — PostgreSQL rejects the insert because name is NOT NULL.
- Missing
org_id:
curl -s -X POST "https://login.quickztna.com/api/db/acl_rules" `
-H "Authorization: Bearer YOUR_TOKEN" `
-H "Content-Type: application/json" `
-d '{
"name": "No-Org",
"source": "*",
"destination": "*",
"action": "allow"
}'
Expected: 400 error — the CRUD handler requires org_id for org-scoped tables.
- Missing
sourceanddestination(NOT NULL):
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": "No-Source-Dest",
"action": "allow"
}'
Expected: Database error — source and destination are NOT NULL.
- Test the dry-run validation endpoint with missing fields:
curl -s -X POST "https://login.quickztna.com/api/acl-validate" `
-H "Authorization: Bearer YOUR_TOKEN" `
-H "Content-Type: application/json" `
-d '{
"org_id": "YOUR_ORG_ID",
"proposed_rules": [
{ "destination": "*", "ports": "*", "protocol": "*", "action": "allow", "priority": 100 }
]
}'
Expected: 400 error with code VALIDATION_ERROR, details array includes "Rule 0: missing source".
- Test with invalid action value:
curl -s -X POST "https://login.quickztna.com/api/acl-validate" `
-H "Authorization: Bearer YOUR_TOKEN" `
-H "Content-Type: application/json" `
-d '{
"org_id": "YOUR_ORG_ID",
"proposed_rules": [
{ "source": "*", "destination": "*", "ports": "*", "protocol": "*", "action": "block", "priority": 100 }
]
}'
Expected: 400 error with VALIDATION_ERROR, details: "Rule 0: action must be 'allow' or 'deny'".
Pass: Missing name, org_id, source, or destination are rejected. The validate endpoint catches missing source, missing destination, and invalid action values with specific error messages.
Fail / Common issues:
- If missing
namereturns 200 with a null name, the database is not enforcing NOT NULL. Check the migration has been applied. - The CRUD layer silently drops unknown columns but does NOT validate required columns — that is the database’s job.
ST4 — Invalid Protocol Value
What it verifies: Rules with non-standard protocol values are stored but only match if the evaluation request uses the exact same protocol string.
Steps:
- Create a rule with an invalid protocol:
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": "Invalid-Protocol",
"source": "*",
"destination": "*",
"ports": "*",
"protocol": "xyz",
"action": "allow",
"priority": 400
}'
Expected: The CRUD layer accepts this — there is no CHECK constraint on the protocol column in the database (unlike action). The rule is stored with protocol: "xyz".
- Evaluate with protocol
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": "SOURCE_ID",
"destination_machine_id": "DEST_ID",
"port": 80,
"protocol": "tcp"
}'
Expected: The rule does NOT match. Line 116 of acl-evaluate.ts: if (rule.protocol !== '*' && rule.protocol !== requestProtocol) continue; — "xyz" !== "tcp", so it skips.
- Test the database constraint on
action:
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": "Invalid-Action",
"source": "*",
"destination": "*",
"ports": "*",
"protocol": "tcp",
"action": "block",
"priority": 401
}'
Expected: Database error — the CHECK constraint action IN ('allow', 'deny') rejects "block".
- Test valid protocol values. The evaluation engine recognizes these protocol strings:
"tcp"— matches only TCP traffic"udp"— matches only UDP traffic"icmp"— matches only ICMP traffic"*"— matches all protocols (wildcard)
Pass: Invalid protocol strings are stored but never match standard traffic. Invalid action values are rejected by the database. Valid protocols (tcp, udp, icmp, *) work as expected.
Fail / Common issues:
- If
action: "block"is accepted, the CHECK constraint may not exist. Verify via the database or check that migration001_initial.sqlhas been applied.
ST5 — Duplicate Rule Detection (Validate Endpoint)
What it verifies: The POST /api/acl-validate endpoint can detect when proposed rules would conflict with or duplicate existing rules, through its impact analysis.
Steps:
-
Ensure an existing allow rule: source
*, destination*, ports443, protocoltcp, priority 100. -
Propose a duplicate rule via the validate endpoint:
curl -s -X POST "https://login.quickztna.com/api/acl-validate" `
-H "Authorization: Bearer YOUR_TOKEN" `
-H "Content-Type: application/json" `
-d '{
"org_id": "YOUR_ORG_ID",
"proposed_rules": [
{
"source": "*",
"destination": "*",
"ports": "443",
"protocol": "tcp",
"action": "allow",
"priority": 100,
"name": "Duplicate-Allow"
}
]
}'
Expected response:
{
"success": true,
"data": {
"impact": [ ... ],
"summary": {
"total_machines": N,
"connections_tested": N,
"changes": 0,
"newly_allowed": 0,
"newly_denied": 0
},
"proposed_rules_count": 1,
"current_rules_count": N
}
}
The changes: 0 indicates the proposed rule does not change any traffic decisions — it is effectively a duplicate.
- Now propose a conflicting rule (deny instead of allow at higher priority):
curl -s -X POST "https://login.quickztna.com/api/acl-validate" `
-H "Authorization: Bearer YOUR_TOKEN" `
-H "Content-Type: application/json" `
-d '{
"org_id": "YOUR_ORG_ID",
"proposed_rules": [
{
"source": "*",
"destination": "*",
"ports": "443",
"protocol": "tcp",
"action": "deny",
"priority": 5,
"name": "Override-Deny"
}
]
}'
Expected: changes is greater than 0, newly_denied is greater than 0. The impact array shows individual connection pairs that would flip from allow to deny, each with changed: true.
- Examine the impact details:
{
"impact": [
{
"source": { "id": "...", "name": "Linux-C", "ip": "100.64.x.x" },
"destination": { "id": "...", "name": "Win-B", "ip": "100.64.x.x" },
"port": 0,
"protocol": "tcp",
"current": { "decision": "allow", "rule": { "name": "Allow-HTTPS" } },
"proposed": { "decision": "deny", "rule": { "name": "Override-Deny" } },
"changed": true
}
]
}
- Verify that the validate endpoint requires admin access:
curl -s -X POST "https://login.quickztna.com/api/acl-validate" `
-H "Authorization: Bearer MEMBER_TOKEN" `
-H "Content-Type: application/json" `
-d '{ "org_id": "YOUR_ORG_ID" }'
Expected: 403 error with code FORBIDDEN, message "Admin access required".
Pass: Duplicate rules show changes: 0. Conflicting rules show the correct number of newly denied/allowed connections. The impact array details which connections change. Non-admin users are rejected.
Fail / Common issues:
connections_tested: 0— no online machines exist in the organization. The auto-generated test pairs only include machines with statusonline.- Empty
impactarray — if you specifytest_connectionsin the request body, you can manually define which machine pairs to test.
Summary
| Sub-test | What it proves |
|---|---|
| ST1 | Invalid port ranges/values are stored but never match traffic during evaluation |
| ST2 | Malformed CIDRs and unrecognized selectors do not crash the engine — they simply never match |
| ST3 | Missing required fields (name, org_id, source, destination) are rejected by DB constraints or the validate endpoint |
| ST4 | Invalid protocol strings are inert; invalid action values are rejected by the DB CHECK constraint |
| ST5 | The validate endpoint detects duplicate/conflicting rules via impact analysis and requires admin access |