Webhook icon
Schedule icon
Script icon
Process icon
If icon
Log icon
Exit icon
Pause icon
SlackIncomingWebhook icon

Gate OpenFGA Model Changes Against Real Authorization Decisions

Compare OpenFGA model decisions on isolated fixtures, assert allow and deny cases, and block unexpected changes with Kestra.

Categories
CoreInfrastructure

An authorization model can be syntactically valid while silently widening access or removing a necessary grant. This flow tests the decisions that matter to your application before an optional human-approved production write.

The default fixture runs against a local OpenFGA service. It checks a direct viewer, an editor who must retain view access, and a stranger who must remain denied. The candidate removes the editor's view permission, so the default run blocks promotion and reports the regression. Both immutable model IDs use the same tuples in a newly created temporary store.

How it works

  1. model_change_webhook starts an on-demand CI gate; nightly_model_check is a disabled backstop.
  2. validate_fixture (Python Script) rejects malformed input before any external call.
  3. compare_models (Python Script) creates a temporary store, writes baseline and candidate models plus shared tuples, pins each check to its model ID, and requests higher consistency. It records the observed allow/deny decision, the expected decision, and a changed-decision list for the supplied check matrix. It deletes the temporary store in finally.
  4. enforce_gate (If) ends the execution as FAILED if any expectation differs.
  5. If promotion is enabled after a passing gate, check_production_revision confirms that the live latest model ID and normalized model content match the tested baseline. request_approval (Pause) defaults to rejection.
  6. After explicit approval, write_production_model rechecks the latest ID, writes the candidate once, and verifies the returned ID. A declined approval leaves production unchanged.

Error handling

  • Input validation runs before store creation. OpenFGA API errors, missing response fields, and nonboolean checks fail closed.
  • Read-only checks and temporary-store deletion retry transient network failures with exponential backoff. Non-idempotent create and write requests are not blindly retried.
  • The comparison task attempts temporary-store deletion even after a failed model write or check. If deletion fails, the error identifies the store for manual cleanup. OpenFGA deletion hides a store; it does not erase its underlying models and tuples.
  • A changed production revision before or after approval blocks the write. OpenFGA does not expose a compare-and-swap model write, so concurrent writers should also be controlled outside this flow.
  • The production write is never automatically retried. If its response is lost, inspect the latest model ID before starting another run.
  • errors sends one Slack alert naming the failed task and cause. An assertion mismatch is an expected gate block and does not send an operational alert.

What this blueprint teaches about Kestra

  • A webhook can start a policy gate while a disabled Schedule documents a future backstop.
  • Script task outputs can carry a machine-readable decision report into an If gate.
  • Exit distinguishes an expected blocked promotion from an infrastructure failure handled by errors; Pause collects an explicit human decision.

Prerequisites

  • OpenFGA HTTP API reachable from the Kestra worker. For the default local setup, name the container openfga-gate on the same Docker network as Kestra and expose port 8080 inside that network.
  • Python 3 on the Kestra worker for the Process runner.
  • A Slack incoming webhook for operational failures.

Secrets

  • OPENFGA_API_TOKEN: bearer token for authenticated OpenFGA; set it to an empty value for an unauthenticated local test service.
  • SLACK_WEBHOOK_URL: Slack incoming webhook URL for operational failures.

Inputs

  • openfga_api_url (STRING, default http://openfga-gate:8080): API origin.
  • baseline_model_json and candidate_model_json (STRING): OpenFGA model JSON documents.
  • fixture_tuples_json (STRING): sanitized relationship tuples shared by both models.
  • assertions_json (STRING): named checks and expected baseline/candidate decisions.
  • promote_to_production (BOOL, default false): activate the approval path after a passing gate.
  • production_store_id and expected_production_model_id (STRING, default disabled): pin the target store and current model before enabling promotion.

Outputs

  • gate_report (JSON): checked decisions, mismatches, changed decisions, model IDs, and temporary store ID on a successful run. Kestra does not publish flow outputs on a blocked FAILED run; inspect compare_models task outputs for its report.
  • production_model_id (STRING): new model ID only after an approved production write.

Quick start

  1. Start a local OpenFGA instance reachable as http://openfga-gate:8080 from the Kestra worker.
  2. Configure OPENFGA_API_TOKEN and SLACK_WEBHOOK_URL as Kestra secrets, and replace the webhook key before exposing the trigger.
  3. Run with defaults. The gate should block: user:bob loses viewer on document:plan, so one of three assertions fails.
  4. Replace both models, tuples, and checks with a representative policy fixture. For a passing local example, set candidate_model_json equal to the baseline model; all three assertions then pass with no changed decisions.
  5. To promote, supply the live store ID and latest model ID, set promote_to_production to true, and review the paused execution. Approve only after checking the report and candidate model.

How to read the result

The changed list covers only checks supplied in assertions_json; it is not an exhaustive diff of all users, objects, or permissions. Add positive, negative, and edge cases for every relation you intend to protect.

How to extend

  • Load fixture JSON from a versioned repository in CI instead of entering it manually.
  • Generate sanitized checks from application authorization tests, then feed them to this flow.
  • Connect the approved model ID to your application deployment process, which should pin that ID in its authorization checks.

Plugins used

Links

See How

New to Kestra?

Use blueprints to kickstart your first workflows.