docs(adr0035): Add relation sync provider ADR - #1226
Merged
Conversation
ADR 0033 shipped the OpenFGA assignment driver with a documented hole: nothing syncs Keystone group membership into the store, so a deployment that switches to it issues tokens missing every group-derived role. Operators querying OpenFGA from services other than Keystone need those membership tuples too, and SCIM provisioning is where a tenant expects a new team to become usable. Propose an optional `relation_sync` provider with two independently pluggable driver kinds, both on the existing BackendRegistration machinery: `dyn RelationSyncOutbox` (ships `sql`) and `dyn RelationSyncTarget` (ships `openfga`). With no [relation_sync] section the provider is not built, no outbox row is written and no task is spawned, so OpenFGA stays optional. - Membership mutations in identity-driver-sql move from a bare insert to a db.begin() transaction that also inserts outbox rows. The outbox is a new rust-only table; no python-owned table is altered, so a co-deployed python keystone is unaffected (the federated_identity_ provider precedent). - An outbox row is a key, never a payload: the relay re-reads the authoritative membership and writes or deletes the tuple. That makes drains idempotent, order-insensitive and coalescing, which matters because Event carries no sequence and spawn-per-hook does not preserve ordering. - RelationSyncHook subscribes to the existing fire-and-forget dispatcher only to wake the relay early. It performs no I/O and no outbox write, so at-most-once delivery costs latency, never data. - A leader-gated reconciler diffs Keystone against each target per group and is the correctness mechanism, not a backstop: a co-deployed python keystone and LDAP-backed domains both write memberships that emit no event this process can observe. - Target credentials stay in server configuration, never the domain configuration API, for the reason ADR 0034 §6 gives; the membership relation is validated against role_to_relation so the relay cannot write relations that grant roles. - Metrics per ADR 0031, with relation_sync_lag_seconds called out as the security-relevant number since it bounds how long a revoked membership still authorizes in the target store. Records the rejected alternatives: an inline write inside the membership transaction, a hook that writes tuples directly, contextual tuples as a substitute rather than a complement, splitting membership into its own provider, and attributed membership edges. Signed-off-by: Artem Goncharov <artem.goncharov@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
ADR 0033 shipped the OpenFGA assignment driver with a documented hole:
nothing syncs Keystone group membership into the store, so a deployment
that switches to it issues tokens missing every group-derived role.
Operators querying OpenFGA from services other than Keystone need those
membership tuples too, and SCIM provisioning is where a tenant expects a
new team to become usable.
Propose an optional
relation_syncprovider with two independentlypluggable driver kinds, both on the existing BackendRegistration
machinery:
dyn RelationSyncOutbox(shipssql) anddyn RelationSyncTarget(shipsopenfga). With no [relation_sync]section the provider is not built, no outbox row is written and no task
is spawned, so OpenFGA stays optional.
a db.begin() transaction that also inserts outbox rows. The outbox is
a new rust-only table; no python-owned table is altered, so a
co-deployed python keystone is unaffected (the federated_identity_
provider precedent).
authoritative membership and writes or deletes the tuple. That makes
drains idempotent, order-insensitive and coalescing, which matters
because Event carries no sequence and spawn-per-hook does not preserve
ordering.
only to wake the relay early. It performs no I/O and no outbox write,
so at-most-once delivery costs latency, never data.
and is the correctness mechanism, not a backstop: a co-deployed python
keystone and LDAP-backed domains both write memberships that emit no
event this process can observe.
configuration API, for the reason ADR 0034 §6 gives; the membership
relation is validated against role_to_relation so the relay cannot
write relations that grant roles.
security-relevant number since it bounds how long a revoked membership
still authorizes in the target store.
Records the rejected alternatives: an inline write inside the membership
transaction, a hook that writes tuples directly, contextual tuples as a
substitute rather than a complement, splitting membership into its own
provider, and attributed membership edges.
Signed-off-by: Artem Goncharov artem.goncharov@gmail.com