Sign in Get API keys
User guide

Writing ACL policies

QuickZTNA ACLs are attribute-based — rules match on user, tag, device posture, time, country, protocol, and port. This guide walks through writing rules, testing them before rollout, versioning, and AI-generated policies.

The ACL model

A rule has src (who's allowed), dst (what they can reach), and optional conditions. A connection is allowed iff at least one rule matches.

{
  "name": "engineers-to-prod-db",
  "src":  "tag:engineer",
  "dst":  "tag:prod-db",
  "proto": "tcp",
  "ports": "5432"
}

Matcher syntax

Both src and dst accept:

tag:<name>

Any machine with this tag. Preferred — scales as fleet grows.

user:<email>

Specific user. E.g. user:alice@acme.com.

group:<idp-group>

IdP group name (case-insensitive). Resolved from OIDC claims or SCIM sync.

<tailnet-ip>

Specific machine by tailnet IP. Fragile — prefer tags.

<CIDR>

Subnet range. Use for advertised subnet routes.

*

Any — use sparingly, only for well-scoped admin rules.

Conditions

prototcp / udp / icmp / *

Transport protocol. Default *.

portsstring

Comma-separated list or ranges: "22,443,5000-5010". Default *.

time_hour"HH-HH"

UTC hour range: "09-18". Applies at connection evaluation time.

day_of_week"mon-fri"

Day range or specific list: "mon,wed,fri".

source_countryISO-3166-1

GeoIP-resolved country: "IN", "US", or list "IN,US,GB".

source_oslinux / darwin / windows

Match by source machine OS.

require_posturebool

Source machine must be in compliant posture state.

Examples

Engineers SSH to prod (business hours, India only)

{
  "name": "eng-ssh-prod-business-hours",
  "src": "tag:engineer",
  "dst": "tag:prod",
  "proto": "tcp",
  "ports": "22",
  "time_hour": "09-18",
  "day_of_week": "mon-fri",
  "source_country": "IN",
  "require_posture": true
}

DB admins to Postgres on multiple ports

{
  "name": "dba-full-db-access",
  "src": "group:dba",
  "dst": "tag:prod-db",
  "proto": "tcp",
  "ports": "5432,5433,6432,9990-9999"
}

CI runners to any test machine (no production)

{
  "name": "ci-to-test",
  "src": "tag:ci",
  "dst": "tag:test",
  "proto": "*",
  "ports": "*"
}

AI-generated rules (natural language)

Type in English — get ACL JSON. Available free on all plans via /api/ai-assist action generate_acl.

$ curl https://login.quickztna.com/api/ai-assist \
    -H "Authorization: Bearer $TOKEN" \
    -d '{
      "action": "generate_acl",
      "org_id": "org_9fX2kR",
      "description": "Finance team can access the accounting server during work hours from the office IP range"
    }'
{
  "rule": {
    "name": "finance-accounting-office-hours",
    "src": "group:finance",
    "dst": "tag:accounting",
    "proto": "tcp",
    "ports": "443",
    "time_hour": "09-18",
    "day_of_week": "mon-fri",
    "source_country": "US"
  }
}

Review the generated rule, adjust if needed, and apply.

Test before rolling out

Simulate, don't guess Always test a new rule against a real src/dst/port combination before saving it. The /api/acl-evaluate endpoint (ztna acl test) is free and instant.
$ ztna acl test --src laptop-alex --dst db-primary --port 5432 --proto tcp
✓ ALLOW

Matched rule: acl_7K9xq (eng-ssh-prod-business-hours)
  src:            tag:laptop matches tag:engineer ✓
  dst:            tag:prod matches ✓
  port 5432:      in "5432" ✓
  time:           14:23 UTC (Thursday) ✓
  source_country: IN ✓
$ ztna acl test --src laptop-alex --dst db-primary --port 3306
✗ DENY
  No rule allows laptop-alex → db-primary on port 3306.
  Closest match: acl_7K9xq (allows port 5432 only).

Apply a rule

ACLs → New rule → paste JSON → "Test" → "Save".
$ curl https://login.quickztna.com/api/db/acl_rules \
    -H "Authorization: Bearer $QZ_API_KEY" \
    -d '{
      "rows": [{ /* rule JSON */ }],
      "org_id": "org_9fX2kR"
    }'
resource "quickztna_acl_rule" "eng_ssh_prod" {
  org_id         = "org_9fX2kR"
  name           = "eng-ssh-prod-business-hours"
  src            = "tag:engineer"
  dst            = "tag:prod"
  proto          = "tcp"
  ports          = "22"
  time_hour      = "09-18"
  day_of_week    = "mon-fri"
  source_country = "IN"
}

Versioning + rollback

Every change to the ACL set creates a new version via /api/governance action policy_version_create. Roll back at any time with policy_version_rollback.

$ curl https://login.quickztna.com/api/governance \
    -H "Authorization: Bearer $QZ_API_KEY" \
    -d '{
      "action": "policy_version_rollback",
      "org_id": "org_9fX2kR",
      "target_version": 42
    }'

JIT overrides

If a user needs time-bounded access outside normal ACL rules, they request JIT. Approver grants → temporary ACL rule is injected → auto-expires at the end of the window. See API: jit_request.

Common pitfalls

Default-allow vs default-deny

By default, no rules = no restrictions (full mesh connectivity). Set org setting default_deny to flip — then you must write explicit allow rules for everything.

Tag typos

ACL engine doesn't warn if you reference tag:prood (typo). Rule just never matches. Use ztna acl test to verify.

Time zone confusion

time_hour is UTC. "9-5 IST business hours" is 03-12 UTC. Use AI-generated rules — it handles the conversion.

Posture condition

require_posture: true means the SOURCE must be compliant. A non-compliant laptop can still be a destination, unless you add the condition to another rule.

See also