Skip to content

[#992] Apply the confidentiality of a backend index to the running backend - #1000

Open
vharseko wants to merge 2 commits into
OpenIdentityPlatform:masterfrom
vharseko:feature/992-index-confidentiality-applied
Open

[#992] Apply the confidentiality of a backend index to the running backend#1000
vharseko wants to merge 2 commits into
OpenIdentityPlatform:masterfrom
vharseko:feature/992-index-confidentiality-applied

Conversation

@vharseko

@vharseko vharseko commented Sep 9, 2026

Copy link
Copy Markdown
Member

Fixes #992

DefaultIndex.setConfidential only compared the setting with the one the index was opened with. Nothing moved the CryptoSuite the indexes of the attribute share to the new value, and nothing bound their codecs again, so confidentiality-enabled reached the running backend in neither direction, and the comparison never converged: every later change of that index - index-entry-limit included - untrusted it again and asked for a rebuild which could not help.

The declared admin action of the property is that the index has to be rebuilt (BackendIndexConfiguration.xml:230), not that the component has to be restarted, so this makes the implementation answer for what is documented.

What the change does

AttributeIndex.applyConfigurationChange compares the old configuration with the new one itself - setConfidential leaves the Index interface, since an index does not need to know about this setting - and when the confidentiality changed:

  • puts the new setting in force on the shared CryptoSuite, through EntryContainer.setIndexConfidentiality, which keeps the cipher of the backend. Done before the write which opens the indexes this change adds, so the key hashed index that enabling confidentiality of an equality index creates binds its codec to the new setting and encrypts its records, rather than only hashing its keys. Applied outside the writes, which the storage may replay: it changes a live object and is idempotent;
  • gives up the trees of the indexes which are kept: one write untrusts and deletes them, a second opens them again, which is what binds their codecs to the setting now in force. Both in a branch of their own, which takes and releases the same exclusive access the removal of an index takes; a change which leaves those trees where they are - of the entry limit alone - takes none, and shares the first of the two writes through writeUpdatedIndexes().

The trees are given up rather than left to the rebuild because their records are in the encoding of the setting being given up, and the codec of the new one does not read them back as what they are: an encrypted record read as clear text decodes to an empty set of entry IDs rather than failing, which is a search answered with no entries at all. An empty untrusted index answers "undefined" instead, and so is not used until the rebuild has run. They are deleted rather than emptied record by record, which for an index of any size would be a transaction of its own making, and in two writes rather than one because the storage engines delete and create the tree of an index as operations of their own - as removing and adding an index does. That order also decides how an interrupted change ends: the write which deletes is the one which untrusts, so what is left is an index the next open of the container creates empty and keeps degraded, never a trusted one holding nothing.

DefaultIndex.codec becomes volatile, since it is now bound again on a live instance other threads are holding.

Tests

IndexConfidentialityChangeTestCase, with a subclass per storage engine (PDBIndexConfidentialityChangeTest, JEIndexConfidentialityChangeTest) because deleting and creating the tree of an index is the engine's own operation. Five behaviours each, all failing before the change:

what it pins before
enabling empties the index it asks to rebuild, which then answers "undefined" the tree kept its record
what the index writes next is encrypted the record was written in clear text
the key hashed index the change creates encrypts its records too its records were written in clear text
disabling stops the encryption the record was still encrypted
a later unrelated change leaves the index trusted it was untrusted again, with the rebuild message repeated

Run green: the ten new tests together with EncryptedPDBTestCase, EncryptedJETestCase, PDBTestCase, JETestCase, ReplayedConfigChangeTest, DefaultIndexTest, EntryIDSetTest, StateTest and OnDiskMergeImporterTest - 225 tests, no failures.

Not in this change

An online rebuild-index of an index whose confidentiality was never changed is unaffected. The offline rebuild already applied the setting correctly, since it opens a container of its own; it still does.

…ex to the running backend

setConfidential() only compared the setting with the one the index was opened
with, so a change of confidentiality-enabled reached neither the CryptoSuite the
indexes of the attribute share nor the codecs bound to it, and the comparison
never converged: every later change of that index untrusted it again and asked
for a rebuild which could not help.

The change now puts the new setting in force on that suite before the indexes it
adds bind their codecs, so the key hashed index enabling confidentiality creates
encrypts its records as well, and gives up the trees of the indexes it keeps: one
write untrusts and deletes them, a second opens them again, which is what binds
their codecs to the setting now in force. Their records are in the encoding being
given up, which the codec of the new one reads back as an empty set of entry IDs
rather than failing, so leaving them would answer searches with no entries at all
until the rebuild had run.

Fixes OpenIdentityPlatform#992
@vharseko
vharseko requested a review from maximthomas September 9, 2026 14:30
@vharseko vharseko added bug security Security fixes / CodeQL code-scanning alerts tests Test suites: fixing, enabling, un-disabling concurrency Thread-safety / race-condition bugs labels Sep 9, 2026
…lity change with its release

CodeQL (java/unreleased-lock) flagged the lock taken under `treesGivenUp` and
released under the same condition in the finally block: the two are balanced,
since the flag is a local which cannot change in between, but the query cannot
see that, and the shape reads worse than the one which replaces it.

The branch which gives up the trees now takes the lock and releases it as a pair
of its own; the write which applies the new configuration to the indexes that are
kept becomes writeUpdatedIndexes(), called by that branch and by the one which
changes nothing but the entry limit. No behaviour changes.
@vharseko
vharseko requested review from maximthomas and removed request for maximthomas September 10, 2026 06:51
@vharseko vharseko added the index Attribute/VLV index subsystem: build, trust, rebuild, confidentiality label Sep 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug concurrency Thread-safety / race-condition bugs index Attribute/VLV index subsystem: build, trust, rebuild, confidentiality security Security fixes / CodeQL code-scanning alerts tests Test suites: fixing, enabling, un-disabling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Changing confidentiality-enabled on a backend index applies nothing until the backend is restarted, and untrusts the index again on every later change

2 participants