OpenKeel: the sovereign French Cloud solution of the inherent group, with a European reach Discover inherent

Reverse-spec · Libraries

keystonemiddleware — Rewrite pack

Client contract and Go rewrite — keystonemiddleware — branch stable/2026.2

Branch stable/2026.2 @ 3a8a835bb573 — repository https://opendev.org/openstack/keystonemiddleware — analysed on 2026-10-28 Service used for the proof: Keystone stable/2026.2 @ e6dc5ae079e6 (../reverse-spec-keystone/), in-process; oslo.context 6.5.0 (oslo-context:) for the reading of headers

This file has two purposes:

  • state what keystonemiddleware requires from Keystone, so that OpenKeel Keystone is accepted by the Python services that remain;
  • state what an equivalent Go middleware (for the OpenKeel services) must reproduce, so that the identity handed to the services stays the same.

1. Contents of _rewrite/

FileContent
[exchanges_stable-2026.2.json](_rewrite/exchanges_stable-2026.2.json)10 scenarios (expected, observed, statuses and headers seen by the service) and the 44 calls of the middleware to Keystone
[response-schemas_stable-2026.2.json](_rewrite/response-schemas_stable-2026.2.json), [response-schemas_stable-2026.2.md](_rewrite/response-schemas_stable-2026.2.md)schemas of the Keystone responses read by the middleware (GET /v3/auth/tokens…)
[observed-errors_stable-2026.2.md](_rewrite/observed-errors_stable-2026.2.md)errors seen on the Keystone side (404 for invalid tokens)

How it was produced: conformance_capture.py scenarios with the keystonemiddleware adapter.

  • The same throw-away Keystone as for keystoneauth.
  • auth_token in front of an "echo" application that returns the headers received.
  • Ordinary users (member, service) are created through the API.

Result: 10/10 as expected.

2. Scenarios

#ScenarioObserved
1valid-user-token-headers200; X-Identity-Status: Confirmed; X-Roles: member,reader (implied role expanded); X-Is-Admin-Project: True (no admin project configured in Keystone, so every project counts as one)
2no-token-rejected401, WWW-Authenticate: Keystone uri="http://keystone.test/v3", JSON body
3invalid-token-rejectedKeystone 404, then 401 to the caller
4delay-auth-decision-marks-invalid200 to the service with X-Identity-Status: Invalid
5spoofed-identity-headers-replacedX-Roles: admin, X-Service-Roles and X-Is-Admin-Project sent by the client are removed or replaced
6spoofed-system-scope-header-with-delay-auth**OpenStack-System-Scope: all sent by the client reaches the service** when the token is invalid and delay_auth_decision = true (see § 5)
7service-token-headersX-Service-Identity-Status: Confirmed, X-Service-Roles: service
8service-token-without-service-role401 (service_token_roles_required = true)
9revoked-token-and-middleware-cachebefore revocation 200; after, same middleware 200 (cache); fresh middleware 401
10application-credential-access-rulesauthorised path 200; other path 401; without service_type 401

3. What keystonemiddleware requires from Keystone

RequirementSource
GET /v3/auth/tokens with X-Subject-Token: 200 + token body, 404 for every invalid token. A 401 means "your own token is invalid" (the middleware re-authenticates once, then answers 503 to its callers)keystonemiddleware/auth_token/_identity.py:130-170
supports ?allow_expired=1 and ?nocatalogkeystonemiddleware/auth_token/_identity.py:48-58
honours OpenStack-Identity-Access-Rules: 1: tokens from application credentials with access rules are only returned to middlewares that send itkeystonemiddleware/auth_token/_identity.py:21; Keystone side: keystone:keystone/token/provider.py:256-275
token body: roles already expanded (implied roles), application_credential.access_rules, catalog (for access rules and X-Service-Catalog), is_admin_project, system, expires_at, user, projectkeystonemiddleware/auth_token/__init__.py:543-590, keystonemiddleware/auth_token/_request.py:61-201
version discovery on /v3, and POST /v3/auth/tokens for its own authentication (keystoneauth contract)../reverse-spec-keystoneauth/07-rewrite-pack_stable-2026.2.md

4. What a Go middleware for the OpenKeel services must reproduce

  1. Remove every client-supplied identity header before validating (the list of keystonemiddleware/auth_token/_request.py:206-227), **including OpenStack-System-Scope** (see § 5).
  2. Read X-Auth-Token, or X-Storage-Token for Swift, and X-Service-Token. Validate them through GET /v3/auth/tokens with the middleware's own token, OpenStack-Identity-Access-Rules: 1, and allow_expired=1 when a service token with the service role is present.
  3. Cache the valid validations (TTL token_cache_time, 300 s), keyed by sha256(token), with optional HMAC or encryption. Never cache an invalid token. Document the revocation delay this implies, or make it configurable (0 = no cache).
  4. Check expiry (unless allow_expired), access rules (same *, ** and {x} grammar), and optionally the bind.
  5. Inject the headers of the table in 01 § 2, with the same names and formats: comma-separated roles, True/False, Confirmed/Invalid, the catalog as JSON with the v3 catalog normalised as _normalize_catalog does (keystonemiddleware/auth_token/_request.py).
  6. On rejection: 401 with the same JSON body and WWW-Authenticate: Keystone uri="…". If Keystone is unavailable: 503 "The Keystone service is temporarily unavailable.". With delay_auth_decision: let the request through, marked Invalid.
  7. Read the same [keystone_authtoken] options (01 § 3) so that deployment tools keep working.
  8. Optionally: audit (CADF audit.http.request / audit.http.response), OAuth2 Bearer, and mTLS (x5t#S256 thumbprint).

Conformance: run the 10 scenarios against the Go middleware placed in front of the same echo application, with the same Keystone. Expected: the same statuses and the same headers, except § 5 where the Go version must not let the header through.

5. Security point found: OpenStack-System-Scope not removed

remove_auth_headers removes the X-* identity headers, but **not OpenStack-System-Scope**: _all_auth_headers does not list it (keystonemiddleware/auth_token/_request.py:206-227), although _set_auth_headers writes it for valid tokens (keystonemiddleware/auth_token/_request.py:158-164).

  • With a valid token, the value is overwritten by the token's scope.
  • With an invalid token and delay_auth_decision = true, the header sent by the client reaches the service (scenario 6, observed).

oslo.context builds system_scope from HTTP_OPENSTACK_SYSTEM_SCOPE (oslo-context:oslo_context/context.py:53). A service using delay_auth_decision that builds its context **without checking X-Identity-Status** would therefore see system_scope = all for an anonymous request.

  • The impact is limited: X-Roles is removed, so rules like role:admin and system_scope:all fail.
  • A rule based only on system_scope:all would pass.

[inferred: impact depends on each service; to report upstream.]

OpenKeel requirement: remove this header like the others.

6. Gaps

GapImpact
external_oauth2_token, oauth2_token, oauth2_mtls_token and audit not runrequirements read from the code; need an OAuth2 server, mTLS and a bus
memcached and ENCRYPT/MAC modes not exercised (per-process cache)same contract, other storage
expired user token with a service token: allow_expired=1 observed only once, the token was not actually expiredtest with a short [token]/expiration in Keystone

Source: reverse-spec-keystonemiddleware/07-rewrite-pack_stable-2026.2.md. The _rewrite/ and _inventory/ files are not published on the site.