Skip to content

How to Generate and Validate Policies

Auto-generate Rego policy skeletons from FastAPI routes and validate at startup or in CI.

CLI Commands

Generate Policies

fastapi-topaz generate-policies --app myapp.main:app --output policies/

Output:

Scanning routes...
Generated policies/myapp/GET/documents.rego
Generated policies/myapp/POST/documents.rego
Generated policies/myapp/check.rego (ReBAC)
Generated 3 policies from 2 routes

Options:

fastapi-topaz generate-policies \
  --app myapp.main:app \
  --output policies/ \
  --config myapp.config:config \
  --overwrite \
  --dry-run \
  --format flat

Validate Policies (CI Integration)

fastapi-topaz policy-diff --app myapp.main:app --policies policies/

Output:

Scanning routes... 12 routes found
Scanning policies... 10 policies found

Missing policies (routes without policies):
   - myapp.DELETE.documents.__id
     Route: DELETE /documents/{id}

Orphaned policies (no matching route):
   - myapp.GET.old_endpoint

Summary: 1 missing, 1 orphaned
Exit code: 1

Generate Route Map

fastapi-topaz policy-map --app myapp.main:app --format markdown

Output:

| Route | Method | Policy Path | Status |
|-------|--------|-------------|--------|
| /documents | GET | myapp.GET.documents | OK |
| /documents | POST | myapp.POST.documents | OK |
| /documents/{id} | DELETE | myapp.DELETE.documents.__id | Missing |

Startup Validation

@app.on_event("startup")
async def startup():
    result = await config.validate_policies_from_app(app)

    if not result.valid:
        for missing in result.missing_policies:
            print(f"Missing policy: {missing.policy_path}")
            print(f"  Route: {missing.method} {missing.path}")

        raise RuntimeError(
            f"Missing {len(result.missing_policies)} policies. "
            "Run 'fastapi-topaz generate-policies' to create them."
        )

Quick validation (one-liner):

@app.on_event("startup")
async def startup():
    await config.validate_policies_from_app(app, fail_on_missing=True)

Generated Rego Template

Policy route (GET /documents):

package myapp.GET.documents

import rego.v1

default allowed = false

# Route: GET /documents
# Generated by fastapi-topaz

allowed if {
    input.identity.type == "IDENTITY_TYPE_SUB"
}

ReBAC policy (myapp.check):

package myapp.check

import rego.v1

default allowed = false

allowed if {
    ds.check({
        "object_type": input.resource.object_type,
        "object_id": input.resource.object_id,
        "relation": input.resource.relation,
        "subject_type": input.resource.subject_type,
        "subject_id": input.identity.value,
    })
}

CI/CD Integration

GitHub Actions

name: Policy Validation

on: [push, pull_request]

jobs:
  validate-policies:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v4
        with:
          python-version: '3.11'
      - run: pip install fastapi-topaz
      - run: |
          fastapi-topaz policy-diff \
            --app myapp.main:app \
            --policies policies/ \
            --strict

Pre-commit Hook

# .pre-commit-config.yaml
repos:
  - repo: local
    hooks:
      - id: policy-diff
        name: Validate Topaz policies
        entry: fastapi-topaz policy-diff --app myapp.main:app --policies policies/
        language: system
        pass_filenames: false

Python API

from fastapi_topaz.codegen import generate_policies, policy_diff

# Generate policies
policies = generate_policies(app, config)
for policy_path, rego_content in policies.items():
    print(f"# {policy_path}\n{rego_content}\n")

# Check diff
diff = await policy_diff(app, config, policies_dir="policies/")
print(f"Missing: {diff.missing}")
print(f"Orphaned: {diff.orphaned}")

See Also