docs: ADR-0038 — EWBI auth drift from mTLS to OAuth2 Client-Credentials (Keycloak federation realm); mTLS deferred

Summary

Corrects a spec drift in the Federation Manager E/WBI authorization model: mTLS is no longer the primary mechanism — OAuth2 Client-Credentials + signed JWT is, per GSMA OPG.04 v6.0 Sec. 9. mTLS is reclassified as deferred defense-in-depth (sender-constrained tokens, RFC 8705) until real Nginx mTLS termination exists.

Introduces ADR-0038 and propagates the correction across the FM and platform architecture docs. The persistence model is excluded from this MR — it is WIP on a separate branch.


The drift

ADR-0016 (FM stack) assumed in passing that E/WBI authorization is mTLS with pre-exchanged client certificates, terminated by Nginx, with FM validating the client-cert thumbprint against partner_ops. Checked against the actual normative artifact — GSMA OPG.04 v6.0 — that does not match:

  • OPG.04 Sec. 9 mandates OAuth2 Client-Credentials + signed JWT (JWS, RFC 7515) for E/WBI authorization. The OpenAPI 3.0.3 contract (OPG.04_EWBI_Federation_API_v1.4.0.yaml) declares a single root-level security requirement (oAuth2ClientCredentials: [fed-mgmt]) covering all 74 operations, with no mTLS securityScheme and zero per-operation overrides.
  • Sec. 9.2 assigns roles (Partner OP = Authorization + Resource Server; Originating OP = Client); Sec. 9.3 mandates signed JWT; Sec. 9.5 requires the same grant for callbacks.
  • Sec. 9's lone "if OAuth2 is used" hedge is internally contradictory with the rest of Sec. 9 and the contract itself — read as a drafting slip, not real optionality.
  • OPG.02 v10.0 Sec. 4.2.3 / 3.3.4.5 do sanction certificate-based mutual authentication for OP-to-OP connection identity — legitimate, but a different layer than Sec. 9's per-request authorization. The two stack; they don't substitute.

On the implementation side: FM does not implement OAuth2/JWT for E/WBI today, and the Nginx mTLS terminator ADR-0016 assumed does not exist yet — today's cert-thumbprint header is not derived from any real TLS client-certificate handshake, so it proves no identity cryptographically. It is a partner lookup, not authentication, and cannot satisfy REQ-FED-01 on its own.

The platform already runs a centralized Keycloak (core Compose profile) serving OEG's NBI auth and FM's own internal admin routes (realm oop). This ADR extends that existing pattern with a dedicated federation realm — partner OPs are an external, adversarial-by-default trust domain, isolated from internal/admin identity.

Decision (ADR-0038)

  1. E/WBI authorization = OAuth2 Client-Credentials + signed JWT via the platform's centralized Keycloak, federation realm. Each partner gets its own Keycloak client (fed-mgmt scope; fed-mgmt-notif for callbacks). FM is both Client (outbound) and Authorization+Resource Server (inbound).
  2. mTLS is not deleted — reclassified as deferred defense-in-depth. partner_ops.cert_thumbprint stays as a future hook (RFC 8705 sender-constrained tokens once real Nginx mTLS termination lands); removed from today's authorization decision path.
  3. Signing = JWS (RFC 7515), matching Keycloak's default and OPG.04 Sec. 9.3's primary recommendation — not the MAC alternative, which only earns its keep absent a real Authorization Server. There is one.

What changes in the docs

File Change
decisions/0038-ewbi-oauth2-client-credentials-mtls-deferred.md New ADR.
decisions/index.md ADR-0038 registered (ADR-0037 is canonical-target-ports on develop — renumbered from 0037 to avoid the collision).
fm/requirements-and-design.md REQ-FED-01 rewritten: OAuth2 Client-Credentials + JWT is the requirement; mTLS is deferred. Spec refs corrected.
fm/overview-and-architecture.md EWBI termination + partner-auth positions updated; assumptions F2/F5/F6 revised (F6 elevated to high risk — the federation realm is now the primary EWBI mechanism, with no fallback).
fm/implementation-and-delivery.md §P.3 rewritten from mTLS to OAuth2 (inbound JWKS/JWT validation; outbound token acquisition + cache); mTLS moved to new deferred §P.3.1. Boundary + threat tables updated.
platform/architecture.md FM EWBI gateway pattern + platform boundary/role tables updated.
platform/future-architecture.md Federation-partner auth row updated.

fm/persistence-model.md is deliberately not in this MR — it is WIP on add-fm-persistent-model. The ADR-0038 Security-Design alignment for the persistence model will land with that branch.

This MR is the architecture-side resolution of the FM E/WBI auth drift, tracked in the oop/code/federation-manager repo:

⚠️ Open tension to flag for reviewers

ADR-0038 chooses a centralized Keycloak federation realm (platform-shared infrastructure) as the Authorization Server. Issue #18 carries a decoupling constraint: "Auth is self-contained to FM; it must not rely on [...] shared platform infrastructure beyond what FM itself runs" and "No hard coupling to any other component's identity provider; FM validates partner tokens against the partner's advertised issuer/authz server."

These two positions are not obviously reconcilable:

  • The ADR's model has FM validate inbound partner tokens against the platform's Keycloak (federation realm) JWKS — i.e., FM as Resource Server trusts its own platform's AS.
  • Issue #18's model has FM validate against the partner's advertised issuer — i.e., cross-realm trust / federation, where each OP runs its own AS and they trust each other's JWTs.

Both are defensible readings of OPG.04 Sec. 9 (Sec. 9.2 assigns both Client and AS roles to each OP for its own domain). The difference is whether the inbound Authorization Server is the local platform Keycloak (ADR-0038) or the partner's remote AS (issue #18). This needs a decision before #18's implementation finalizes, so the architecture and the implementation agree. I've left the ADR accepted on the architecture side but flagged this here so it is reviewed consciously.

Commits

c0f9fea docs: propagate ADR-0038 to platform architecture + future-architecture
78dcdf3 docs: align FM implementation-and-delivery EWBI auth to ADR-0038
6dbfe4a docs: align FM overview-and-architecture to ADR-0038
497e93c docs: align FM REQ-FED-01 to ADR-0038 (OAuth2 over deferred mTLS)
ff414a3 feat: add ADR-0038 — EWBI auth is OAuth2 via Keycloak; mTLS deferred

Merge request reports

Loading
+6 −7
Changes for decisions/0016-fm-python-fastapi-stack.md: 6 added lines, 7 removed lines.
Original line number Diff line number Diff line
@@ -27,15 +27,14 @@ exception; (b) FM follows the platform stack — a single Python/FastAPI service

Option (b). FM is one Python/FastAPI service like every other OOP component, following the same
hexagonal `python-template` layout and importing oop-commons. Nginx (already in front of every
service) terminates TLS/mTLS and forwards the client certificate to FM in a header; FM middleware
validates the certificate thumbprint. Outbound EWBI mTLS uses `httpx` with per-partner client
certificates. There is no Envoy sidecar and no Go control plane.
service) terminates HTTPS and routes the canonical EWBI path to FM. OAuth2 Client-Credentials is
the current EWBI authorization mechanism (ADR-0042); `httpx` handles outbound token acquisition
and Bearer-authenticated partner calls. There is no Envoy sidecar and no Go control plane.

## Consequences

Single-language stack: shared Pydantic models, shared auth/logging/health middleware, shared
developer knowledge; one deployable, one scaling model (stateless horizontal pods). Cost: FM does
not get Envoy's battle-tested mTLS/filter machinery — certificate handling and header stripping
are FM application code and must be covered by the topology-leakage and certificate-validation
test suites. Revisit trigger: EWBI traffic or mTLS policy complexity (revocation, SPIFFE-style
rotation) outgrowing what Nginx + FastAPI middleware can express.
not get Envoy's policy/filter machinery, so Bearer-token validation and header stripping are FM
application code and require security tests. If mTLS sender-binding is added later (ADR-0042),
revisit whether its revocation and rotation policy still fits Nginx + FastAPI.
+48 −0
Changes for decisions/0042-ewbi-oauth2-client-credentials-mtls-deferred.md: 48 added lines, 0 removed lines.
Original line number Diff line number Diff line
---
type: Decision Record
title: "ADR-0042: EWBI authorization is OAuth2 Client-Credentials; mTLS is deferred defense-in-depth"
description: "FM authorizes E/WBI requests with OAuth2 Client-Credentials + signed JWT (OPG.04 Sec. 9); our Keycloak issues inbound tokens, each partner's Authorization Server issues outbound tokens, and mTLS is deferred"
tags: [adr, fm, federation, security]
timestamp: 2026-08-06T00:00:00Z
status: "accepted"
---

# ADR-0042: EWBI authorization is OAuth2 Client-Credentials; mTLS is deferred defense-in-depth

**Status:** accepted
**Date:** 2026-08-06
**Affects:** [fm/requirements-and-design.md](/fm/requirements-and-design.md), [fm/overview-and-architecture.md](/fm/overview-and-architecture.md), [fm/implementation-and-delivery.md](/fm/implementation-and-delivery.md), [fm/persistence-model.md](/fm/persistence-model.md), [platform/architecture.md](/platform/architecture.md), [platform/future-architecture.md](/platform/future-architecture.md), [ADR-0016](0016-fm-python-fastapi-stack.md)

## Context

FM's specs (REQ-FED-01, ADR-0016) currently describe mTLS with pre-exchanged client certificates as *the* E/WBI authorization mechanism: Nginx terminates mTLS and forwards the client-certificate thumbprint, FM looks it up in `partner_ops`.

Checked against GSMA OPG.04 v6.0's actual normative artifact — the OpenAPI 3.0.3 contract GSMA publishes alongside the PRD (Annex A; distributed separately as `OPG.04_EWBI_Federation_API_v1.4.0.yaml`), not just the PDF prose — the E/WBI Federation Management Service declares one root-level `security` requirement covering all 74 operations, with zero per-operation overrides and no mTLS securityScheme anywhere in the document:

```yaml
security:
  - oAuth2ClientCredentials: [fed-mgmt]
  - notifClientCredentials: [fed-mgmt-notif]
```

Sec. 9.2 assigns roles (Partner OP = Authorization + Resource Server, Originating OP = Client); Sec. 9.3 mandates signed JWT (JWS, RFC 7515); Sec. 9.5 requires the same grant for callbacks. Sec. 9's opening paragraph has one internally-contradictory hedge ("if OAuth2 is used") against otherwise-unconditional language throughout the rest of Sec. 9 and the contract itself — read as a drafting slip, not real optionality.

GSMA OPG.02 v10.0 Sec. 4.2.3 / Sec. 3.3.4.5 separately and explicitly sanctions certificate-based mutual authentication ("commonly used API authentication mechanisms, e.g. Public Key Certificates") for OP-to-OP *connection* identity — legitimate, but a different layer than Sec. 9's per-request *authorization*, and not what OPG.04's contract checks.

FM does not currently implement OAuth2/JWT for E/WBI at all. Real Nginx mTLS termination also does not exist yet — today's cert-thumbprint header is not derived from any actual TLS client-certificate handshake, so it proves no identity cryptographically; it is a partner lookup, not authentication.

The platform already runs a centralized Keycloak as core shared infrastructure (`core` Compose profile: PostgreSQL, Redis, NATS, Keycloak, Nginx; `platform/architecture.md` Security Architecture table), currently serving OEG's NBI auth and FM's own internal admin routes (both realm `oop`). This ADR extends that existing pattern rather than introducing a new one.

## Decision

1. **E/WBI authorization is OAuth2 Client-Credentials + signed JWT (OPG.04 Sec. 9).** For inbound calls, the partner is a client of our platform's centralized Keycloak, using the dedicated `federation` realm; FM validates the resulting token. For outbound calls, FM is a client of the partner OP's Authorization Server and obtains the token from that partner's token endpoint. The local `federation` realm stays separate from the `oop` realm used for internal admin and app-provider authentication. Each partner gets its own local Keycloak client with the `fed-mgmt` scope (`fed-mgmt-notif` for callbacks).
2. **mTLS is not built and is reclassified as deferred defense-in-depth.** No certificate thumbprint or client-certificate path is retained in today's `partner_ops` schema. When sender-constrained tokens are designed (for example RFC 8705), the required trust and certificate fields will be introduced with that design. Today's caller-supplied thumbprint header proves no identity and is removed completely.
3. Signing is JWS (RFC 7515), matching Keycloak's default and OPG.04 Sec. 9.3's primary recommendation — not the Sec. 9.3 pre-shared-symmetric-key (MAC) alternative, which only earns its keep when there is no real Authorization Server to lean on. There is one.

## Consequences

FM gains a real dependency on the centralized Keycloak for inbound EWBI authorization (already a platform dependency for OEG and FM's own admin routes, so no new infrastructure). FM must implement inbound Bearer-JWT validation (signature via Keycloak's JWKS, issuer, expiry, `fed-mgmt` scope) and outbound Client-Credentials token acquisition and caching before calling a partner. `partner_ops` stores the partner's inbound client identity plus our outbound client ID, token endpoint, and a reference to the mounted secret; it stores no OAuth client secret inline.

Cost: FM does not get to skip Authorization Server work by leaning on Nginx alone — Nginx's role here stays limited to HTTPS termination and routing, not the authorization decision. OPG.04 Sec. 9 requires TLS and OAuth2 but defines no mTLS OpenAPI security scheme. If the project later adds RFC 8705 sender-binding, that is a separate project decision grounded in OPG.02 Sec. 4.2.3/3.3.4.5, not a claim that OPG.04 currently mandates mTLS.

This ADR does not change FM's stack choice (ADR-0016 stands); it corrects only the E/WBI authorization mechanism ADR-0016 assumed in passing.
+1 −0
Changes for decisions/index.md: 1 added line, 0 removed lines.
Original line number Diff line number Diff line
@@ -44,3 +44,4 @@ the reasoning lives here. Never renumber; supersede instead (see the record-deci
* [ADR-0039](0039-source-spec-stated-by-publisher.md) - source_spec is stated by the publishing component (OEG from CAMARA, FM from OPG.04) on network capability commands; SRM never derives it from the envelope source and never synthesizes a family, API, or version
* [ADR-0040](0040-target-serveability-is-a-tri-state.md) - can_serve_target answers can_serve | unknown_subscriber | unsupported_identifier; a boolean cannot carry the 404/422 split §E.2 requires, and 404 needs positive evidence (supersedes ADR-0038's boolean)
* [ADR-0041](0041-tfsdk-owns-adapter-construction.md) - TF-SDK builds adapters behind a factory keyed by adapter kind; SRM resolves control_path_ref to an instance but never enumerates adapter kinds
* [ADR-0042](0042-ewbi-oauth2-client-credentials-mtls-deferred.md) - E/WBI authorization is OAuth2 Client-Credentials: local Keycloak for inbound, partner Authorization Server for outbound; mTLS deferred
+147 −80

File changed.

Preview size limit exceeded, changes collapsed.

+98 −40

File changed.

Preview size limit exceeded, changes collapsed.

Loading
Loading