Skip to content

feat(mapping): ADR-0020 (mapping engine) phase 1#794

Open
gtema wants to merge 5 commits into
mainfrom
mapping
Open

feat(mapping): ADR-0020 (mapping engine) phase 1#794
gtema wants to merge 5 commits into
mainfrom
mapping

Conversation

@gtema

@gtema gtema commented Jun 13, 2026

Copy link
Copy Markdown
Collaborator
  • feat(core): add mapping engine provider scaffolding
  • feat: Implement mapping-driver-raft and integration tests
  • feat: Add enable/disable virtual user methods
  • feat(mapping): ADR-0020 Phase 1

gtema added 4 commits June 13, 2026 12:24
Add data model, provider traits, service layer, and mock for the unified
mapping engine.

Data model (core-types/mapping/):
- DomainResolutionMode, IdentitySource
- MappingRuleSet, MappingRuleSetCreate, MappingRuleSetUpdate,
  MappingRuleSetListParameters
- MappingRule, MatchCriteria, MatchCondition, ClaimCondition,
  IdentityBinding
- Authorization, GroupAssignment, GroupStrategy
- VirtualUser, MatchResult, VirtualUserListParameters
- RuleMutations, RuleMutation, RulePosition (imperative mutation types)
- MappingProviderError with 8 variants

Provider scaffolding (core/mapping/):
- MappingApi trait with 8 methods sorted alphabetically
- MappingBackend trait with 11 methods (adds CRUD for virtual users)
- MappingService delegates all methods to backend driver
- MappingProvider enum (Service/Mock) with full dispatch
- MockMappingProvider via mockall, plus MappingHook stub

Infrastructure wiring:
- Config struct in config/mapping.rs with driver field (default: raft)
- PluginManagerApi: get/register_mapping_backend
- Provider struct: MappingProvider field, accessor, mock builder
- KeystoneError::Mapping variant for error propagation
- GroupRef struct in identity/group.rs
Add the Raft backend driver for the mapping provider with full CRUD
support for rulesets and virtual user shadow records.

Changes:
- Create crates/mapping-driver-raft with MappingBackend implementation
- Register driver in plugin_manager as "raft", add workspace dependency
- Fix MappingBackend::create_ruleset signature: take MappingRuleSet
  instead of MappingRuleSetCreate so service layer can attach mapping_id
and ruleset_version before storage
- Add service-layer unit tests for create, delete, get, list, update,
  mutate_rules (including immutability checks and error cases)
- Add 14 integration tests for ruleset CRUD, list filtering, and vuser
  get/delete under --profile raft
- Add From<MappingRuleSetCreate> for MappingRuleSet and
  MappingRuleSet::with_update() in core-types
- Exclude mapping tests from default nextest profile, document --profile
  raft in CONTRIBUTING.md

Signed-off-by: Artem Goncharov <artem.goncharov@gmail.com>
Virtual user lifecycle need to be extended with dedicated methods for
audit purpose.
Implement CAS-protected virtual user shadow registry upsert,
CAS-on-delete, scope resolution, and the core authenticate_by_mapping
pipeline for ADR-0020 Phase 1.

Storage layer (openstack-keystone-distributed-storage):
- Add StoreError::Conflict for concurrent modification races
- State machine performs per-entry atomic batch: on CAS violation for
  any mutation, the entire entry batch is discarded (not individual
mutations)
- Add Mutation::CreateIfAbsent for atomic create-or-conflict semantics
- Add expected_revision field to Mutation::Remove for CAS-protected
  deletes
- Mock storage enforces CAS-on-remove with per-entry batch discard
- StorageApi converts Response.violations to StoreError::Conflict
- 7 new mock storage tests: CAS revision matching, CreateIfAbsent,
  CAS-on-remove (matching, mismatching, nonexistent, batch partial
conflict)
- StoreCommand serialization/deserialization for CAS-on-remove roundtrip

Mapping engine (openstack-keystone-core):
- Add authenticate_by_mapping pipeline: ruleset resolution, claim
  evaluation, HMAC-SHA256 virtual user ID derivation, and CAS-protected
shadow upsert
- Add claim evaluation engine with first-match-wins rule evaluation,
  AllOf/AnyOf/AllOfStrict criteria, regex matching, and interpolation
- Add identity binding resolution with domain resolution mode
  enforcement
- Add HMAC-SHA256 deterministic user ID derivation (dashless hex, 32
  chars) for stable token serialization across backends
- Add get_ruleset_by_source composite index lookup for (domain_id,
  source)
- Add AuthenticationContext::Mapping to auth context enum with correct
  scope handling; authenticate_by_mapping emits authorization: None,
scope resolution happens at new_for_scope time by reading
VirtualUser.authorizations
- Add calculate_effective_roles early-return for mapping context to
  bypass role assignment lookup (virtual users have no role assignments)
- Add service-layer CAS retry with exponential backoff (5 retries,
  50-800ms) for delete_ruleset and delete_virtual_user
- Add allowed_domains O(1) HashSet lookup in engine with 256 cardinality
  limit
- Add AllowedDomainsTooLarge error variant replacing misleading
  DomainClaimRequired
- 39 new unit tests: 8 HMAC, 13 claim engine, 3 authenticate_by_mapping,
  6 scope resolution (domain/project/system match, mismatch, not found,
empty), 2 allowed_domains cardinality, 2 CAS retry limits, 5
CAS-on-remove

Mapping driver (openstack-keystone-mapping-driver-raft):
- Add get_ruleset_by_source with composite index key format
- Add source index maintenance on ruleset create/update/delete
- delete_ruleset_impl and delete_virtual_user_impl capture revision from
  StoreDataEnvelope and pass to Mutation::remove() with CAS
- Convert StoreError::Conflict to MappingProviderError::CasConflict in
  delete_ruleset and delete_virtual_user public methods
- Remove dead cas_upsert_virtual_user_impl (service layer handles retry)

Security improvements:
- Wrap cluster_salt with secrecy::SecretString for redaction
- Fix HMAC byte conversion bug (u64 -> u32+u16 for last 6 bytes)
- CAS prevents TOCTOU races on shadow registry upsert and deletion
- is_system immutable (cannot be added after creation via update)

Types (openstack-keystone-core-types):
- Add MappingAuthRequest, MappingContext auth types
- Add NoMatchingRule, DisabledRuleset, CasConflict,
  HmacDerivationFailed, AllowedDomainsTooLarge errors
- Add IdentitySource::to_string_key() for stable index key generation

Documentation (ADR-0020):
- Remove IdentitySource::Mtls, TlsCaResource, and mTLS use cases
@github-actions

github-actions Bot commented Jun 13, 2026

Copy link
Copy Markdown

🦢 Load Test Results

Goose Attack Report

Plan Overview

Action Started Stopped Elapsed Users
Increasing 26-06-13 19:32:22 26-06-13 19:32:24 00:00:02 0 → 4
Maintaining 26-06-13 19:32:24 26-06-13 19:32:54 00:00:30 4
Decreasing 26-06-13 19:32:54 26-06-13 19:32:54 00:00:00 0 ← 4

Request Metrics

Method Name # Requests # Fails Average (ms) Min (ms) Max (ms) RPS Failures/s
GET 6264 0 18.66 11 80 208.80 0.00
Aggregated 6264 0 18.66 11 80 208.80 0.00

Response Time Metrics

Method Name 50%ile (ms) 60%ile (ms) 70%ile (ms) 80%ile (ms) 90%ile (ms) 95%ile (ms) 99%ile (ms) 100%ile (ms)
GET 16 20 22 24 25 26 32 80
Aggregated 16 20 22 24 25 26 32 80

Status Code Metrics

Method Name Status Codes
GET 6,264 [200]
Aggregated 6,264 [200]

Transaction Metrics

Transaction # Times Run # Fails Average (ms) Min (ms) Max (ms) RPS Failures/s
ListUsers
0.0 0 0 0.00 0 0 0.00 0.00
0.1 3825 0 15.22 11 46 127.50 0.00
ValidateToken
1.0 0 0 0.00 0 0 0.00 0.00
1.1 2439 0 24.18 19 80 81.30 0.00
Aggregated 6264 0 18.66 11 80 208.80 0.00

Scenario Metrics

Transaction # Users # Times Run Average (ms) Min (ms) Max (ms) Scenarios/s Iterations
ListUsers 2 3823 15.23 11 46 127.43 1911.50
ValidateToken 2 2437 24.18 19 80 81.23 1218.50
Aggregated 4 6260 18.71 11 80 208.67 3130.00

View full report

@github-actions

github-actions Bot commented Jun 13, 2026

Copy link
Copy Markdown

🐰 Bencher Report

Branchmapping
Testbedubuntu-latest
Click to view all benchmark results
BenchmarkLatencyBenchmark Result
nanoseconds (ns)
(Result Δ%)
Upper Boundary
nanoseconds (ns)
(Limit %)
Command_Serde/apply/remove📈 view plot
🚷 view threshold
86,223.00 ns
(-41.33%)Baseline: 146,967.05 ns
485,192.41 ns
(17.77%)
Command_Serde/apply/set📈 view plot
🚷 view threshold
85,963.00 ns
(-34.97%)Baseline: 132,180.70 ns
300,096.53 ns
(28.65%)
Command_Serde/pack/delete📈 view plot
🚷 view threshold
123.67 ns
(+1.62%)Baseline: 121.70 ns
146.56 ns
(84.38%)
Command_Serde/pack/delete_index📈 view plot
🚷 view threshold
113.33 ns
(-0.49%)Baseline: 113.89 ns
137.31 ns
(82.53%)
Command_Serde/pack/set📈 view plot
🚷 view threshold
192.77 ns
(-2.23%)Baseline: 197.18 ns
241.24 ns
(79.91%)
Command_Serde/pack/set_index📈 view plot
🚷 view threshold
113.33 ns
(-0.69%)Baseline: 114.12 ns
137.25 ns
(82.57%)
Command_Serde/unpack/delete📈 view plot
🚷 view threshold
224.17 ns
(+19.75%)Baseline: 187.20 ns
229.91 ns
(97.50%)
Command_Serde/unpack/delete_index📈 view plot
🚷 view threshold
185.03 ns
(+12.30%)Baseline: 164.77 ns
205.96 ns
(89.84%)
Command_Serde/unpack/set📈 view plot
🚷 view threshold
265.28 ns
(+8.36%)Baseline: 244.81 ns
290.98 ns
(91.17%)
Command_Serde/unpack/set_index📈 view plot
🚷 view threshold
173.03 ns
(+6.43%)Baseline: 162.57 ns
203.06 ns
(85.21%)
Payload_encryption/pack/inner📈 view plot
🚷 view threshold
65.83 ns
(+5.08%)Baseline: 62.64 ns
78.33 ns
(84.04%)
Payload_encryption/pack/remove_cmd📈 view plot
🚷 view threshold
118.78 ns
(-2.09%)Baseline: 121.31 ns
156.19 ns
(76.05%)
Payload_encryption/pack/set_cmd📈 view plot
🚷 view threshold
217.06 ns
(-4.54%)Baseline: 227.37 ns
288.12 ns
(75.34%)
Payload_encryption/unpack/inner📈 view plot
🚷 view threshold
181.21 ns
(+11.35%)Baseline: 162.74 ns
191.08 ns
(94.83%)
Payload_encryption/unpack/remove_cmd📈 view plot
🚷 view threshold
226.54 ns
(+15.17%)Baseline: 196.70 ns
244.76 ns
(92.56%)
Payload_encryption/unpack/set_cmd📈 view plot
🚷 view threshold
281.23 ns
(+9.52%)Baseline: 256.77 ns
311.75 ns
(90.21%)
Raft_1Node_Latency/prefix/1node📈 view plot
🚷 view threshold
4,711,500.00 ns
(+25.87%)Baseline: 3,743,032.24 ns
6,991,269.19 ns
(67.39%)
Raft_1Node_Latency/read/1node📈 view plot
🚷 view threshold
650.54 ns
(+15.29%)Baseline: 564.27 ns
727.54 ns
(89.42%)
Raft_1Node_Latency/remove/1node📈 view plot
🚷 view threshold
241,660.00 ns
(-36.86%)Baseline: 382,716.72 ns
1,007,990.77 ns
(23.97%)
Raft_1Node_Latency/write/1node📈 view plot
🚷 view threshold
260,310.00 ns
(-29.45%)Baseline: 368,966.55 ns
798,607.11 ns
(32.60%)
build_snapshot/default📈 view plot
🚷 view threshold
95,812.00 ns
(+3.34%)Baseline: 92,718.91 ns
157,450.67 ns
(60.85%)
fernet token/project📈 view plot
🚷 view threshold
1,461.70 ns
(+1.42%)Baseline: 1,441.21 ns
1,664.73 ns
(87.80%)
get_data_keyspace📈 view plot
🚷 view threshold
0.35 ns
(+9.70%)Baseline: 0.32 ns
0.38 ns
(92.41%)
get_db📈 view plot
🚷 view threshold
0.35 ns
(+9.93%)Baseline: 0.32 ns
0.38 ns
(92.47%)
get_fernet_token_timestamp/project📈 view plot
🚷 view threshold
134.96 ns
(-8.21%)Baseline: 147.03 ns
175.36 ns
(76.96%)
get_keyspace📈 view plot
🚷 view threshold
4.80 ns
(-3.32%)Baseline: 4.97 ns
9.86 ns
(48.73%)
🐰 View full continuous benchmarking report in Bencher

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.

1 participant