Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
44 changes: 22 additions & 22 deletions backend/src/api/public/v1/akrites-external/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,66 +51,66 @@ const contactIngestRateLimiter = envTunableRateLimiter(
export function akritesExternalRouter(): Router {
const router = Router()

// TODO: swap for a dedicated cdp:packages:read scope once Akrites gets its own
// Auth0 M2M scopes (per the akrites-external draft contract) — reusing the
// internal CDP UI scopes for now since that's what's actually issued today.
// Any one of the dedicated Akrites scope or the old Self Serve scopes works for now —
// drop READ_PACKAGES/READ_STEWARDSHIPS once Akrites cuts over.
const packagesSubRouter = Router()
packagesSubRouter.use(rateLimiter)
packagesSubRouter.use(requireScopes([SCOPES.READ_PACKAGES, SCOPES.READ_STEWARDSHIPS], 'all'))
packagesSubRouter.use(
requireScopes(
[SCOPES.READ_AKRITES_PACKAGES, SCOPES.READ_PACKAGES, SCOPES.READ_STEWARDSHIPS],
'any',
Comment thread
ulemons marked this conversation as resolved.
),
)
packagesSubRouter.get('/detail', safeWrap(getAkritesExternalPackageDetail))
packagesSubRouter.post(/^\/detail:batch\/?$/, safeWrap(getAkritesExternalPackageDetailBatch))
router.use('/packages', packagesSubRouter)

// TODO: the contract gates advisories behind a dedicated read:advisories scope
// (see the scope-naming note in the akrites-external OpenAPI). That scope isn't
// issued by Auth0 yet, so reuse READ_PACKAGES for now — advisories are package
// security data and, unlike the packages endpoints above, need no stewardship read.
// Dedicated read:akrites-advisories, or Self Serve's read:packages as a
// fallback until Akrites cuts over — drop it then.
const advisoriesScopes = [SCOPES.READ_PACKAGES, SCOPES.READ_AKRITES_ADVISORIES]
const advisoriesSubRouter = Router()
advisoriesSubRouter.use(rateLimiter)
advisoriesSubRouter.use(requireScopes([SCOPES.READ_PACKAGES]))
advisoriesSubRouter.use(requireScopes(advisoriesScopes, 'any'))
advisoriesSubRouter.get('/detail', safeWrap(getAkritesExternalAdvisoryDetail))
advisoriesSubRouter.post(/^\/detail:batch\/?$/, safeWrap(getAkritesExternalAdvisoryDetailBatch))
router.use('/advisories', advisoriesSubRouter)

// Security contacts expose contact PII (e.g. reporter emails), so the contract gates
// them behind a dedicated cdp:maintainers:read scope and explicitly forbids reaching
// them via the packages scope. That scope isn't issued by Auth0 yet, so reuse the
// closest issued one — READ_MAINTAINER_ROLES (maintainer data) — NOT READ_PACKAGES.
// TODO: swap for cdp:maintainers:read once issued.
// Contact PII stays behind a dedicated scope, never the packages scope: dedicated
// read:akrites-maintainers, or Self Serve's read:maintainer-roles as a fallback.
//
// requireScopes is applied per-route (not router-level) so each route can put its own
// rate limiter *before* the scope check — failed-auth requests still count against that
// route's quota — without forcing every route in this subrouter onto the same limiter
// instance. /ingest gets its own dedicated contactIngestRateLimiter instead of sharing
// the read endpoints' quota, matching the blast-radius jobs endpoint below.
const contactsScopes = [SCOPES.READ_MAINTAINER_ROLES]
const contactsScopes = [SCOPES.READ_MAINTAINER_ROLES, SCOPES.READ_AKRITES_MAINTAINERS]
const contactsSubRouter = Router()
contactsSubRouter.get(
'/detail',
rateLimiter,
requireScopes(contactsScopes),
requireScopes(contactsScopes, 'any'),
safeWrap(getAkritesExternalContactDetail),
)
contactsSubRouter.post(
/^\/detail:batch\/?$/,
rateLimiter,
requireScopes(contactsScopes),
requireScopes(contactsScopes, 'any'),
safeWrap(getAkritesExternalContactDetailBatch),
)
// Sync, single-purl on-demand ingest — starts a Temporal workflow and blocks a while,
// so it gets the dedicated contactIngestRateLimiter, not the shared rateLimiter above.
contactsSubRouter.post(
'/ingest',
contactIngestRateLimiter,
requireScopes(contactsScopes),
requireScopes(contactsScopes, 'any'),
safeWrap(ingestAkritesExternalContactDetail),
)
router.use('/contacts', contactsSubRouter)

// TODO: the contract gates blast-radius behind a dedicated read:advisories scope
// (same as advisories above — see the scope-naming note in the akrites-external
// OpenAPI). Not issued by Auth0 yet, so reuse READ_PACKAGES for now.
// Same underlying data as advisories above, same scopes: read:akrites-advisories,
// or Self Serve's read:packages as a fallback until Akrites cuts over.
const blastRadiusSubRouter = Router()
blastRadiusSubRouter.use(requireScopes([SCOPES.READ_PACKAGES]))
blastRadiusSubRouter.use(requireScopes(advisoriesScopes, 'any'))
blastRadiusSubRouter.post('/jobs', blastRadiusRateLimiter, safeWrap(submitBlastRadiusJob))
// Bulk submit multiplies Temporal workflow starts per request (up to
// MAX_BLAST_RADIUS_JOBS_PER_BATCH), so it sits behind the same strict
Expand Down
104 changes: 70 additions & 34 deletions backend/src/api/public/v1/akrites-external/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,38 +16,47 @@ info:
7-day result cache is specced separately and not yet built.


TODO: scopes below (read:packages, read:stewardships) are the existing
internal CDP UI scopes, reused here for now. Swap for a dedicated
cdp:packages:read scope once Akrites gets its own Auth0 M2M scopes per
the akrites-external draft contract.
Auth0 now issues a dedicated Akrites Enclave M2M client with its own
read:akrites-packages / read:akrites-advisories / read:akrites-maintainers
scopes. Until Akrites cuts over to that client's credentials, the Self Serve
client's existing scopes (read:packages, read:stewardships,
read:maintainer-roles) still work on every operation below — each operation
lists both the new dedicated scope(s) and the old fallback scope(s) as
alternatives. Drop the old alternative once the cutover is confirmed.

servers:
- url: https://cm.lfx.dev/api/v1
description: Production

security:
- M2MBearer:
- read:akrites-packages
- M2MBearer:
- read:packages
- M2MBearer:
- read:stewardships

tags:
- name: Packages
description: Package detail — requires read:packages and read:stewardships (see TODO above).
description: >
Package detail — requires read:akrites-packages (dedicated Akrites Enclave
scope), or either read:packages or read:stewardships from the old Self Serve
client as a fallback until the cutover completes.
- name: Advisories
description: >
Security advisories for a package, split out of package detail. The draft
contract gates these behind a dedicated read:advisories scope; until Auth0
issues it, the implementation reuses read:packages (advisories need no
stewardship read). Confirm the final scope name (read:advisories vs
cdp:advisories:read) with Akrites/product.
Security advisories for a package, split out of package detail. Requires
read:akrites-advisories (dedicated Akrites Enclave scope), or read:packages
from the old Self Serve client as a fallback until the cutover completes
(advisories need no stewardship read).
- name: Contacts
description: >
Security contacts for a package — includes contact PII (e.g. reporter
emails). The contract gates these behind a dedicated cdp:maintainers:read
scope and forbids reaching them via the packages scope; until Auth0 issues
it, the implementation requires read:maintainer-roles (NOT read:packages).
The response shape is still under discussion upstream (reportingMethods /
reportingGuidelines / integrationHints are reserved and always null today).
emails). Requires read:akrites-maintainers (dedicated Akrites Enclave
scope), or read:maintainer-roles from the old Self Serve client as a
fallback until the cutover completes — never reachable via the packages
scopes. The response shape is still under discussion upstream
(reportingMethods / reportingGuidelines / integrationHints are reserved
and always null today).
- name: Blast Radius
description: >
Advisory reachability analysis — submit (2a) and poll (2b) are both
Expand All @@ -59,14 +68,14 @@ tags:
entry starts its own workflow — and stays behind the same strict rate
limiter as the single-job route; bulk poll
(jobs:batch/poll) is read-only and capped at 100 like the other batch
endpoints. Same interim scope note as Advisories applies (read:packages,
pending a dedicated read:advisories scope).
endpoints. Same scope note as Advisories applies (read:akrites-advisories,
or read:packages as a fallback until the cutover completes).

components:
securitySchemes:
# Modeled as oauth2/clientCredentials (not http/bearer): OpenAPI only allows
# non-empty scope arrays in security requirements for oauth2 / openIdConnect
# schemes, and the operations below require read:packages + read:stewardships.
# schemes.
M2MBearer:
type: oauth2
description: >
Expand All @@ -77,9 +86,12 @@ components:
clientCredentials:
tokenUrl: https://linuxfoundation.auth0.com/oauth/token
scopes:
read:packages: Read package detail
read:stewardships: Read package stewardship data
read:maintainer-roles: Read security contacts (interim scope for Contacts; see the Contacts tag)
read:akrites-packages: Read package detail (dedicated Akrites Enclave scope)
read:akrites-advisories: Read security advisories and submit/poll blast-radius jobs (dedicated Akrites Enclave scope)
read:akrites-maintainers: Read security contacts (dedicated Akrites Enclave scope)
read:packages: Read package detail (fallback — old Self Serve client scope, being phased out)
read:stewardships: Read package stewardship data (fallback — old Self Serve client scope, being phased out)
read:maintainer-roles: Read security contacts (fallback — old Self Serve client scope, being phased out)

schemas:
Error:
Expand Down Expand Up @@ -853,8 +865,11 @@ paths:
summary: Get package detail by PURL
tags: [Packages]
security:
- M2MBearer:
- read:akrites-packages
- M2MBearer:
- read:packages
- M2MBearer:
- read:stewardships
parameters:
- name: purl
Expand Down Expand Up @@ -883,7 +898,7 @@ paths:
schema:
$ref: '#/components/schemas/Error'
'403':
description: Token missing read:packages or read:stewardships scope.
description: Token missing read:akrites-packages, read:packages, or read:stewardships scope.
content:
application/json:
schema:
Expand All @@ -901,8 +916,11 @@ paths:
summary: Bulk package detail lookup
tags: [Packages]
security:
- M2MBearer:
- read:akrites-packages
- M2MBearer:
- read:packages
- M2MBearer:
- read:stewardships
requestBody:
required: true
Expand Down Expand Up @@ -960,7 +978,7 @@ paths:
schema:
$ref: '#/components/schemas/Error'
'403':
description: Token missing read:packages or read:stewardships scope.
description: Token missing read:akrites-packages, read:packages, or read:stewardships scope.
content:
application/json:
schema:
Expand All @@ -972,6 +990,8 @@ paths:
summary: Get advisories for a package by PURL
tags: [Advisories]
security:
- M2MBearer:
- read:akrites-advisories
- M2MBearer:
- read:packages
parameters:
Expand Down Expand Up @@ -1001,7 +1021,7 @@ paths:
schema:
$ref: '#/components/schemas/Error'
'403':
description: Token missing read:packages scope.
description: Token missing read:akrites-advisories, or read:packages, scope.
content:
application/json:
schema:
Expand All @@ -1019,6 +1039,8 @@ paths:
summary: Bulk advisory lookup
tags: [Advisories]
security:
- M2MBearer:
- read:akrites-advisories
- M2MBearer:
- read:packages
requestBody:
Expand Down Expand Up @@ -1077,7 +1099,7 @@ paths:
schema:
$ref: '#/components/schemas/Error'
'403':
description: Token missing read:packages scope.
description: Token missing read:akrites-advisories, or read:packages, scope.
content:
application/json:
schema:
Expand All @@ -1099,6 +1121,8 @@ paths:
Not yet implemented: the 7-day result cache and force-bypass semantics.
tags: [Blast Radius]
security:
- M2MBearer:
- read:akrites-advisories
- M2MBearer:
- read:packages
requestBody:
Expand Down Expand Up @@ -1131,7 +1155,7 @@ paths:
schema:
$ref: '#/components/schemas/Error'
'403':
description: Token missing read:packages scope.
description: Token missing read:akrites-advisories, or read:packages, scope.
content:
application/json:
schema:
Expand Down Expand Up @@ -1160,6 +1184,8 @@ paths:
back with status: 'failed' and the rest still submit.
tags: [Blast Radius]
security:
- M2MBearer:
- read:akrites-advisories
- M2MBearer:
- read:packages
requestBody:
Expand Down Expand Up @@ -1188,7 +1214,7 @@ paths:
schema:
$ref: '#/components/schemas/Error'
'403':
description: Token missing read:packages scope.
description: Token missing read:akrites-advisories, or read:packages, scope.
content:
application/json:
schema:
Expand All @@ -1212,6 +1238,8 @@ paths:
submit limiter.
tags: [Blast Radius]
security:
- M2MBearer:
- read:akrites-advisories
- M2MBearer:
- read:packages
requestBody:
Expand Down Expand Up @@ -1253,7 +1281,7 @@ paths:
schema:
$ref: '#/components/schemas/Error'
'403':
description: Token missing read:packages scope.
description: Token missing read:akrites-advisories, or read:packages, scope.
content:
application/json:
schema:
Expand All @@ -1269,6 +1297,8 @@ paths:
status.
tags: [Blast Radius]
security:
- M2MBearer:
- read:akrites-advisories
- M2MBearer:
- read:packages
parameters:
Expand Down Expand Up @@ -1298,7 +1328,7 @@ paths:
schema:
$ref: '#/components/schemas/Error'
'403':
description: Token missing read:packages scope.
description: Token missing read:akrites-advisories, or read:packages, scope.
content:
application/json:
schema:
Expand All @@ -1321,10 +1351,12 @@ paths:
operationId: getContactDetail
summary: Get security contact detail by PURL
description: >
Requires read:maintainer-roles (interim for the contract's cdp:maintainers:read).
Never reachable with the packages scope.
Requires read:akrites-maintainers, or read:maintainer-roles from the old Self
Serve client as a fallback. Never reachable with the packages scope.
tags: [Contacts]
security:
- M2MBearer:
- read:akrites-maintainers
- M2MBearer:
- read:maintainer-roles
parameters:
Expand Down Expand Up @@ -1354,7 +1386,7 @@ paths:
schema:
$ref: '#/components/schemas/Error'
'403':
description: Token missing read:maintainer-roles scope.
description: Token missing read:akrites-maintainers, or read:maintainer-roles, scope.
content:
application/json:
schema:
Expand All @@ -1372,6 +1404,8 @@ paths:
summary: Bulk security contact lookup
tags: [Contacts]
security:
- M2MBearer:
- read:akrites-maintainers
- M2MBearer:
- read:maintainer-roles
requestBody:
Expand Down Expand Up @@ -1430,7 +1464,7 @@ paths:
schema:
$ref: '#/components/schemas/Error'
'403':
description: Token missing read:maintainer-roles scope.
description: Token missing read:akrites-maintainers, or read:maintainer-roles, scope.
content:
application/json:
schema:
Expand Down Expand Up @@ -1472,6 +1506,8 @@ paths:
so callers aren't held open for the duration of the ingest.
tags: [Contacts]
security:
- M2MBearer:
- read:akrites-maintainers
- M2MBearer:
- read:maintainer-roles
requestBody:
Expand Down Expand Up @@ -1507,7 +1543,7 @@ paths:
schema:
$ref: '#/components/schemas/Error'
'403':
description: Token missing read:maintainer-roles scope.
description: Token missing read:akrites-maintainers, or read:maintainer-roles, scope.
content:
application/json:
schema:
Expand Down
Loading
Loading