feat: validate Keycloak tokens and enforce CAMARA roles, scopes and consent per operation

What this does

OEG now checks the Keycloak access token on every CAMARA route (QoD, Edge Application Management, Location Retrieval) and enforces what each operation's CAMARA spec declares.

Token validation (core/auth/)

  • Reads Authorization: Bearer <JWT> and checks the signature against Keycloak's JWKS, with the algorithm fixed to RS256. It also checks iss, aud (default oeg) and exp.
  • Signing keys are cached per kid (5-minute lifetime, refetched when an unknown kid arrives) and loaded once at startup.
  • The caller context is built from the token: tenant from organization, roles from realm_access.roles, and subject from sub. Two-legged vs three-legged is detected from auth_time.

Authorization per operation (core/authorization.py)

  • One table, OPERATION_REQUIREMENTS, lists the required role, CAMARA scope and three-legged requirement for every route.
    • All routes require the app-provider role.
    • Location Retrieval always requires a three-legged (user-consented) token.
    • QoD: two-legged tokens don't need a scope, as the spec allows.
    • EAM and Location Retrieval always need their scope.
  • A route with no entry in the table fails closed: a server error, never silently allowed through.

Error mapping

  • 401: missing or invalid token, or missing required claims.
  • 403: wrong role, missing scope, or a two-legged token where consent is required.
  • 503: Keycloak's JWKS can't be reached (the caller did nothing wrong).
  • /platform/readyz now also reports JWKS health.

Rollout switch

KEYCLOAK_SETTINGS__ENFORCEMENT_ENABLED=false logs auth_would_reject / authz_would_reject instead of rejecting. It's meant only for a supervised migration window; the default is true.

Also included

  • Location Retrieval receives the caller's subject_id for device lookup. For now this is the raw sub.
  • New dependency: PyJWT.

Configuration

Env var Notes
KEYCLOAK_SETTINGS__ISSUER Must equal Keycloak's public URL + /realms/oop (what Keycloak writes into iss), not the in-cluster service name
KEYCLOAK_SETTINGS__JWKS_URL In-cluster URL, e.g. http://keycloak:8080/realms/oop/protocol/openid-connect/certs
KEYCLOAK_SETTINGS__AUDIENCE Default oeg
KEYCLOAK_SETTINGS__ENFORCEMENT_ENABLED Default true

The defaults point to *.invalid addresses on purpose, so a deployment missing this configuration fails loudly instead of trusting a guessed URL.

Not in this PR

  • The GSMA federation routes (/platform/v1/federation/*) don't have a role check yet. A follow-up will require the operator role on them. OEG → FM stays protected by network policy only.

Merge request reports

Loading
Loading