feat: Add domain config SQL driver - #1139
Open
magdang wants to merge 1 commit into
Open
Conversation
magdang
force-pushed
the
feat/domain-config-sql-driver
branch
2 times, most recently
from
August 3, 2026 10:39
4c0d363 to
f7666b5
Compare
gtema
reviewed
Aug 4, 2026
Collaborator
There was a problem hiding this comment.
this migration is not necessary - it is part of the initial schema installed by the python keystone - no native compatible provider defines migration
Collaborator
Author
There was a problem hiding this comment.
Agreed — dropped. The migration module, its migrations() override and the sea-orm-migration dependency are all gone; the two tables are now created only from their entities on keystone-manage db sync, like the other natively-compatible providers. Verified end to end against a fresh SQLite database:
$ keystone-manage --config keystone.conf db sync
schema sync completed successfully
$ sqlite3 db.sqlite ".schema whitelisted_config"
CREATE TABLE "whitelisted_config" ( "domain_id" varchar(64) NOT NULL, "group" varchar(255) NOT NULL, "option" varchar(255) NOT NULL, "value" text NOT NULL, CONSTRAINT "pk-whitelisted_config" PRIMARY KEY ("domain_id", "group", "option") );
The branch is also rebased onto main now that #1138 has merged, so it is a single commit and no longer conflicting.
magdang
force-pushed
the
feat/domain-config-sql-driver
branch
from
August 7, 2026 19:36
f7666b5 to
0545044
Compare
The SQL persistence behind the /v3/domains/{domain_id}/config API
family: one row per option keyed by (domain_id, group, option), the
value stored as the JSON it was written as, matching python-keystone's
JsonBlob.
The tables are python-keystone's whitelisted_config and
sensitive_config, created from their entities on `keystone-manage db
sync`. They are part of python-keystone's initial schema, so this
driver ships no migration of its own.
Keeping the secrets in their own table is what makes the read paths
safe by construction rather than by filtering: the group and option
scoped reads back the endpoints that hand options to a client, and
they only ever select from whitelisted_config. Only the whole-config
read touches sensitive_config, because the identity backend has to be
handed the bind password, and DomainConfig skips it on serialization.
Rows that drifted out of the configurable set are dropped when a row
is decoded, which is the one place every read passes through, so a
group that is no longer configurable and an option that is no longer
whitelisted read alike. Dropping the latter deeper down would let it
reach the emptiness check the reads make their None decision on, and
be served outright by the option scoped read, which has no later
DomainConfig::from_options to drop it.
%(option)s references are stored verbatim and resolved by
DomainConfig::substitute, never on a driver read: resolving them in
get_domain_config would inline the bind password into ldap.url, which
that same read serves to the API.
Nothing resolves a DomainConfigBackend yet; the driver is reachable
only through the registry it submits itself to (name: "sql").
Part of openstack-experimental#954. Closes openstack-experimental#956.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Yousef Hussein <ymh1874@gmail.com>
magdang
force-pushed
the
feat/domain-config-sql-driver
branch
from
August 7, 2026 19:54
0545044 to
e30db97
Compare
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.
Phase 1 of #954: the SQL persistence for the
/v3/domains/{domain_id}/configAPI family.Rebased onto
mainnow that #1138 (Phase 0, the core types and thebackend trait) has merged, so this is a single commit against
main.There is still no service or handler, so nothing resolves a backend of
the
DomainConfigBackendkind yet — the driver is reachable only throughthe registry it submits itself to (
name: "sql"), and its tables throughkeystone-manage db sync.Storage
One row per option keyed by
(domain_id, group, option), value in a textcolumn as the JSON it was written as, matching python-keystone's
JsonBlob— whatever type a client writes is the type a later readreturns.
The tables are named
whitelisted_configandsensitive_config, i.e.python-keystone's names, rather than the
domain_config/domain_config_sensitivesketched in the issue. Every other*-driver-sqlcrate in this workspace transcribes python-keystone's schema down to the
table and column names (
user,group,local_user, …), and Phase 0'score types already document these two by name.
They are part of the initial schema python-keystone installs, so this
driver ships no migration of its own (per review feedback): the tables
are created from their entities on
keystone-manage db sync, like everyother natively-compatible provider. A test against a real SQLite database
covers that path, which also pins down that the reserved words
groupandoptionare usable as column names.Sensitive options
Keeping secrets in their own table is what makes the read paths safe by
construction rather than by filtering:
to a client, and they only ever select from
whitelisted_config;Nonewithoutissuing a statement at all;
sensitive_config, because theidentity backend has to be handed the bind password — and
DomainConfigskips it on serialization, so it still cannot reach a response.
Substitution
%(option)sreferences are stored verbatim and resolved byDomainConfig::substitute, not on a driver read. Resolving them insideget_domain_configwould inline the bind password intoldap.url, whichthat same read serves to the API; the resolved form is for the identity
backend alone, so it belongs to the resolution layer, where the issue also
lists it (Phase 3).
Behaviour notes
longer configurable is skipped with a warning rather than failing the
whole read, which would otherwise leave the domain unreadable — and its
identity backend uninitializable — until an operator deleted the row.
Both spellings of that drift, a group that is no longer configurable and
an option that is no longer whitelisted, are dropped as the row is
decoded, which is the one place every read passes through. Dropping the
latter deeper down, where
DomainConfig::from_optionsdoes it, would letit reach the emptiness check the reads make their
Nonedecision on — soa domain holding nothing but a drifted row would answer
200with anempty configuration where a drifted group answers
404— and theoption scoped read, which has no later
from_optionsto drop it, wouldserve it outright.
delete_configdoes; the row count answers that without a precedingread, and the transaction rolls back the half that did match. Deleting a
whole configuration that is not there stays silent, also matching
python-keystone's driver.
nor sensitive: storing one would create a row every later read skips.
are what the caller just supplied and they are skipped on serialization,
so echoing them back cannot put a secret on the wire, while a caller
that patches only
ldap.passwordstill sees a group that is not empty.All of them answer with what was stored rather than what was asked for,
the way python-keystone answers from the rows it wrote: the two differ
over a group the whitelist emptied, which the request still carries and
no later read would ever report.
Outside the driver crate
core-types,GroupMismatch, for a groupscoped write whose payload addresses another group (python-keystone's
"trying to update group X, so that, and only that, …").
DomainConfigBackendadded toPluginManager's backend registry so thedriver's
inventory::submit!has a registry to submit to. Resolving andholding the backend comes with the provider that consumes it — Phase 3/4.
Testing
cargo test -p openstack-keystone-domain-config-driver-sql— 50 tests,including two against a real SQLite database for the schema.
cargo test -p openstack-keystone-core-types -p openstack-keystone-corekeystone-manage db syncagainst a fresh SQLite database creates bothtables with the composite primary key;
db upstill runs clean.cargo clippy --all-targetson the touched crates: no new warnings.Part of #954. Closes #956.