What We’re Testing
JIT grants are inherently time-bounded. This chapter tests the expiry and revocation lifecycle after a grant has been approved.
Expiry mechanics (set during jit_approve):
expires_atis computed asNOW() + requested_duration_hours * 3600000milliseconds.- The linked ACL rule in
acl_rulesalso hasexpires_atset to the same value. - The
enforce-key-expirycron task sweeps theacl_rulestable and disables rules whereexpires_at <= NOW(). This runs on a scheduled interval server-side — the handler itself does not enforce expiry at query time. - A grant remains
status = 'approved'injit_access_grantseven after expiry; the enforcement is via the ACL rule’senabledflag.
jit_revoke (org admin only):
- Sets
status = 'revoked'andrevoked_at = NOW()on the grant. - Sets
enabled = falseon allacl_ruleswherejit_grant_idmatches the grant ID. - Audit event:
jit.revoked.
jit_list filter: pass status: "approved" to see active grants, or omit for all statuses.
Governance metrics get_metrics returns jit_access.active_grants — the count of grants where status = 'approved' AND expires_at > NOW().
DB tables touched: jit_access_grants, acl_rules.
Your Test Setup
| Machine | Role |
|---|---|
| ⊞ Win-A | Admin PowerShell: approvals, revocations, verification |
| 🐧 Linux-C | Linux shell: verify ACL rule state via API; check metrics post-expiry |
Prerequisites: At least one approved JIT grant exists (from Chapter 67 — ST3, or create a fresh one). You need an admin JWT token.
ST1 — Verify Approved Grant and Linked ACL Rule
What it verifies: An approved grant has a correct expires_at and is linked to an ACL rule that is currently enabled: true.
Steps:
- On ⊞ Win-A , list approved grants:
curl -s -X POST "https://login.quickztna.com/api/governance" `
-H "Authorization: Bearer YOUR_ADMIN_TOKEN" `
-H "Content-Type: application/json" `
-d '{
"action": "jit_list",
"org_id": "YOUR_ORG_ID",
"status": "approved"
}'
Expected: One or more grants with status: "approved" and a non-null expires_at in the future.
-
For one grant, verify the
expires_atmatches the requested duration. If the grant was approved atTwithrequested_duration_hours: 2, thenexpires_atshould beT + 2 hours(within a few seconds of tolerance). -
Use the
acl_rule_idreturned during approval to inspect the linked rule:
curl -s "https://login.quickztna.com/api/db/acl_rules?org_id=YOUR_ORG_ID&id=eq.ACL_RULE_UUID" `
-H "Authorization: Bearer YOUR_ADMIN_TOKEN"
Expected: The rule has enabled: true, expires_at matching the grant’s expires_at, and jit_grant_id matching the grant’s id.
- On 🐧 Linux-C , verify the same rule via the API:
curl -s "https://login.quickztna.com/api/db/acl_rules?org_id=YOUR_ORG_ID&id=eq.ACL_RULE_UUID" \
-H "Authorization: Bearer YOUR_ADMIN_TOKEN"
Expected: Same result — enabled: true.
Pass: Grant shows status: "approved", expires_at matches approval_time + requested_duration_hours. Linked ACL rule is enabled: true with matching expires_at and jit_grant_id.
Fail / Common issues:
- No approved grants in the list — create a new JIT request and approve it (Chapter 67 — ST3) before running this sub-test.
expires_atis null on the ACL rule — the rule was created before theexpires_atcolumn was added toacl_rules(migration 002). The column should exist; if not, the migration did not run.
ST2 — Revoke a Grant Early (Manual Revocation)
What it verifies: jit_revoke immediately disables the linked ACL rule and transitions the grant to status: "revoked", without waiting for expiry.
Steps:
- On ⊞ Win-A , pick an approved grant and revoke it:
curl -s -X POST "https://login.quickztna.com/api/governance" `
-H "Authorization: Bearer YOUR_ADMIN_TOKEN" `
-H "Content-Type: application/json" `
-d '{
"action": "jit_revoke",
"org_id": "YOUR_ORG_ID",
"grant_id": "APPROVED_GRANT_UUID"
}'
Expected response (HTTP 200):
{
"success": true,
"data": {
"grant_id": "APPROVED_GRANT_UUID",
"status": "revoked"
},
"error": null
}
- Verify the grant status:
curl -s -X POST "https://login.quickztna.com/api/governance" `
-H "Authorization: Bearer YOUR_ADMIN_TOKEN" `
-H "Content-Type: application/json" `
-d '{
"action": "jit_list",
"org_id": "YOUR_ORG_ID",
"status": "revoked"
}'
Expected: The grant appears with status: "revoked" and a non-null revoked_at timestamp.
- Verify the ACL rule is now disabled:
curl -s "https://login.quickztna.com/api/db/acl_rules?org_id=YOUR_ORG_ID&id=eq.ACL_RULE_UUID" `
-H "Authorization: Bearer YOUR_ADMIN_TOKEN"
Expected: enabled: false.
- On 🐧 Linux-C , confirm from that side:
curl -s "https://login.quickztna.com/api/db/acl_rules?org_id=YOUR_ORG_ID&id=eq.ACL_RULE_UUID" \
-H "Authorization: Bearer YOUR_ADMIN_TOKEN"
Expected: enabled: false.
Pass: jit_revoke returns status: "revoked". The grant row has a revoked_at timestamp. The linked ACL rule is enabled: false.
Fail / Common issues:
- ACL rule remains
enabled: true— the handler runsUPDATE acl_rules SET enabled = false WHERE jit_grant_id = ? AND org_id = ?. If the rule’sjit_grant_idcolumn is null (rule was created before migration 002 added the column), the UPDATE matches zero rows. Verify the rule hasjit_grant_idset. - HTTP 403 —
jit_revokerequires org admin. - Attempting to revoke an already-revoked grant succeeds silently — the UPDATE is idempotent (no status check before running). This is by design.
ST3 — Approve a Grant on a Pending-State Grant Cannot Happen Twice
What it verifies: Attempting to approve a grant that is no longer 'pending' (already 'approved', 'denied', or 'revoked') returns INVALID_STATE.
Steps:
- Create a fresh JIT request:
curl -s -X POST "https://login.quickztna.com/api/governance" `
-H "Authorization: Bearer YOUR_ADMIN_TOKEN" `
-H "Content-Type: application/json" `
-d '{
"action": "jit_request",
"org_id": "YOUR_ORG_ID",
"source_selector": "tag:test-src",
"destination_selector": "tag:test-dst",
"duration_hours": 1
}'
Note the grant_id.
- Approve it:
curl -s -X POST "https://login.quickztna.com/api/governance" `
-H "Authorization: Bearer YOUR_ADMIN_TOKEN" `
-H "Content-Type: application/json" `
-d '{
"action": "jit_approve",
"org_id": "YOUR_ORG_ID",
"grant_id": "FRESH_GRANT_UUID"
}'
Expected: HTTP 200, status: "approved".
- Revoke it immediately:
curl -s -X POST "https://login.quickztna.com/api/governance" `
-H "Authorization: Bearer YOUR_ADMIN_TOKEN" `
-H "Content-Type: application/json" `
-d '{
"action": "jit_revoke",
"org_id": "YOUR_ORG_ID",
"grant_id": "FRESH_GRANT_UUID"
}'
Expected: HTTP 200, status: "revoked".
- Now try to approve the revoked grant again:
curl -s -X POST "https://login.quickztna.com/api/governance" `
-H "Authorization: Bearer YOUR_ADMIN_TOKEN" `
-H "Content-Type: application/json" `
-d '{
"action": "jit_approve",
"org_id": "YOUR_ORG_ID",
"grant_id": "FRESH_GRANT_UUID"
}'
Expected error (HTTP 400):
{
"success": false,
"data": null,
"error": {
"code": "INVALID_STATE",
"message": "Grant is already revoked"
}
}
Pass: Re-approving a revoked grant returns HTTP 400 INVALID_STATE. The message reflects the current status of the grant.
Fail / Common issues:
- Second approval succeeds and creates a second ACL rule — the handler’s
if (grant.status !== 'pending')check is not firing. Verify the grant was actually revoked (thejit_listwithstatus: "revoked"should show it).
ST4 — Governance Metrics Reflect Active Grant Count
What it verifies: get_metrics correctly counts only grants where status = 'approved' AND expires_at > NOW(). Revoked or expired grants are not included.
Steps:
- On ⊞ Win-A , note the current active grant count:
curl -s -X POST "https://login.quickztna.com/api/governance" `
-H "Authorization: Bearer YOUR_ADMIN_TOKEN" `
-H "Content-Type: application/json" `
-d '{
"action": "get_metrics",
"org_id": "YOUR_ORG_ID"
}'
Note data.jit_access.active_grants.
- Create and approve a new grant:
# Create
$req = curl -s -X POST "https://login.quickztna.com/api/governance" `
-H "Authorization: Bearer YOUR_ADMIN_TOKEN" `
-H "Content-Type: application/json" `
-d '{"action":"jit_request","org_id":"YOUR_ORG_ID","source_selector":"tag:metrics-src","destination_selector":"tag:metrics-dst","duration_hours":1}'
$grantId = ($req | ConvertFrom-Json).data.grant_id
# Approve
curl -s -X POST "https://login.quickztna.com/api/governance" `
-H "Authorization: Bearer YOUR_ADMIN_TOKEN" `
-H "Content-Type: application/json" `
-d "{`"action`":`"jit_approve`",`"org_id`":`"YOUR_ORG_ID`",`"grant_id`":`"$grantId`"}"
- Re-check
get_metrics:
curl -s -X POST "https://login.quickztna.com/api/governance" `
-H "Authorization: Bearer YOUR_ADMIN_TOKEN" `
-H "Content-Type: application/json" `
-d '{
"action": "get_metrics",
"org_id": "YOUR_ORG_ID"
}'
Expected: data.jit_access.active_grants is one higher than before.
- Now revoke the grant:
curl -s -X POST "https://login.quickztna.com/api/governance" `
-H "Authorization: Bearer YOUR_ADMIN_TOKEN" `
-H "Content-Type: application/json" `
-d "{`"action`":`"jit_revoke`",`"org_id`":`"YOUR_ORG_ID`",`"grant_id`":`"$grantId`"}"
- Re-check
get_metricsonce more:
Expected: data.jit_access.active_grants returns to the original value. The revoked grant is no longer counted.
Pass: Active grant count increments after approval, decrements after revocation. Revoked grants are excluded from the count.
Fail / Common issues:
- Count does not change — the metrics query filters on
status = 'approved' AND expires_at > NOW(). Ifexpires_atwas null (old data), the grant would be excluded from both before and after counts. Verifyexpires_atis set on the grant. - Count decrements immediately after revocation — this is the correct behavior. The SQL
expires_at > NOW()combined withstatus = 'approved'is evaluated at query time.
ST5 — Request History Includes Full Lifecycle
What it verifies: get_request_history shows all grants across all statuses with requester and approver emails joined from user_credentials.
Steps:
- On 🐧 Linux-C , query the full request history:
curl -s -X POST "https://login.quickztna.com/api/governance" \
-H "Authorization: Bearer YOUR_ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"action": "get_request_history",
"org_id": "YOUR_ORG_ID"
}'
Expected response (HTTP 200):
{
"success": true,
"data": {
"requests": [
{
"id": "<uuid>",
"status": "revoked",
"source_selector": "tag:test-src",
"destination_selector": "tag:test-dst",
"requested_duration_hours": 1,
"reason": null,
"requester_email": "member@example.com",
"approver_email": "admin@example.com",
"granted_at": "2026-03-17T...",
"expires_at": "2026-03-17T...",
"revoked_at": "2026-03-17T...",
"denial_reason": null
}
]
},
"error": null
}
-
Verify the history contains grants from all the sub-tests above (pending, approved, denied, revoked statuses).
-
Verify that
requester_emailandapprover_emailare populated (not null) for grants that were approved or denied. These come from a JOIN onuser_credentials. -
For the denied grant from Chapter 67 — ST4, verify
denial_reasonis populated if thedenial_reasoncolumn exists on the DB (migration 024/025). -
Confirm the response is ordered by
created_at DESC(most recent grant appears first).
Pass: get_request_history returns grants across all statuses. requester_email and approver_email are populated for reviewed grants. denial_reason is present for denied grants. Results are ordered newest first.
Fail / Common issues:
requester_email: null— the JOIN onuser_credentialsusesCAST(jag.requester_user_id AS uuid). If the UUID format injit_access_grantsdoes not match the format inuser_credentials, the JOIN returns null. Verify both tables store UUIDs in the same format.denial_reasonfield absent entirely — the handler dynamically includes this column only if theSELECT denial_reason FROM jit_access_grants LIMIT 0probe query succeeds. If it throws,hasDenialReason = falseand the field is not included.
Summary
| Sub-test | What it proves |
|---|---|
| ST1 | Approved grant has expires_at matching approval_time + duration_hours; linked ACL rule is enabled: true |
| ST2 | jit_revoke immediately sets enabled: false on the linked ACL rule and transitions grant to "revoked" |
| ST3 | Attempting to approve a non-pending grant returns HTTP 400 INVALID_STATE with the current status in the message |
| ST4 | get_metrics.jit_access.active_grants counts only approved, non-expired grants; decrements on revocation |
| ST5 | get_request_history shows all statuses with joined requester/approver emails; ordered newest first |