Implements the mongodb-superpowers-package design: Stable Tasks 1-50 and Advanced Tasks 1-15. The design assumes 19 Stable + 12 Advanced Gradle projects under modules/mongodb*. This repository's fail-closed registry declares exactly 19 leaf identities, so those modules become package boundaries inside the registered leaf :adapter:outbound:persistence-mongo, with the design's module dependency table enforced by ten ArchUnit rules. The mapping and every deviation are recorded in docs/mongodb/repository-adaptation.md. Contract highlights, all enforced by tests rather than convention: - Transaction body retry and commit retry are separate loops. A new session per body attempt; commit-only retry on an unknown commit. The body is never replayed after a commit ambiguity, so a failover cannot become a duplicate. - MongoExecutionOutcome keeps both ambiguous outcomes distinct from success and failure, and MongoFailureContext records only the design-permitted fields. - Failure classification reads server error labels before numeric codes. - BSON representations come from a pinned manifest, never a library default, and a golden type-signature gate fails on any drift. - Index and validator changes go through the manifest and the admin plane; metadata ownership gates every drop. - Every Advanced capability refuses construction unless its flag is enabled. Verified against real servers, not only unit tests. Running the lanes for the first time exposed four defects that a green `check` had hidden: - Four release lanes passed while executing zero tests; the gate now counts executed tests per lane and fails on zero. - The "single replica set" fixture was a standalone, because Testcontainers 2.x needs withReplicaSet(); its test only asserted a connection string. - The three-node fixture was three independent clusters, so no election could occur, and awaitNewPrimary() compared against the post-stop primary. - The migration lease checked modifiedCount, so a same-millisecond refresh read as a lost lease. scripts/verify-mongodb-platform.sh now reports: 9 lanes, 0 skipped, 0 failed, every evidence category produced. scripts/verify-mongodb-advanced.sh reports NOT PROMOTABLE: actual-topology evidence (real sharded cluster, real KMS, real target deployment) is unobtainable here, so it is named rather than assumed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
92 lines
4.9 KiB
Markdown
92 lines
4.9 KiB
Markdown
# Advanced — CSFLE and Queryable Encryption
|
|
|
|
**Capabilities:** `MongoCapability.CSFLE`, `MongoCapability.QUERYABLE_ENCRYPTION`
|
|
**Properties:** `ca-skeleton.persistence-mongo.advanced.csfle.enabled`,
|
|
`ca-skeleton.persistence-mongo.advanced.queryable-encryption.enabled`
|
|
**Status:** Advanced.
|
|
|
|
## Requirements
|
|
|
|
| | |
|
|
|---|---|
|
|
| Topology | Replica set or sharded cluster. |
|
|
| Server | MongoDB 7.0 or 8.0 (see §4 for the 8.0 query-type limits). |
|
|
| Privilege | `MongoPrincipalRole.ENCRYPTION_ADMIN` for the key vault; the application role never holds it. |
|
|
| Environment | A real KMS and key vault. A local key provider does not exercise any of the failure modes that matter. |
|
|
|
|
## 1. CSFLE
|
|
|
|
`MongoCsfleProfile` binds a collection to its `MongoCsfleFieldPolicy` list, a key vault
|
|
`MongoCredentialReference` and the key vault namespace. `MongoCsfleClientFactory` builds the encrypted
|
|
client; `MongoDataKeyResolver` resolves data keys.
|
|
|
|
`MongoCsfleMode`:
|
|
|
|
| Mode | Queryable | Trade-off |
|
|
|---|---|---|
|
|
| `RANDOMIZED` | no | Same plaintext encrypts differently each time. The safe default. |
|
|
| `DETERMINISTIC` | equality only | Same plaintext always yields the same ciphertext, so equality works — and so does frequency analysis. |
|
|
| `UNINDEXED` | no | Stored encrypted, excluded from any index. |
|
|
|
|
`MongoCsfleFieldPolicy.forPii(field, queryable)` defaults to `RANDOMIZED` when the field is not
|
|
queried. Deterministic encryption requires a written `equalityQueryJustification`; the constructor
|
|
refuses a blank one, naming frequency analysis. A low-cardinality deterministic field (a status, a
|
|
country, a boolean) leaks its distribution to anyone who can read the collection, which is the party
|
|
encryption was protecting against.
|
|
|
|
## 2. Queryable Encryption
|
|
|
|
`MongoQueryableEncryptionProfile` binds a collection to `MongoEncryptedFieldDescriptor` entries.
|
|
`MongoQueryableEncryptionQueryType` has exactly two values:
|
|
|
|
- `EQUALITY`
|
|
- `RANGE` — must declare its domain (`min`, `max`). The constructor refuses a range field without one,
|
|
because changing the domain later means re-encrypting the field.
|
|
|
|
`MongoQueryableEncryptionCollectionManager` owns the collection's lifecycle, because a QE collection
|
|
is not just a collection: it carries metadata collections.
|
|
|
|
## 3. Metadata ownership
|
|
|
|
`MongoEncryptionMetadataOwnership` maps `customers` to `enxcol_.customers.esc` and
|
|
`enxcol_.customers.ecoc`, and reports `__safeContent__`-prefixed indexes as
|
|
`MongoMetadataOwnership.ENCRYPTION_MANAGED`.
|
|
|
|
These are never application-owned and never droppable by drift reconciliation. A drift tool that
|
|
drops `enxcol_.customers.ecoc` corrupts the collection's queryability. This is the single most
|
|
important integration point between encryption and
|
|
[ADR-MONGO-004](../../adr/ADR-MONGO-004-index-schema-admin-plane.md).
|
|
|
|
## 4. Unsupported combinations
|
|
|
|
Refused at declaration, not discovered at runtime:
|
|
|
|
| Combination | Why |
|
|
|---|---|
|
|
| CSFLE **and** QE on the same collection | Two incompatible encryption schemes over one namespace. Both profile constructors refuse it. |
|
|
| CSFLE on a time series collection | `requireNotTimeSeries(true)` raises `MongoOperationRejectedException`. |
|
|
| QE `prefix` / `suffix` / `substring` | Not available on the platform's 8.0 baseline. The factory methods throw `UnsupportedOperationException` rather than returning a profile that fails later. |
|
|
| Deterministic CSFLE without a justification | `IllegalArgumentException` naming frequency analysis. |
|
|
| Range QE without a declared domain | `IllegalArgumentException` naming re-encryption. |
|
|
|
|
## 5. Failure recovery
|
|
|
|
| Symptom | Cause | Action |
|
|
|---|---|---|
|
|
| `MongoEncryptionException` on read | Wrong data key, or the key vault is unreachable | Check KMS reachability and the key vault credential. Data is intact; the client cannot decrypt it. |
|
|
| `MongoEncryptionException` on write | KMS permission revoked mid-operation | Restore the grant. Writes fail closed — nothing was written in plaintext. |
|
|
| Queries return nothing on a deterministic field | The field was re-keyed | Equality matching is over ciphertext; a new key produces different ciphertext. Re-encrypt the field. |
|
|
| QE queries fail after a drift reconciliation | A metadata collection was dropped | Restore from backup. This is why ownership gates drops. |
|
|
|
|
**Key rotation.** Rotating the customer master key re-wraps the data keys and does not require
|
|
re-encrypting documents. Rotating a *data* key does require re-encrypting every document that used
|
|
it. These are different operations with different costs, and confusing them is how a rotation becomes
|
|
an outage.
|
|
|
|
## 6. Promotion evidence
|
|
|
|
Per [ADR-MONGO-ADV-001](../../adr/ADR-MONGO-ADV-001-capability-promotion.md), promotion requires the
|
|
real KMS and key vault, plus negative cases that fail closed: wrong key, missing permission, rotation
|
|
mid-operation (`MongoAtlasCapabilityContractSuite.kmsFailureModes`). A local key provider certifies
|
|
none of these — it never rejects anything.
|