docs: add ADR-0011 for Akrites CDP public API authentication (CM-1333) - #4345
Conversation
Signed-off-by: Joana Maia <jmaia@contractor.linuxfoundation.org>
PR SummaryLow Risk Overview The ADR records how Akrites will authenticate to CDP’s existing public API for the Credential delivery is manual via 1Password until LF can sync secrets to GCP; alternatives (shared client secret, cross-account AWS Secrets Manager, Reviewed by Cursor Bugbot for commit ab89118. Bugbot is set up for automated code reviews on this repo. Configure here. |
There was a problem hiding this comment.
Pull request overview
Adds a proposed ADR for Akrites machine-to-machine authentication to the CDP public API.
Changes:
- Defines the Auth0 token exchange and authorization flow.
- Outlines implementation across four affected repositories.
- Adds ADR-0009 to the ADR index.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 9 comments.
| File | Description |
|---|---|
docs/adr/0009-akrites-cdp-public-api-authentication.md |
Documents the proposed authentication architecture and implementation plan. |
docs/adr/README.md |
Adds ADR-0009 to the index. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Align proposed approach with LF-owned Secrets Manager cross-account read model, prune Alternatives to shared client_secret fallback only, switch scopes to read:packages + read:advisories + read:maintainers, and fix Mermaid diagram parse errors. Signed-off-by: Joana Maia <jmaia@contractor.linuxfoundation.org>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 10 comments.
Comments suppressed due to low confidence (3)
docs/adr/0009-akrites-cdp-public-api-authentication.md:66
- The PR description says this integration will use a dedicated resource server and a
read:akritesscope, but the ADR decides to reusecdp_public_apiwith three different scopes. These are materially different authentication designs; update either the PR description or this decision so reviewers know which architecture is actually proposed.
Authenticate Akrites against the **existing `cdp_public_api` resource
server**, gated by an **`azp` allowlist middleware** in CDP (sole consumer
identity gate) and domain scopes **`read:packages`**, **`read:advisories`**,
**`read:maintainers`** on the granted token. Distribute the RSA private
docs/adr/README.md:18
- The index date does not match the ADR's
2026-07-21date, leaving conflicting metadata for the same decision.
| [ADR-0009](./0009-akrites-cdp-public-api-authentication.md) | Akrites → CDP public API authentication | proposed | 2026-07-14 |
docs/adr/0009-akrites-cdp-public-api-authentication.md:314
- The ADR ends without the mandatory
## Consequencessection and its### Positive,### Negative, and### Riskssubsections required by.claude/rules/adr-format.md:16-31. Capture the accepted trade-offs and mitigations before this ADR is merged.
LF rather than auto-retry; do not tight-loop.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (8)
docs/adr/0009-akrites-cdp-public-api-authentication.md:59
- The referenced companion document is not included in this PR or present in the repository, despite the PR description saying it is added. Add
docs/akrites-cdp-auth-highlevel.md, or remove this reference and update the PR description.
High-level overview: `docs/akrites-cdp-auth-highlevel.md`
(mirror of the internal DSP page).
docs/adr/0009-akrites-cdp-public-api-authentication.md:236
- This route-level scope check does not replace the existing nested checks.
akrites-external/index.ts:43still also requiresread:stewardships, and line 66 requiresread:maintainer-roles; neither is granted here, so the documented Akrites token will receive 403s for package and contact endpoints. Update those subrouter guards and the OpenAPI contract to use the new domain scopes; otherwise grant the existing scopes.
requireScopes(
[SCOPES.READ_PACKAGES, SCOPES.READ_ADVISORIES, SCOPES.READ_MAINTAINERS],
'all',
),
docs/adr/0009-akrites-cdp-public-api-authentication.md:254
- ECS
ValueFrominjects the secret only when a task starts; a running task does not receive rotated values. That option cannot support the documentedinvalid_clientre-read without redeployment. Require an SDK/runtime fetch through the task or pod role, or explicitly document forced task deployment on rotation.
full LF secret ARN. Prod options: ECS `ValueFrom` on the task definition
with the LF ARN, or EKS External Secrets Operator + IRSA. Dev:
1Password via the LF-provided vault item.
docs/adr/0009-akrites-cdp-public-api-authentication.md:175
- A cross-account Secrets Manager secret cannot use the AWS-managed
aws/secretsmanagerkey, so a customer-managed KMS key andkms:Decryptpermissions are mandatory, not conditional. Also,kms:Decryptbelongs in the KMS key policy and caller identity policy—not the secret resource policy. Apply the same correction to the optional-CMK wording earlier in the ADR.
**Resource policy on the LF secret** — grants Akrites' workload IAM role
`secretsmanager:GetSecretValue` + `secretsmanager:DescribeSecret`. Deny
wildcards. If the secret is CMK-encrypted, the KMS key policy must also
allow `kms:Decrypt` for that role. Akrites' AWS account ID + workload role
ARN required from the Akrites team before the resource policy can be
docs/adr/0009-akrites-cdp-public-api-authentication.md:257
- The Auth0 request description omits required form fields (
client_id,audience, andclient_assertion), and the assertion step omits Auth0's required claims. Following these steps literally cannot complete the token exchange; document the complete request and the requirediss,sub,aud,exp, and uniquejticlaims.
2. Build and sign `client_assertion` JWT (RS256).
3. POST to Auth0 `/oauth/token` with `grant_type=client_credentials` +
`client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer`.
docs/adr/0009-akrites-cdp-public-api-authentication.md:264
- This ADR ends after Alternatives and omits the mandatory
## Consequencessection with### Positive,### Negative, and### Risks. Add it as required by.claude/rules/adr-format.md:16-31; rotation ordering, cross-account availability, and the consumer-allowlist operational burden should be captured there.
## Alternatives Considered
docs/adr/README.md:18
- The index date does not match the ADR's
2026-07-21date, making the ADR index inaccurate.
| [ADR-0009](./0009-akrites-cdp-public-api-authentication.md) | Akrites → CDP public API authentication | proposed | 2026-07-14 |
docs/adr/0009-akrites-cdp-public-api-authentication.md:7
- The Context section substantially exceeds the required 2–5 sentences in
.claude/rules/adr-format.md:25. Keep only the situation and decision forces here, and move implementation details into clearly named sections after the Decision.
## Context
Fix audience contradiction (shared AUTH0_CONFIG across public routes), correct public URL path to /api/v1/akrites-external, reword the new route mount as an insertion before the 404 handler, soften KMS wording with a DevOps validation note, expand Akrites-side delivery patterns to reflect ValueFrom rotation constraints, spell out the /oauth/token request body, drop the stale highlevel doc reference, and bump the ADR index date. Signed-off-by: Joana Maia <jmaia@contractor.linuxfoundation.org>
eb630a6 to
70f1c0b
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (4)
docs/adr/0009-akrites-cdp-public-api-authentication.md:361
- The ADR is missing the mandatory
## Consequencessection with### Positive,### Negative, and### Risks..claude/rules/adr-format.md:27-30requires all of these before an ADR can be added, so please document the accepted trade-offs and mitigations.
LF rather than auto-retry; do not tight-loop.
docs/adr/0009-akrites-cdp-public-api-authentication.md:7
- This Context contains substantially more than the required 2–5 sentences and mixes background with detailed implementation guidance.
.claude/rules/adr-format.md:25requires a concise Context; move the KMS, route, and scope details into dedicated sections so the decision record remains scannable.
## Context
docs/adr/0009-akrites-cdp-public-api-authentication.md:1
- The PR description says it adds
docs/akrites-cdp-auth-highlevel.md, but that companion document is absent from the changed files and repository. Either include the promised mirror or update the PR description so the documented scope matches the change.
# ADR-0009: Akrites → CDP public API authentication
docs/adr/0009-akrites-cdp-public-api-authentication.md:247
- This mount-level
allcheck, combined with removing inner scope guards, does not implement the domain-level handler isolation claimed at lines 62–64: every admitted token must carry all three scopes and can reach every handler. Either enforce each scope on its corresponding subrouter, or explicitly document that these scopes are an all-or-nothing route bundle rather than independent domain authorization.
requireScopes(
[SCOPES.READ_PACKAGES, SCOPES.READ_ADVISORIES, SCOPES.READ_MAINTAINERS],
'all',
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (5)
docs/adr/0009-akrites-cdp-public-api-authentication.md:1
- The PR description says this change adds
docs/akrites-cdp-auth-highlevel.md, but that companion document is absent from the changed files and the checkout. Either add the promised document or update the PR description so the delivered scope is accurate.
# ADR-0009: Akrites → CDP public API authentication
docs/adr/0009-akrites-cdp-public-api-authentication.md:7
- This Context is far beyond the required 2–5 sentences and mixes implementation instructions, open questions, and decision details into the problem statement. Please keep the Context concise and move the detailed material into focused sections;
.claude/rules/adr-format.md:25makes the 2–5 sentence structure mandatory for ADRs.
## Context
docs/adr/0009-akrites-cdp-public-api-authentication.md:78
- Cross-account Secrets Manager access cannot use the AWS-managed
aws/secretsmanagerkey; AWS requires a customer-managed KMS key plus aligned key and identity policies. Leaving the CMK as conditional allows an implementation that cannot retrieve the secret, so make this a requirement rather than a DevOps validation item throughout the ADR.
If the secret is encrypted with a customer-managed KMS key (likely
required for cross-account decrypt — LF DevOps to confirm), the KMS key
policy must also grant `kms:Decrypt` to that role. Akrites reads
docs/adr/0009-akrites-cdp-public-api-authentication.md:256
- This is not the repository's current state:
akritesExternalRouter()and its mount already exist, and the router has four endpoint-specificrequireScopesguards (index.ts:43,54,66,76). The proposed mount-wide all-scopes guard also conflicts with the existing OpenAPI security declarations. Document this as a migration, decide whether per-domain authorization is intentionally being removed, and include the required OpenAPI update.
`akritesExternalRouter()` is a **new module** at
`backend/src/api/public/v1/akrites-external/index.ts` — separate from the
existing `akritesRouter` and free of its inner scope guards, so the
mount-level `requireScopes` above is authoritative for this route.
docs/adr/0009-akrites-cdp-public-api-authentication.md:311
- The ADR ends after Alternatives and omits the mandatory
## Consequencessection with### Positive,### Negative, and### Risks. Add those sections before merging; they are required by.claude/rules/adr-format.md:28and are especially important for the stated cross-account and rotation trade-offs.
## Alternatives Considered
…WS caller identity (CM-1333) Signed-off-by: Joana Maia <jmaia@contractor.linuxfoundation.org>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (7)
docs/adr/0009-akrites-cdp-public-api-authentication.md:365
- The ADR ends without the mandatory
## Consequencessection and its### Positive,### Negative, and### Riskssubsections..claude/rules/adr-format.md:27-31requires all four headings, so this ADR does not satisfy the repository template yet.
LF rather than auto-retry; do not tight-loop.
docs/adr/0009-akrites-cdp-public-api-authentication.md:7
- This section is substantially longer than the required 2–5 sentence Context in
.claude/rules/adr-format.md:25. Please move implementation detail into later supporting sections and keep Context focused on the forces that prompted the decision.
## Context
docs/adr/0009-akrites-cdp-public-api-authentication.md:68
- The Decision is five sentences, while
.claude/rules/adr-format.md:26limits it to 1–3 sentences. Condense the selected authentication, authorization, and secret-delivery choice here; retain the conditional and implementation details in supporting sections.
## Decision
docs/adr/0009-akrites-cdp-public-api-authentication.md:248
- Requiring all three scopes at the mount and then removing the subrouter checks makes authorization all-or-nothing: every accepted token can call every domain handler. That contradicts lines 62–64, which say domain scopes constrain which handlers are reachable, and prevents least-privilege package-, advisory-, or maintainer-only access. Keep the
azpgate global and enforce each domain scope on its relevant subrouter, or explicitly revise the decision if all-or-nothing access is intended.
requireScopes(
[SCOPES.READ_PACKAGES, SCOPES.READ_ADVISORIES, SCOPES.READ_MAINTAINERS],
'all',
),
docs/adr/0009-akrites-cdp-public-api-authentication.md:221
config.get('akritesExternal.clientId')throws during module import when the environment variable is absent. Because the plan only adds the custom-environment mapping, any local, test, or deployed environment not populated before this code rolls out will fail backend startup. Include the required environment propagation and rollout order, or make configuration optional while keeping the route fail-closed.
Add:
```ts
export const AKRITES_EXTERNAL_CLIENT_ID: string = config.get<string>('akritesExternal.clientId')
**docs/adr/0009-akrites-cdp-public-api-authentication.md:313**
* This retry procedure cannot work for the documented ECS `ValueFrom` option: as lines 281–282 note, a running task cannot re-read the rotated value and must be replaced, so an in-process retry uses the same stale key. Specify separate recovery behavior for each delivery mode and update the sequence diagram accordingly; ECS needs task replacement rather than re-fetch-and-retry.
- On
invalid_client: discard the cached key → re-fetch from the secret
store (SDK read, or task/pod refresh depending on the pattern chosen
in step 1) → retry the token exchange once. LF rotates the keypair
without notice.
**docs/adr/0009-akrites-cdp-public-api-authentication.md:1**
* The PR description says this change also adds `docs/akrites-cdp-auth-highlevel.md`, but that companion document is absent from the changed files. Add the promised document or update the PR description so reviewers and consumers have an accurate deliverable list.
ADR-0009: Akrites → CDP public API authentication
</details>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (4)
docs/adr/0011-akrites-cdp-public-api-authentication.md:7
- The Context section exceeds the repository's required 2–5 sentence limit by a wide margin (
.claude/rules/adr-format.md:16-27). Please condense it to the problem and architectural forces, moving implementation details into the existing flow/repository sections or Consequences.
## Context
docs/adr/0011-akrites-cdp-public-api-authentication.md:33
- Cross-account Secrets Manager access requires a customer-managed KMS key; the AWS-managed
aws/secretsmanagerkey cannot be used for a cross-account secret. AWS also requires an identity-based policy on Akrites' caller role in addition to the secret and KMS resource policies. Treating the CMK as conditional and omitting the caller policy can leave the chosen flow denied at runtime. Make these requirements explicit throughout the Decision and affected-repository responsibilities.
> **KMS note (to validate with LF DevOps):** cross-account Secrets Manager
> reads are commonly documented as requiring a customer-managed KMS key —
> the AWS-managed `aws/secretsmanager` key policy can't be modified to
> grant `kms:Decrypt` to an external principal. If that is accurate for
> LF's current SM setup, the LF secret must be created against a CMK and
docs/adr/0011-akrites-cdp-public-api-authentication.md:91
- The Decision contains five sentences, but
.claude/rules/adr-format.md:16-27limits this section to 1–3. Move the deployment assumption and detailed IAM/KMS conditions into Consequences/Risks and keep this section to the selected authentication, authorization, and credential-delivery choices.
Assumes Akrites has an AWS account and a workload role — pending confirmation
(see Context).
docs/adr/0011-akrites-cdp-public-api-authentication.md:369
- This ADR ends without the mandatory
## Consequencessection and its### Positive,### Negative, and### Riskssubsections required by.claude/rules/adr-format.md:16-28. Add them to record the accepted trade-offs, especially cross-account IAM/KMS dependencies, rotation failure behavior, and scope-grant governance.
let CDP stay pure-claims via the existing `requireScopes` middleware.
…cdp-auth-adr Signed-off-by: Joana Maia <jmaia@contractor.linuxfoundation.org> # Conflicts: # docs/adr/README.md
… delivery (CM-1333) Signed-off-by: Joana Maia <jmaia@contractor.linuxfoundation.org>
…r' into docs/CM-1333-akrites-cdp-auth-adr Signed-off-by: Joana Maia <jmaia@contractor.linuxfoundation.org> # Conflicts: # docs/adr/README.md
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
Suppressed comments (6)
docs/adr/0016-akrites-cdp-public-api-authentication.md:21
- The PR description still says Akrites hosting is an open decision and describes cross-account AWS Secrets Manager plus a possible
client_secretfallback. This ADR instead records GCP as confirmed and chooses manual 1Password delivery withprivate_key_jwt. Update the PR description and its auth-flow summary to match the decision being reviewed.
**Akrites' service is hosted on GCP** (confirmed with the Akrites team on
2026-07-28). `lfx-secrets-management` delivers credentials to AWS-side
destinations and 1Password today; GCP is not a supported destination, and
docs/adr/0016-akrites-cdp-public-api-authentication.md:64
- The repository ADR rule limits
## Decisionto 1–3 sentences (.claude/rules/adr-format.md:26), while this decision contains five. Condense the selected authentication, authorization, and delivery choices into at most three sentences and move operational detail to Consequences or Affected Repositories.
## Decision
Authenticate the `Akrites Enclave` client against the **existing
`cdp_public_api` resource server**, gated by three Akrites-namespaced scopes
docs/adr/0016-akrites-cdp-public-api-authentication.md:268
- This ADR ends after Alternatives Considered, but
.claude/rules/adr-format.md:27-28requires a following## Consequencessection with### Positive,### Negative, and### Risks. Add those sections and record the operational trade-offs of manual key delivery, coordinated rotation, and scope-based isolation.
## Alternatives Considered
### Client secret instead of RSA private_key_jwt (not taken)
docs/adr/0016-akrites-cdp-public-api-authentication.md:1
- The PR title and summary call this ADR-0011, but ADR-0011 already exists for mailing-list date handling and this new record is correctly numbered ADR-0016. Please update the PR metadata to ADR-0016 so links and release history do not identify the wrong decision.
This issue also appears on line 19 of the same file.
# ADR-0016: Akrites → CDP public API authentication
docs/adr/0016-akrites-cdp-public-api-authentication.md:9
- The repository ADR rule requires
## Contextto contain 2–5 sentences (.claude/rules/adr-format.md:25), but this section runs through line 59 and contains substantially more. Keep only the decision forces here and move implementation details into the later flow/repository sections.
This issue also appears in the following locations of the same file:
- line 61
- line 266
## Context
Akrites is a new external consumer that needs read-only access to CDP's public
docs/adr/0016-akrites-cdp-public-api-authentication.md:41
- This isolation claim does not match the current CDP implementation.
backend/src/api/public/v1/akrites-external/index.ts:56-132callsrequireScopes(..., 'any')with legacy Self Serve scopes on every surface, and the OpenAPI contract documents those fallbacks, so a non-Akrites client carrying those scopes can still access this route. Either document the transitional fallback and state that isolation begins only after cutover, or remove the fallback scopes in the implementation before recording this decision as the current enforcement model.
required: `/akrites-external` requires Akrites-namespaced scopes
(`read:akrites-packages`, `read:akrites-advisories`,
`read:akrites-maintainers`) via per-endpoint `requireScopes`. Consumers of
| Consumer isolation is claim-based via Auth0 grants. Three Akrites-namespaced | ||
| scopes (`read:akrites-packages`, `read:akrites-advisories`, | ||
| `read:akrites-maintainers`) are defined on `cdp_public_api` and granted only | ||
| to the `Akrites Enclave` client. Auth0 will not issue these scopes to any |
There was a problem hiding this comment.
@joanagmaia this part doesn't reflect the current code, at the moment we also accept other scopes on this route. We should remember to remove the additional ones once the whole flow is in place.
There was a problem hiding this comment.
Good catch. Updated the ADR to document the transitional state: added a note in the Context section explaining the requireScopes([...], 'any') fallback (Akrites-namespaced + legacy Self Serve scopes) and updated the implementation snippet to show the actual current scope lists with a call-out to drop the legacy ones on cut-over. Target state description stays as-is.
…-1333) Signed-off-by: Joana Maia <jmaia@contractor.linuxfoundation.org>
…cdp-auth-adr Signed-off-by: Joana Maia <jmaia@contractor.linuxfoundation.org>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
Suppressed comments (3)
docs/adr/0016-akrites-cdp-public-api-authentication.md:1
- The PR title and summary call this ADR-0011, but ADR-0011 already documents mailing-list date handling and this change is correctly indexed as ADR-0016. Update the PR metadata to ADR-0016 so links and release history identify the right decision.
# ADR-0016: Akrites → CDP public API authentication
docs/adr/0016-akrites-cdp-public-api-authentication.md:21
- This ADR records GCP hosting and manual 1Password delivery as confirmed, while the PR description still presents an unconfirmed AWS workload and cross-account Secrets Manager as the chosen strategy. Update the description to match the decision being reviewed; otherwise reviewers and companion implementers receive contradictory deployment and rotation requirements.
**Akrites' service is hosted on GCP** (confirmed with the Akrites team on
2026-07-28). `lfx-secrets-management` delivers credentials to AWS-side
destinations and 1Password today; GCP is not a supported destination, and
docs/adr/0016-akrites-cdp-public-api-authentication.md:346
- The repository ADR template requires a
Consequencessection with positive, negative, and risk subsections, and every numbered ADR from 0010 through 0015 includes it. Add this section so the manual credential handoff, coordinated rotation window, transitional legacy-scope exposure, and workflow-triggering scope risks are recorded rather than leaving only rejected alternatives.
- **Why superseded**: reviewer feedback on the `auth0-terraform` PR
(@detjensrobert, 2026-07-21) — trust decisions on resource servers
should be claim-based, not caller-metadata based. Namespaced
Akrites-only scopes put the identity gate inside Auth0's grant model and
let CDP stay pure-claims via the existing `requireScopes` middleware.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
Suppressed comments (5)
docs/adr/0016-akrites-cdp-public-api-authentication.md:12
- The stated read-only permission boundary is not what this scope grants.
read:akrites-maintainersalso authorizesPOST /akrites-external/contacts/ingest, which startsingestSecurityContactsForPurlWorkflowand persists refreshed contact data (backend/src/api/public/v1/akrites-external/index.ts:121-125). Either separate that operation behind a write scope/exclude it from Akrites, or explicitly document the side-effecting ingest capability and its authorization rationale.
Akrites is a new external consumer that needs read-only access to CDP's public
API through a new route. The route runs on the existing CDP public API listener
(`v1Router`, `backend/src/api/public/v1/index.ts`) — same service, same
process as `/members`, `/organizations`, `/akrites`, etc.
docs/adr/0016-akrites-cdp-public-api-authentication.md:250
- The cut-over checklist omits the two project-profiling routes. They use their own
projectProfilingScopesand currently acceptREAD_MAINTAINER_ROLES(backend/src/api/public/v1/akrites-external/index.ts:73-84), so following only the listed removals would leave legacy Self Serve tokens able to call them after the stated cut-over.
Target state (post cut-over): `requireScopes([SCOPES.READ_AKRITES_*])` with
no `'any'` fallback — dedicated scope per subrouter, no legacy scopes.
docs/adr/0016-akrites-cdp-public-api-authentication.md:1
- The PR metadata is still stale: the title calls this ADR-0011 (which is already the mailing-list ADR), while the description still presents AWS cross-account delivery and the hosting/auth-method decision as open. This document records ADR-0016, confirmed GCP hosting, 1Password delivery, and
private_key_jwt; update the title and description to match the decision being reviewed.
# ADR-0016: Akrites → CDP public API authentication
docs/adr/0016-akrites-cdp-public-api-authentication.md:255
- The affected-file plan does not address the OpenAPI credential guidance:
openapi.yaml:91-93still tells Akrites to exchange a client ID/secret, contradicting this ADR'sprivate_key_jwtdecision. Include and implement an update toM2MBearerdescribing the private-key-signedclient_assertion, otherwise generated API documentation directs consumers to the rejected auth method.
**`backend/src/api/public/v1/akrites-external/openapi.yaml`** — advertises
the Akrites-namespaced scopes everywhere scopes appear: global and
operation-level `security` requirements, 403 response descriptions, tags,
and the OAuth2 flow description — the same scopes `requireScopes` enforces.
docs/adr/0016-akrites-cdp-public-api-authentication.md:346
- This ADR ends without the
## Consequencessection required bydocs/adr/template.md:27-36and used by the other decision ADRs. Add Positive, Negative, and Risks entries covering the chosen approach, especially manual credential hand-off/rotation downtime and the temporary legacy-scope exposure, so the accepted trade-offs remain part of the decision record.
- **Why superseded**: reviewer feedback on the `auth0-terraform` PR
(@detjensrobert, 2026-07-21) — trust decisions on resource servers
should be claim-based, not caller-metadata based. Namespaced
Akrites-only scopes put the identity gate inside Auth0's grant model and
let CDP stay pure-claims via the existing `requireScopes` middleware.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
Suppressed comments (6)
docs/adr/0016-akrites-cdp-public-api-authentication.md:23
- The current PR description still calls the AWS account/workload role an open decision and selects cross-account AWS Secrets Manager with a possible client-secret fallback. This ADR instead records GCP as confirmed and chooses manual 1Password delivery with
private_key_jwt. Update the description so reviewers and companion implementations see the same decision.
**Akrites' service is hosted on GCP** (confirmed with the Akrites team on
2026-07-28). `lfx-secrets-management` delivers credentials to AWS-side
destinations and 1Password today; GCP is not a supported destination, and
adding it is non-trivial (LF has no GCP accounts to test against, and the
auth/service-account setup is undefined). LF CloudOps has taken GCP support
docs/adr/0016-akrites-cdp-public-api-authentication.md:12
- The granted access is not entirely read-only:
POST /akrites-external/contacts/ingestis authorized byREAD_AKRITES_MAINTAINERSand starts a workflow that can replace persisted repository contacts (backend/src/api/public/v1/packages/ingestAkritesExternalContactDetail.ts:35-66). Either explicitly include this mutation in the decision and accepted risks, or protect it with a separate write scope; otherwise the stated privilege model understates what the client can do.
Akrites is a new external consumer that needs read-only access to CDP's public
API through a new route. The route runs on the existing CDP public API listener
(`v1Router`, `backend/src/api/public/v1/index.ts`) — same service, same
process as `/members`, `/organizations`, `/akrites`, etc.
docs/adr/0016-akrites-cdp-public-api-authentication.md:231
- The cut-over checklist omits both project-profiling routes. They use
projectProfilingScopes = [READ_MAINTAINER_ROLES, READ_AKRITES_MAINTAINERS]inakrites-external/index.ts:73-84, so following only the listed removals would leave the legacy Self Serve scope active on those endpoints. Add project profiling to this section and its legacy-scope removal plan.
**`backend/src/api/public/v1/akrites-external/index.ts`** — each subrouter
gates on its Akrites-namespaced scope. During the cut-over period the
legacy Self Serve scopes are also accepted (`'any'`); remove them once
Akrites confirms end-to-end token exchange:
docs/adr/0016-akrites-cdp-public-api-authentication.md:282
- This ADR omits the required
## Consequencessection fromdocs/adr/template.md:27-36. Record positive outcomes, negative trade-offs, and risks—especially manual key hand-off/rotation and temporary legacy-scope access—so the decision captures its operational consequences rather than only its alternatives.
## Alternatives Considered
docs/adr/0016-akrites-cdp-public-api-authentication.md:255
- The existing OpenAPI flow description still tells Akrites to exchange a client ID/secret (
openapi.yaml:90-93), which conflicts with this ADR'sprivate_key_jwtdecision. Include that required description update in the affected-file plan, not only the scope changes, so generated consumer documentation does not prescribe the rejected credential flow.
**`backend/src/api/public/v1/akrites-external/openapi.yaml`** — advertises
the Akrites-namespaced scopes everywhere scopes appear: global and
operation-level `security` requirements, 403 response descriptions, tags,
and the OAuth2 flow description — the same scopes `requireScopes` enforces.
docs/adr/0016-akrites-cdp-public-api-authentication.md:1
- The PR title and Summary identify this as ADR-0011, but ADR-0011 already exists for mailing-list date handling and this change correctly allocates ADR-0016. Update the PR metadata to ADR-0016 so links and release history do not refer to the wrong decision.
This issue also appears in the following locations of the same file:
- line 19
- line 228
- line 252
- line 282
# ADR-0016: Akrites → CDP public API authentication
Summary
auth0-terraform,lfx-secrets-management,crowd.dev, and Akrites (external).Context
Akrites is a new external consumer needing read-only access to CDP's public API through a new
/akrites-externalroute on the existing public API listener. The ADR formalizes the M2M auth strategy:cdp_public_apiresource server (not a new one) — a new RS would mean minting a second audience for the same logical API with no separate service backing it.read:akrites-packages,read:akrites-advisories,read:akrites-maintainers. All three are provisioned oncdp_public_apiand granted only to theAkrites Enclaveclient in Auth0 — Auth0 refuses to issue them to any other client, so tokens from other CDP consumers (e.g.lfx_one) cannot carry them. CDP's per-endpointrequireScopesmiddleware is the sole API-layer enforcement point. Noazpallowlist or client-identity check in CDP source (earlier revisions used one; superseded per reviewer feedback on theauth0-terraformPR — see the ADR's Alternatives Considered).private_key_jwtauthentication, with the private key in an LF-owned AWS Secrets Manager entry. A resource (item) policy on that secret grants Akrites' workload IAM roleGetSecretValue(+kms:Decryptif CMK — validation with LF DevOps deferred). Akrites reads cross-account — same pattern as cross-account S3.lfx-secrets-managementon the standard cadence. Akrites reacts toinvalid_clientby re-reading the latest key version and retrying once.Open decision
Where Akrites runs (AWS account + workload IAM role) is not yet confirmed — deferred to the week of 2026-07-27. If Akrites has no AWS account (or no workload role for LF to grant access to via the item policy), we fall back to a shared
client_secretvariant: same client, same scopes, same CDP middleware chain — only thelfx-secrets-managementsync source flips fromauth0_jwtback toauth0, and rotation becomes out-of-band. Fallback is documented in both docs; CDP-side code is unaffected either way.Companion PRs
auth0-terraform— newAkrites Enclaveclient, three Akrites-namespaced scopes oncdp_public_api, new grant: PR #350 (draft) — branchfeat/CM-1333-akrites-external-cdp-auth.lfx-secrets-management(to be implemented) — sync entry rotating the client toprivate_key_jwt, destinations 1Password + LF-owned AWS Secrets Manager: branchfeat/CM-1333-akrites-external-secret-sync(PR pending).Jira: CM-1333
🤖 Generated with Claude Code