Skip to content

feat(credential): Implement ADR 0019 phases 1-2#897

Merged
gtema merged 1 commit into
mainfrom
claude/adr-0019-keystone-compat-nfzp1z
Jul 2, 2026
Merged

feat(credential): Implement ADR 0019 phases 1-2#897
gtema merged 1 commit into
mainfrom
claude/adr-0019-keystone-compat-nfzp1z

Conversation

@gtema

@gtema gtema commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Foundational credentials provider (EC2/TOTP/custom blob storage):
core traits plus SQL/Fernet backend. Phases 3-9 (HTTP API routes,
OS-EC2 legacy endpoints, /v3/ec2tokens signing, TOTP auth-pipeline
integration, cascading-delete wiring, keystone-manage CLI, OPA
policies) not included.

core-types/core:

  • New credential module: Credential/CredentialCreate/
    CredentialUpdate/CredentialListParameters DTOs,
    CredentialProviderError.
  • CredentialBackend/CredentialApi traits + CredentialService: EC2 id
    = SHA-256(blob.access) computed pre-backend-call for audit events,
    UUID id otherwise, 400 on missing user_id under system scope,
    access/trust_id/app_cred_id/access_token_id in blob immutable on
    update (CVE-2020-12691).
  • Wired into ServiceState/PluginManager; new
    EventPayload::Credential variant for audit dispatch.

credential-driver-sql (new crate):

  • SeaORM entity for credential table. Per ADR 0019 the table is
    owned exclusively by Python Keystone's alembic migrations, so
    SqlDriver::setup() is a deliberate no-op unlike every other SQL
    driver here. test_support creates the table for this crate's tests
    only.
  • fernet.rs: setup/load/rotate per corrected ADR 0019 rotation —
    staged key 0 promoted by rename to old_primary + 1, outgoing
    primary kept for decryption, fresh key staged, files beyond
    MAX_ACTIVE_KEYS (3, hardcoded) pruned oldest-first. key_hash =
    SHA-1 over raw base64url key-file bytes (not decoded key), matching
    keystone/credential/providers/fernet/core.py. Null Key detection
    refuses to load unless insecure_allow_null_key set.
  • CRUD backend (create/get/list/update/delete, delete-for-user/
    -project, EC2 access-key lookup via SHA-256 hash); reads
    [credential] config fresh per call via
    ServiceState.config_manager, matching identity-driver-sql.

Config: new [credential] section (driver, key_repository default
/etc/keystone/credential-keys/, insecure_allow_null_key).

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
Signed-off-by: Artem Goncharov artem.goncharov@gmail.com

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

🦢 Load Test Results

Goose Attack Report

Plan Overview

Action Started Stopped Elapsed Users
Increasing 26-07-02 08:58:28 26-07-02 08:58:38 00:00:10 0 → 20
Maintaining 26-07-02 08:58:38 26-07-02 08:59:08 00:00:30 20
Decreasing 26-07-02 08:59:08 26-07-02 09:00:05 00:00:57 0 ← 20

Request Metrics

Method Name # Requests # Fails Average (ms) Min (ms) Max (ms) RPS Failures/s
DELETE DELETE /v3/auth/tokens 807 0 66.02 10 97 26.90 0.00
DELETE DELETE /v3/projects/:id (teardown) 2 0 30.00 23 37 0.07 0.00
DELETE DELETE /v3/users/:id (teardown) 3 0 36.67 33 40 0.10 0.00
GET 4864 0 61.39 41 121 162.13 0.00
GET GET /v3/auth/tokens (validate new) 804 1 140.33 48 60001 26.80 0.03
GET GET /v3/projects/:id 1180 0 50.52 41 68 39.33 0.00
GET GET /v3/users/:id 1647 0 54.27 47 82 54.90 0.00
POST POST /v3/auth/tokens 802 0 49.39 41 73 26.73 0.00
Aggregated 10109 1 64.64 10 60001 336.97 0.03

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)
DELETE DELETE /v3/auth/tokens 66 67 67 68 70 72 82 97
DELETE DELETE /v3/projects/:id (teardown) 23 23 23 37 37 37 37 37
DELETE DELETE /v3/users/:id (teardown) 37 37 37 37 40 40 40 40
GET 54 58 60 68 95 97 100 120
GET GET /v3/auth/tokens (validate new) 66 66 67 68 70 71 78 60,000
GET GET /v3/projects/:id 50 51 52 52 54 56 61 68
GET GET /v3/users/:id 54 55 55 56 58 59 65 82
POST POST /v3/auth/tokens 49 50 50 51 53 54 58 73
Aggregated 54 57 60 65 73 95 100 60,000

Status Code Metrics

Method Name Status Codes
DELETE DELETE /v3/auth/tokens 807 [204]
DELETE DELETE /v3/projects/:id (teardown) 2 [204]
DELETE DELETE /v3/users/:id (teardown) 3 [204]
GET 4,864 [200]
GET GET /v3/auth/tokens (validate new) 1 [0], 803 [200]
GET GET /v3/projects/:id 1,180 [200]
GET GET /v3/users/:id 1,647 [200]
POST POST /v3/auth/tokens 802 [200]
Aggregated 9,296 [200], 812 [204], 1 [0]

Transaction Metrics

Transaction # Times Run # Fails Average (ms) Min (ms) Max (ms) RPS Failures/s
ReadHeavy
0.0 1 0 23.00 23 23 0.03 0.00
0.1 1307 0 59.62 45 86 43.57 0.00
0.2 1308 0 50.17 42 68 43.60 0.00
0.3 1304 0 50.09 42 70 43.47 0.00
TokenLifecycle
1.0 0 0 0.00 0 0 0.00 0.00
1.1 807 0 256.63 117 60065 26.90 0.00
ValidateToken
2.0 0 0 0.00 0 0 0.00 0.00
2.1 945 0 95.24 57 121 31.50 0.00
UserCRUD
3.0 0 0 0.00 0 0 0.00 0.00
3.1 0 0 0.00 0 0 0.00 0.00
3.2 1647 0 54.33 47 83 54.90 0.00
3.3 3 0 36.67 33 40 0.10 0.00
ProjectCRUD
4.0 0 0 0.00 0 0 0.00 0.00
4.1 0 0 0.00 0 0 0.00 0.00
4.2 1180 0 50.59 41 68 39.33 0.00
4.3 2 0 30.00 23 37 0.07 0.00
Aggregated 8504 0 76.84 23 60065 283.47 0.00

Scenario Metrics

Transaction # Users # Times Run Average (ms) Min (ms) Max (ms) Scenarios/s Iterations
ReadHeavy 7 1304 160.91 143 191 43.47 186.29
TokenLifecycle 5 802 182.62 162 217 26.73 160.40
ValidateToken 3 942 95.29 82 121 31.40 314.00
UserCRUD 3 1644 54.34 47 83 54.80 548.00
ProjectCRUD 2 1178 50.61 42 68 39.27 589.00
Aggregated 20 5870 101.36 42 217 195.67 1797.69

Error Metrics

Method Name # Error
GET GET /v3/auth/tokens (validate new) 1 error sending request GET /v3/auth/tokens (validate new): operation timed out

View full report

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

🐰 Bencher Report

Branchclaude/adr-0019-keystone-compat-nfzp1z
Testbedubuntu-latest

🚨 1 Alert

BenchmarkMeasure
Units
ViewBenchmark Result
(Result Δ%)
Upper Boundary
(Limit %)
Command_Serde/unpack/delete_indexLatency
nanoseconds (ns)
📈 plot
🚷 threshold
🚨 alert (🔔)
197.29 ns
(+22.46%)Baseline: 161.10 ns
196.39 ns
(100.46%)

Click to view all benchmark results
BenchmarkLatencyBenchmark Result
nanoseconds (ns)
(Result Δ%)
Upper Boundary
nanoseconds (ns)
(Limit %)
Command_Serde/apply/remove📈 view plot
🚷 view threshold
164,060.00 ns
(-41.49%)Baseline: 280,376.14 ns
1,684,691.51 ns
(9.74%)
Command_Serde/apply/set📈 view plot
🚷 view threshold
151,990.00 ns
(-34.22%)Baseline: 231,071.44 ns
981,751.11 ns
(15.48%)
Command_Serde/pack/delete📈 view plot
🚷 view threshold
120.56 ns
(-0.05%)Baseline: 120.62 ns
140.74 ns
(85.66%)
Command_Serde/pack/delete_index📈 view plot
🚷 view threshold
112.57 ns
(+2.28%)Baseline: 110.06 ns
128.38 ns
(87.69%)
Command_Serde/pack/set📈 view plot
🚷 view threshold
193.38 ns
(-0.03%)Baseline: 193.43 ns
229.05 ns
(84.43%)
Command_Serde/pack/set_index📈 view plot
🚷 view threshold
113.09 ns
(+2.93%)Baseline: 109.88 ns
127.74 ns
(88.53%)
Command_Serde/unpack/delete📈 view plot
🚷 view threshold
233.63 ns
(+20.94%)Baseline: 193.17 ns
233.80 ns
(99.93%)
Command_Serde/unpack/delete_index📈 view plot
🚷 view threshold
🚨 view alert (🔔)
197.29 ns
(+22.46%)Baseline: 161.10 ns
196.39 ns
(100.46%)

Command_Serde/unpack/set📈 view plot
🚷 view threshold
273.45 ns
(+4.48%)Baseline: 261.73 ns
321.83 ns
(84.97%)
Command_Serde/unpack/set_index📈 view plot
🚷 view threshold
164.85 ns
(+3.27%)Baseline: 159.63 ns
193.77 ns
(85.07%)
Payload_encryption/pack/remove_cmd📈 view plot
🚷 view threshold
116.94 ns
(-1.08%)Baseline: 118.22 ns
150.87 ns
(77.51%)
Payload_encryption/pack/set_cmd📈 view plot
🚷 view threshold
231.57 ns
(+12.41%)Baseline: 206.01 ns
270.08 ns
(85.74%)
Payload_encryption/unpack/remove_cmd📈 view plot
🚷 view threshold
208.96 ns
(+1.83%)Baseline: 205.20 ns
246.33 ns
(84.83%)
Payload_encryption/unpack/set_cmd📈 view plot
🚷 view threshold
290.45 ns
(+5.92%)Baseline: 274.22 ns
337.48 ns
(86.06%)
Raft_1Node_Latency/prefix/1node📈 view plot
🚷 view threshold
2,435,900.00 ns
(-14.14%)Baseline: 2,837,166.72 ns
5,737,702.11 ns
(42.45%)
Raft_1Node_Latency/read/1node📈 view plot
🚷 view threshold
42,822.00 ns
(+471.56%)Baseline: 7,492.15 ns
44,235.97 ns
(96.80%)
Raft_1Node_Latency/remove/1node📈 view plot
🚷 view threshold
411,910.00 ns
(-24.12%)Baseline: 542,833.12 ns
2,292,366.03 ns
(17.97%)
Raft_1Node_Latency/write/1node📈 view plot
🚷 view threshold
415,250.00 ns
(-26.12%)Baseline: 562,057.81 ns
2,124,856.65 ns
(19.54%)
build_snapshot/default📈 view plot
🚷 view threshold
111,710.00 ns
(+6.66%)Baseline: 104,730.22 ns
161,605.99 ns
(69.12%)
fernet token/project📈 view plot
🚷 view threshold
1,419.20 ns
(+2.11%)Baseline: 1,389.84 ns
1,610.77 ns
(88.11%)
get_data_keyspace📈 view plot
🚷 view threshold
0.31 ns
(-0.31%)Baseline: 0.32 ns
0.37 ns
(85.63%)
get_db📈 view plot
🚷 view threshold
0.32 ns
(+1.05%)Baseline: 0.32 ns
0.37 ns
(86.91%)
get_fernet_token_timestamp/project📈 view plot
🚷 view threshold
143.66 ns
(-1.31%)Baseline: 145.56 ns
179.35 ns
(80.10%)
get_keyspace📈 view plot
🚷 view threshold
4.43 ns
(-6.16%)Baseline: 4.72 ns
8.75 ns
(50.66%)
🐰 View full continuous benchmarking report in Bencher

Foundational credentials provider (EC2/TOTP/custom blob storage):
core traits plus SQL/Fernet backend. Phases 3-9 (HTTP API routes,
OS-EC2 legacy endpoints, /v3/ec2tokens signing, TOTP auth-pipeline
integration, cascading-delete wiring, keystone-manage CLI, OPA
policies) not included.

core-types/core:
- New `credential` module: Credential/CredentialCreate/
  CredentialUpdate/CredentialListParameters DTOs,
  CredentialProviderError.
- CredentialBackend/CredentialApi traits + CredentialService: EC2 id
  = SHA-256(blob.access) computed pre-backend-call for audit events,
  UUID id otherwise, 400 on missing user_id under system scope,
  access/trust_id/app_cred_id/access_token_id in blob immutable on
  update (CVE-2020-12691).
- Wired into ServiceState/PluginManager; new
  EventPayload::Credential variant for audit dispatch.

credential-driver-sql (new crate):
- SeaORM entity for `credential` table. Per ADR 0019 the table is
  owned exclusively by Python Keystone's alembic migrations, so
  SqlDriver::setup() is a deliberate no-op unlike every other SQL
  driver here. test_support creates the table for this crate's tests
  only.
- fernet.rs: setup/load/rotate per corrected ADR 0019 rotation —
  staged key `0` promoted by rename to `old_primary + 1`, outgoing
  primary kept for decryption, fresh key staged, files beyond
  MAX_ACTIVE_KEYS (3, hardcoded) pruned oldest-first. key_hash =
  SHA-1 over raw base64url key-file bytes (not decoded key), matching
  keystone/credential/providers/fernet/core.py. Null Key detection
  refuses to load unless insecure_allow_null_key set.
- CRUD backend (create/get/list/update/delete, delete-for-user/
  -project, EC2 access-key lookup via SHA-256 hash); reads
  `[credential]` config fresh per call via
  ServiceState.config_manager, matching identity-driver-sql.

Config: new `[credential]` section (driver, key_repository default
/etc/keystone/credential-keys/, insecure_allow_null_key).

Assisted-By-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Artem Goncharov <artem.goncharov@gmail.com>
@gtema gtema force-pushed the claude/adr-0019-keystone-compat-nfzp1z branch from 544900c to e5ead2a Compare July 2, 2026 08:47
@gtema gtema merged commit 94a5001 into main Jul 2, 2026
31 checks passed
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