Skip to content

feat: add OpenFGA as a policy store - #958

Open
Furox-Art wants to merge 4 commits into
permitio:masterfrom
Furox-Art:feat/openfga-policy-store
Open

Furox-Art wants to merge 4 commits into
permitio:masterfrom
Furox-Art:feat/openfga-policy-store

Conversation

@Furox-Art

Copy link
Copy Markdown

Fixes Issue

Closes #661

/claim #661

Changes proposed

This PR adds OpenFGA as a policy store in OPAL, alongside OPA and Cedar, so OPAL can manage OpenFGA services end-to-end: it syncs OpenFGA authorization models from a git policy repo, syncs relationship tuples from external data sources, and reports transaction health/liveness, with working single- and multi-client docker-compose demos.

Design note: OpenFGA does not store independently addressable "policy files" - a store holds one immutable, versioned authorization model plus relationship tuples. OpenFGAClient therefore keeps the bundle's policy modules in memory, compiles them into a single combined authorization model, and writes a new model version on every policy update (including deltas and deletions). Data updates are translated into relationship tuples and written through OpenFGA's write API.

Acceptance criteria (issue #661)

1. Ability to configure OpenFGA as a policy store in OPAL

  • PolicyStoreTypes.OPENFGA + factory wiring: packages/opal-client/opal_client/policy_store/schemas.py, packages/opal-client/opal_client/policy_store/policy_store_client_factory.py
  • OpenFGAClient implementing OPAL's full policy-store interface (policy/data updates, transaction log, liveness probe, backup/restore): packages/opal-client/opal_client/policy_store/openfga_client.py
  • Configuration surface: six new OPAL_POLICY_STORE_OPENFGA_* confi options in packages/opal-client/opal_client/config.py (store id/name, auto-provisioning by name, authorization-model pinning, max tuples per write, duplicate-tuple handling), documented in documentation/docs/getting-started/configuration.mdx; bearer-token auth via the existing OPAL_POLICY_STORE_AUTH_TYPE=token.

2. OpenFGA models/policies are auto-synced from git

  • BundleMaker now accepts non-rego policy modules, so .fga files flow through OPAL's existing git-watch -> bundle -> pub/sub -> client pipeline: packages/opal-common/opal_common/git_utils/bundle_maker.py
  • A .fga DSL -> JSON transpiler (union/intersection/exclusion, computed usersets, tuple-to-usersets, type restrictions incl. wildcards and conditions, extend type, modules, CEL conditions): packages/opal-client/opal_client/policy_store/openfga_dsl.py
  • On the server, .fga repos are enabled with the pre-existing OPAL_FILTER_FILE_EXTENSIONS / OPAL_POLICY_REPO_POLICY_EXTENSIONS options (documented in the tutorial and set in the demo compose files).

3. OpenFGA supports the data fetching pattern and syncing data from external data sources

  • OPAL's store-agnostic data-update pipeline (data sources -> set_policy_data/patch_policy_data) is wired to OpenFGA tuples: packages/opal-client/opal_client/policy_store/openfga_tuples.py converts tuple-native payloads ({"tuples": [...]} / lists / single tuples) and a convenient nested {object: {relation: [users]}} form (with optional OpenFGA relationship conditions) into tuples; writes are chunked to OpenFGA's 100-tuples-per-write limit and idempotent (on_duplicate: ignore); delete_policy_data reads then deletes the affected tuples; get_data_with_input maps onto OpenFGA's Check API.

4. A working end-to-end demo with example ReBAC policies and mock data

  • Demo policy repo (ReBAC authorization model in the .fga DSL + static data): docker/docker_files/openfga_policy_repo/
  • Mock external data source (relationship tuples served over HTTP): docker/docker_files/openfga_data/tuples.json
  • Full walkthrough incl. verifying with the Check API and a live model update: documentation/docs/tutorials/openfga.mdx

5. Docker-compose examples of running OPAL with single or multiple OpenFGA clients

  • Single client: docker/docker-compose-example-openfga.yml
  • Multiple clients (two opal-clients, each keeping its own OpenFGA in sync): docker/docker-compose-example-openfga-multi.yml

6. 100% UT coverage on the code and at least one integration test

  • 95 new tests; 100% line coverage on every new module (openfga_client.py: 402 stmts, openfga_dsl.py: 414 stmts, openfga_tuples.py: 80 stmts - verified with coverage.py)
  • Integration tests: packages/opal-client/opal_client/tests/openfga_client_test.py runs the full OPAL client <-> OpenFGA flow over real HTTP against a stateful, in-process fake of the OpenFGA API (store provisioning/reuse, authorization-model writes, tuple write/read with pagination, Check with a small Zanzibar-style resolver), plus openfga_client_liveness_test.py for the liveness probe. openfga_dsl_test.py is a conformance suite that transpiles the official openfga/language dsl->json transformer test cases (vendored under tests/fixtures/openfga_transformer, Apache-2.0).

Check List (Check all the applicable boxes)

  • I sign off on contributing this submission to open-source
  • My code follows the code style of this project.
  • My change requires changes to the documentation.
  • I have updated the documentation accordingly.
  • All new and existing tests passed.
  • This PR does not contain plagiarized content.
  • The title of my pull request is a short description of the requested changes.

Screenshots

Not applicable (no UI changes).

Note to reviewers

  • Test results: all 95 new tests pass; the full opal-client suite passes (127 tests, including the pre-existing OPA/Cedar suites, proving no regressions). In opal-common, the suites around the touched code pass (68 tests, e.g. paths_test.py); 8 bundle_maker_test.py cases fail on this Windows machine for environmental reasons (local-git test setup) and were verified to fail identically on unmodified master; data_updater_test.py / server_to_client_intergation_test.py do not collect on Windows (POSIX-only fcntl import) - also pre-existing.
  • API endpoint/payload shapes and status codes were verified against the official OpenFGA OpenAPI v3 spec (github.com/openfga/api); the DSL transpiler is verified against the official openfga/language transformer fixtures.
  • OpenFGA authorization models are immutable, so every policy update writes a new model version; "deleting" a module rewrites the combined model without it (inherent to OpenFGA - see "Notes and limitations" in the tutorial).
  • Lint: new/changed Python files are clean under the repo's pinned pre-commit tooling (black 23.1.0, isort 5.12.0) and flake8 --select=E9,F63,F7,F82.

Disclosure: This PR was authored by @Furox-Art (AI-assisted). Happy to adjust anything.

BundleMaker assumed every policy module is a rego file and tried to extract
a rego package from it. Policy stores such as OpenFGA use other policy
formats (e.g. ".fga" authorization models), which have no rego package -
non-rego policy modules now get an empty package name instead of failing
rego parsing, so they can be synced from git like any other policy module.
Adds OpenFGA (https://openfga.dev) as a policy store alongside OPA and
Cedar (POLICY_STORE_TYPE=OPENFGA).

OpenFGA does not store independently addressable policy files: a store
holds one immutable, versioned authorization model plus relationship
tuples. The new OpenFGAClient maps OPAL's concepts onto OpenFGA:

- policy updates: every policy module in a bundle (.fga DSL file or a
  JSON model fragment) is compiled into a single combined authorization
  model which is written to POST /stores/{id}/authorization-models on
  every policy update (modules are tracked in memory so delta bundles,
  set_policy and delete_policy all modify the combined model).
- data updates: OPAL data updates are converted into relationship tuples
  (openfga_tuples.convert_to_tuples accepts tuple-native payloads and a
  convenient {object: {relation: [users]}} nested form) and written via
  POST /stores/{id}/write, chunked to OpenFGA's 100-tuples-per-write
  limit and idempotent via on_duplicate: ignore. Deletions read the
  affected tuples first and delete them explicitly.
- get_data_with_input maps onto OpenFGA's Check API.

Also includes an OpenFGA DSL (.fga) -> JSON transpiler (openfga_dsl),
validated against the official openfga/language transformer test cases,
plus a merge/extend mechanism for combining multiple .fga modules into
one authorization model.

Client configuration follows the existing policy-store patterns: new
OPAL_POLICY_STORE_OPENFGA_* confi options (store id/name, auto-create,
authorization model pinning, max tuples per write, duplicate handling),
bearer-token auth support, transaction-log state, and a liveness probe
against /healthz (falling back to GET /stores) via the shared
LivenessProbeMixin.
- openfga_client_test.py: an in-process, stateful fake of the OpenFGA
  HTTP API (stores, authorization models, tuple write/read with
  pagination, check with a small Zanzibar-style resolver) exercises the
  full OPAL client <-> OpenFGA flow over real HTTP: store provisioning
  and reuse, bundle/delta application, data-updates-to-tuples, chunked
  writes, deletion, backup/restore, error and connection-failure paths.
- openfga_dsl_test.py: conformance tests transpiling the official
  openfga/language dsl->json transformer test cases (vendored under
  tests/fixtures/openfga_transformer, Apache-2.0) plus error paths.
- openfga_tuples_test.py: data-update -> tuple conversion cases.
- openfga_client_liveness_test.py: liveness probe behavior (healthz,
  fallback to /stores, recovery), mirroring the OPA/Cedar suites.

Covers 100% of lines of openfga_client.py, openfga_dsl.py and
openfga_tuples.py.
- docker/docker-compose-example-openfga.yml: OpenFGA + opal-server +
  opal-client demo. The policy repo (an authorization model in the .fga
  DSL plus static data) is committed into a local git repo that the
  server watches; the client (POLICY_STORE_TYPE=OPENFGA) transpiles the
  model into an OpenFGA authorization model and converts data fetched
  from a mock external data source (nginx-served tuples.json) into
  relationship tuples.
- docker/docker-compose-example-openfga-multi.yml: the same demo with
  two opal-clients, each keeping its own OpenFGA instance in sync.
- documentation/docs/tutorials/openfga.mdx: a walkthrough (how OpenFGA
  maps to OPAL concepts, running the demo, verifying with the Check
  API, live policy updates, and all OpenFGA-specific config options).
- documents the new OPAL_POLICY_STORE_OPENFGA_* options in
  documentation/docs/getting-started/configuration.mdx.
@netlify

netlify Bot commented Sep 14, 2026

Copy link
Copy Markdown

Deploy Preview for opal-docs ready!

Name Link
🔨 Latest commit 160a5d5
🔍 Latest deploy log https://app.netlify.com/projects/opal-docs/deploys/6aa74e6e2185aa0008c0ed6b
😎 Deploy Preview https://deploy-preview-958--opal-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add OpenFGA as a Policy Store

1 participant