Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
863bdb8
docs: add ADR-0009 for Akrites CDP public API authentication (CM-1333)
joanagmaia Jul 15, 2026
31719fc
Merge branch 'main' into docs/CM-1333-akrites-cdp-auth-adr
joanagmaia Jul 15, 2026
a8c185e
docs: refine ADR-0009 for Akrites CDP auth (CM-1333)
joanagmaia Jul 21, 2026
2e01746
Merge branch 'main' into docs/CM-1333-akrites-cdp-auth-adr
joanagmaia Jul 21, 2026
70f1c0b
docs: address ADR-0009 review feedback (CM-1333)
joanagmaia Jul 21, 2026
2bb785c
docs: address ADR-0009 review comments on route/router refactor and A…
joanagmaia Jul 21, 2026
251453a
docs: switch ADR-0009 to claim-based isolation via Akrites-namespaced…
joanagmaia Jul 22, 2026
a2ffa93
Merge remote-tracking branch 'origin/main' into docs/CM-1333-akrites-…
joanagmaia Jul 22, 2026
53b0125
docs: change ADR number
joanagmaia Jul 22, 2026
8d793fd
docs: trim Akrites-side steps in ADR-0010 to high-level and note atom…
joanagmaia Jul 22, 2026
9fb6094
Merge remote-tracking branch 'origin/main' into docs/CM-1333-akrites-…
joanagmaia Jul 23, 2026
626a9fc
fix: link to adr
joanagmaia Jul 23, 2026
06323f3
Merge remote-tracking branch 'origin/main' into docs/CM-1333-akrites-…
joanagmaia Aug 6, 2026
e014147
docs: align akrites auth ADR with confirmed GCP hosting and 1Password…
joanagmaia Aug 6, 2026
57cd934
Merge remote-tracking branch 'origin/docs/CM-1333-akrites-cdp-auth-ad…
joanagmaia Aug 6, 2026
bd2e628
docs: document transitional scope acceptance on /akrites-external (CM…
joanagmaia Aug 10, 2026
eabaece
Merge remote-tracking branch 'origin/main' into docs/CM-1333-akrites-…
joanagmaia Aug 10, 2026
513483a
Merge branch 'main' into docs/CM-1333-akrites-cdp-auth-adr
joanagmaia Aug 10, 2026
ab89118
Merge branch 'main' into docs/CM-1333-akrites-cdp-auth-adr
joanagmaia Aug 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
204 changes: 204 additions & 0 deletions docs/adr/0009-akrites-cdp-public-api-authentication.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
# ADR-0009: Akrites → CDP public API authentication

**Date**: 2026-07-15
**Status**: proposed
**Deciders**: CDP team, LF Auth, Akrites team

## Context

Akrites is a new upstream consumer that needs read-only access to CDP's public
API. LF Auth provisions LF-managed M2M credentials using RSA keypair
authentication: Akrites signs a JWT `client_assertion` with a vault-resident
private key, exchanges it at LFX Auth0 for a short-lived Bearer token, then
calls CDP. CDP verifies the token, enforces a single-consumer restriction, and
gates endpoints on a dedicated scope.

The Auth0 audience (resource-server identifier) is not yet finalized — it is
referenced throughout as `{{AKRITES_CDP_AUDIENCE}}`.

High-level overview: https://linuxfoundation-dxwx.dsp.so/fkNtkFCx-akrites-cdp-auth

## Decision

Authenticate Akrites via **LFX Auth0 with a dedicated resource server and
required scopes**. On the CDP side, add an `azp` allowlist middleware
after JWT verification that asserts the token was issued specifically to
`{{AKRITES_AUTH0_CLIENT_ID}}`, so no other M2M client granted the same
audience can reach the `/akrites` router.

## Auth Flow

```mermaid
sequenceDiagram
participant Vault as LF Vault
participant Akrites
participant Auth0 as LFX Auth0
participant CDP

Note over Akrites,Vault: On startup or after rotation
Akrites->>Vault: read RSA private key
Vault-->>Akrites: private key

Note over Akrites,Auth0: Token exchange (repeated on expiry)
Akrites->>Akrites: sign client_assertion JWT (RS256)
Akrites->>Auth0: POST /oauth/token (client_credentials + jwt-bearer)
Auth0-->>Akrites: Bearer access_token (scope=read:<resource>)
Comment thread
joanagmaia marked this conversation as resolved.
Outdated

Note over Akrites,CDP: API call
Akrites->>CDP: GET /public/v1/akrites/* + Bearer token
CDP->>Auth0: fetch JWKS (cached)
CDP->>CDP: oauth2Middleware: verify sig + iss + aud
CDP->>CDP: azpAllowlistMiddleware: assert azp == AKRITES_CLIENT_ID
CDP->>CDP: requireScopes: assert read:<resource>
Comment thread
joanagmaia marked this conversation as resolved.
Outdated
CDP-->>Akrites: 200 OK

Note over Akrites,Auth0: On invalid_client (key rotated by LF)
Akrites->>Vault: re-read private key
Akrites->>Auth0: retry token exchange once
```

## Affected Repositories

### `auth0-terraform`

Three changes. The existing `auth0_resource_server.cdp_public_api` (lines 400–494
of `resource_servers.tf`) and `grants_cdp.tf` are the direct reference — Akrites
gets its own isolated resource server, not a grant on the shared CDP one.

**`resource_servers.tf`** — add these two blocks:
```hcl
resource "auth0_resource_server" "cdp_akrites_api" {
name = "CDP Akrites API"
identifier = {
"dev" = "{{AKRITES_CDP_AUDIENCE_DEV}}"
"staging" = "{{AKRITES_CDP_AUDIENCE_STAGING}}"
"prod" = "{{AKRITES_CDP_AUDIENCE_PROD}}"
}[terraform.workspace]

signing_alg = "RS256"
token_lifetime = {
"dev" = 3600
"staging" = 10800
"prod" = 10800
}[terraform.workspace]
token_dialect = "access_token"
allow_offline_access = false

subject_type_authorization {
user { policy = "deny_all" }
client { policy = "require_client_grant" }
}
}

resource "auth0_resource_server_scopes" "cdp_akrites_api" {
resource_server_identifier = auth0_resource_server.cdp_akrites_api.identifier

scopes {
name = "read:<resource>"
Comment thread
joanagmaia marked this conversation as resolved.
Outdated
description = "Read-only access to CDP data via the Akrites API"
}
}
```

**`clients_m2m.tf`** — add one entry to `local.m2m_clients` (line ~10):
```hcl
"Akrites" = { oidc_conformant = true },
```
The existing `auth0_client.m2m_clients` `for_each` resource instantiates it
automatically with `grant_types = ["client_credentials"]`. Auth method starts
as `client_secret_post`; `lfx-secrets-management` rotation converts it to
Private Key JWT — same as all other M2M clients.

**`grants_akrites.tf`** _(new file)_
```hcl
resource "auth0_client_grant" "akrites_cdp" {
client_id = auth0_client.m2m_clients["Akrites"].id
audience = auth0_resource_server.cdp_akrites_api.identifier
scopes = ["read:<resource>"]
Comment thread
joanagmaia marked this conversation as resolved.
Outdated
depends_on = [auth0_resource_server_scopes.cdp_akrites_api]
}
```

---

### `lfx-secrets-management`

No structural changes. Add Akrites as a new entry in the sync config:

- **Source**: Auth0 — uses existing `Auth0JWTConfig` model
(`secretsmanagement/services/auth0.py`)
- **Destinations**:
- AWS Secrets Manager (prod) — `secretsmanagement/services/aws.py` via `boto3`
- 1Password (dev) — `secretsmanagement/services/onepassword.py` via `op` CLI
- **Orchestration**: `secretsmanagement/sync.py` lines 37–96 — Akrites follows
same source → destination pattern as all other M2M clients

CDP holds no private key. Token verification is JWKS-only.

---

### `crowd.dev` (CDP — this repo)

**`backend/config/custom-environment-variables.json`** (currently lines 161–166)

Add alongside the existing `auth0` block:
```json
"auth0Akrites": {
"issuerBaseURLs": "CROWD_AUTH0_AKRITES_ISSUER_BASE_URLS",
"audience": "CROWD_AUTH0_AKRITES_AUDIENCE",
"clientId": "CROWD_AUTH0_AKRITES_CLIENT_ID"
}
Comment thread
joanagmaia marked this conversation as resolved.
Outdated
```

**`backend/src/conf/index.ts`** (line 106)

Add:
```ts
export const AKRITES_AUTH0_CONFIG: Auth0Configuration = config.get<Auth0Configuration>('auth0Akrites')
```
Comment thread
joanagmaia marked this conversation as resolved.
Outdated
`Auth0Configuration` interface (`backend/src/conf/configTypes.ts` lines 68–73) is reused as-is.
Comment thread
joanagmaia marked this conversation as resolved.
Outdated

**`backend/src/security/scopes.ts`**

Add to `SCOPES` const:
```ts
READ_RESOURCE: 'read:<resource>',
Comment thread
joanagmaia marked this conversation as resolved.
Outdated
```
Comment thread
joanagmaia marked this conversation as resolved.
Outdated

**`backend/src/api/public/middlewares/azpAllowlistMiddleware.ts`** _(new file)_

Reads `req.auth.payload.azp`. Throws `UnauthorizedError` if not in the
configured allowlist. Fails closed — any unknown client ID is rejected even if
Auth0 is misconfigured to grant it the same audience.

**`backend/src/api/public/v1/index.ts`** (line 44)

Replace:
```ts
router.use('/akrites', oauth2Middleware(AUTH0_CONFIG), akritesRouter())
```
With:
```ts
router.use(
'/akrites',
oauth2Middleware(AKRITES_AUTH0_CONFIG),
azpAllowlistMiddleware([AKRITES_AUTH0_CONFIG.clientId]),
requireScopes([SCOPES.READ_AKRITES], 'all'),
Comment thread
joanagmaia marked this conversation as resolved.
Outdated
akritesRouter(),
Comment thread
joanagmaia marked this conversation as resolved.
Outdated
)
```

---

### Akrites (external repo)

Implement the token exchange described in the Auth Flow diagram above:

1. Read RSA private key from vault (AWS Secrets Manager in prod via
`lfx-secrets-management`, 1Password in dev).
2. Build and sign `client_assertion` JWT (RS256).
3. POST to Auth0 `/oauth/token` — cache the returned Bearer token until it
expires.
Comment thread
joanagmaia marked this conversation as resolved.
Outdated
4. Attach Bearer token to all CDP requests via `Authorization` header.
5. On `invalid_client`: discard cached key → re-read from vault → retry token
exchange once. LF rotates the keypair without notice.
Comment thread
joanagmaia marked this conversation as resolved.
Outdated
1 change: 1 addition & 0 deletions docs/adr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ Use the `/adr` skill in Claude Code to record new ADRs or query past decisions.
| [ADR-0006](./0006-database-schema-types-as-source-of-truth.md) | Database schema types as the source of truth | accepted | 2026-07-09 |
| [ADR-0007](./0007-test-factory-primitives-and-defaults.md) | Test factory primitives and defaults | accepted | 2026-07-10 |
| [ADR-0008](./0008-how-we-write-unit-tests.md) | How we write unit tests | accepted | 2026-07-13 |
| [ADR-0009](./0009-akrites-cdp-public-api-authentication.md) | Akrites → CDP public API authentication | proposed | 2026-07-14 |
Comment thread
joanagmaia marked this conversation as resolved.
Outdated
Comment thread
joanagmaia marked this conversation as resolved.
Outdated

## Why ADRs?

Expand Down
Loading