PPactDocs
Administration

Network & IP allowlist

Restrict Pact access to trusted networks with tenant-scoped IP allowlist and denylist policies, scoped to admin/API/all traffic and enforced on every request.

Network & IP allowlist

Enterprise IT teams often need to restrict a SaaS tool to corporate egress IPs. Pact supports tenant-scoped allowlist and denylist policies stored in tenant_ip_policies, enforced by a middleware (api/middleware/ip_policy.py) on every authenticated route — admin, API, SCIM, and SAML callbacks alike. The admin CRUD is api/routes/admin_ip_policy.py; owner/admin only.

Manage policies

code
GET    /v1/admin/network/policies         — list
POST   /v1/admin/network/policies         — create
GET    /v1/admin/network/policies/{id}    — read one
PATCH  /v1/admin/network/policies/{id}    — partial update
DELETE /v1/admin/network/policies/{id}    — delete

A create payload:

code
POST /v1/admin/network/policies
{
  "name": "Corp egress only",
  "mode": "allowlist",              // allowlist | denylist
  "cidrs": ["203.0.113.0/24", "198.51.100.7/32"],
  "enforced_for": ["all"],          // all | admin | api
  "enabled": true
}

Scopes

The enforced_for list decides which traffic a policy governs, matched to the request path:

  • ["admin"] — fires only on /v1/admin/...
  • ["api"] — fires on /v1/admin/... and other /v1/... API routes
  • ["all"] — fires on everything (the default, most restrictive)

Paths that are never gated

Health probes (/health*) and password login (/v1/auth/login) are always exempt — blocking liveness checks would break Azure Front Door and Container Apps, and IP-gating the login itself is a denial-of-service shape. The policy protects authenticated traffic, not the login endpoint.

Dry-run before you enforce

Test a candidate IP against your live policies without dispatching a real request — the admin UI uses this to answer "would this IP be blocked?":

code
POST /v1/admin/network/policies/test
{ "ip": "203.0.113.9", "scope": "api" }   →
{ "allow": true, "reason": "...",
  "matched_policy_id": 3, "matched_policy_name": "Corp egress only" }

There is also a CIDR validation endpoint (POST /v1/admin/network/policies/validate-cidrs) so the UI can reject malformed input before save.

Guardrails against self-lockout

An allowlist with empty cidrs would refuse every request, so the create route rejects it with 400. A denylist with empty cidrs is allowed (it denies no one) as a legitimate intermediate state during edits. Every create, update, and delete emits auth.ip_policy.{created,updated,deleted} to the audit stream so SOC 2 reviewers see network-policy changes.