Skip to content

Commit 8cc66b0

Browse files
committed
feat(llmo): CDN log delivery on CloudFront connector (LLMO-5566)
main #2682 shipped the CloudFront onboarding wizard (LlmoCloudFrontController, cdn-onboard/cloudfront/*) while this branch was open, making the branch's parallel edge-optimize/* wizard redundant. Reset onto main and rebuilt around the only net-new slice: CloudWatch log delivery. - Add enableCdnLogDelivery + rescanCdnLogDelivery on LlmoCloudFrontController under cdn-onboard/cloudfront/log-delivery and .../log-rescan - Assume-role externalId uses main's client-supplied per-session UUID (validateCloudfrontCredentials); imsOrgId for the org-scoped delivery destination is resolved server-side from the site's organization - Reuse tokowaka CloudFrontEdgeClient.listDistributions for the rescan (no local edge-optimize support module needed) - Add src/support/cdn-log-delivery.js (CloudWatch Logs delivery control plane) and @aws-sdk/client-cloudwatch-logs; OpenAPI + capability/route entries; tests Introduced by: #2680
1 parent d4666f9 commit 8cc66b0

13 files changed

Lines changed: 1177 additions & 25 deletions

File tree

docs/LLMO-5566/plan.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
---
2+
ticket: LLMO-5566
3+
repo: adobe/spacecat-api-service
4+
branch: feature/LLMO-5566-cloudfront-log-delivery-assume-role
5+
generated: 2026-06-26
6+
---
7+
8+
# Implementation Plan: Automate CloudFront CDN Log Delivery with AssumeRole Setup (LLMO-5566)
9+
10+
## Problem Statement
11+
12+
SpaceCat's LLM Optimizer needs CDN access logs from customers' CloudFront distributions to power
13+
traffic-analysis features. Previously there was no automated way to configure CloudWatch Logs
14+
cross-account delivery from the customer's AWS account into Adobe's cdn-logs S3 bucket. Customers
15+
also needed a guided wizard to wire up the prerequisite CloudFront configuration (origins, cache
16+
behaviors, Lambda@Edge routing function).
17+
18+
## Solution Overview
19+
20+
Two parallel workstreams:
21+
22+
1. **CloudFront Edge Optimize Wizard** (15 endpoints) — an LLMO-admin-facing step-by-step wizard
23+
that connects to the customer's AWS account via an assumed-role (STS AssumeRole) and configures
24+
CloudFront distributions for edge optimization. Each step is a discrete, idempotent POST endpoint.
25+
26+
2. **CDN Log Delivery** (1 endpoint) — `POST /sites/:siteId/llmo/cdn-log-delivery` uses the same
27+
connector role to create a CloudWatch Logs delivery-source in the customer's account and link it
28+
to Adobe's cross-account delivery-destination, enabling automatic CDN access-log forwarding.
29+
30+
Both workstreams are gated by `isLLMOAdministrator()` and categorized as `INTERNAL_ROUTES` in the
31+
FACS hybrid permission model (not surfaced on the external customer FACS API).
32+
33+
## Key Files Changed vs main
34+
35+
| File | Change |
36+
|------|--------|
37+
| `src/controllers/llmo/llmo.js` | +811 lines — 15 wizard endpoints + 1 CDN log delivery endpoint |
38+
| `src/support/edge-optimize.js` | +1346 lines — CloudFront SDK operations (AssumeRole, origins, behaviors, Lambda@Edge) |
39+
| `src/support/cdn-log-delivery.js` | +178 lines — CloudWatch Logs delivery-source + delivery creation |
40+
| `src/routes/index.js` | +16 routes (15 wizard + cdn-log-delivery) |
41+
| `src/routes/facs-capabilities.js` | +18 lines — routes registered in INTERNAL_ROUTES |
42+
| `src/routes/required-capabilities.js` | +16 lines — routes registered in INTERNAL_ROUTES (S2S system) |
43+
| `docs/openapi/llmo-api.yaml` | +1089 lines — full OpenAPI spec for all 16 new endpoints |
44+
| `test/controllers/llmo/llmo.test.js` | +1595 lines — unit tests for all endpoints |
45+
| `test/support/edge-optimize.test.js` | +1421 lines — unit tests for edge-optimize support layer |
46+
| `test/support/cdn-log-delivery.test.js` | +182 lines — unit tests for CDN log delivery support |
47+
| `test/e2e/llmo-cdn-log-delivery.e2e.js` | +476 lines — 4-tier e2e test suite |
48+
49+
## Auth and Permission Model
50+
51+
- All 16 endpoints require a valid SpaceCat session (JWT, IMS, or API key)
52+
- `gateEdgeOptimizeWizard()` in `llmo.js` enforces: site must exist + user has site access +
53+
`isLLMOAdministrator()` flag
54+
- Routes are in `INTERNAL_ROUTES` (not `PRODUCTS_ROUTES`) — they are not part of the external
55+
FACS customer permission surface
56+
57+
## E2E Test Strategy
58+
59+
Four tiers based on credential availability:
60+
61+
- **Tier 1** (always run): Input validation — 400 for missing/invalid body fields
62+
- **Tier 2** (always run): Auth gate — 403 when called with a non-LLMO-admin API key
63+
- **Tier 3** (LLMO_ADMIN_API_KEY required): Response shape — soft-fail AWS calls return
64+
`{connected: false}` / `{checks: [{name, ok}]}`
65+
- **Tier 4** (LLMO_ADMIN_API_KEY + TEST_AWS_ACCOUNT_ID + TEST_EXTERNAL_ID + TEST_DISTRIBUTION_ID):
66+
Full AWS integration — real AssumeRole + distributions/origins/behaviors listing +
67+
cdn-log-delivery idempotency
68+
69+
## Acceptance Criteria
70+
71+
- All 15 wizard endpoints + cdn-log-delivery reachable at correct paths
72+
- All routes properly gated by `isLLMOAdministrator()`
73+
- CDN log delivery is idempotent (returns `{alreadyExisted: true}` on repeat calls)
74+
- FACS coverage invariant passes (`test/routes/facs-capabilities.test.js` — 26/26)
75+
- Unit tests pass (`npm test`)
76+
- OpenAPI spec validates (`npm run docs:lint`)

docs/openapi/api.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,10 @@ paths:
666666
$ref: './llmo-api.yaml#/site-llmo-cloudfront-plan'
667667
/sites/{siteId}/llmo/cdn-onboard/cloudfront/permissions:
668668
$ref: './llmo-api.yaml#/site-llmo-cloudfront-permissions'
669+
/sites/{siteId}/llmo/cdn-onboard/cloudfront/log-delivery:
670+
$ref: './llmo-api.yaml#/site-llmo-cloudfront-log-delivery'
671+
/sites/{siteId}/llmo/cdn-onboard/cloudfront/log-rescan:
672+
$ref: './llmo-api.yaml#/site-llmo-cloudfront-log-rescan'
669673
/sites/{siteId}/llmo/edge-optimize-status:
670674
$ref: './llmo-api.yaml#/llmo-edge-optimize-status'
671675
/sites/{siteId}/llmo/probes/edge-optimize:

docs/openapi/llmo-api.yaml

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3451,6 +3451,107 @@ site-llmo-cloudfront-permissions:
34513451
security:
34523452
- api_key: [ ]
34533453

3454+
site-llmo-cloudfront-log-delivery:
3455+
post:
3456+
tags:
3457+
- llmo
3458+
summary: Enable CDN access-log forwarding for one CloudFront distribution
3459+
description: |
3460+
Enables CloudWatch Logs delivery of the selected CloudFront distribution's access logs to
3461+
Adobe's cross-account cdn-logs destination, via the customer's connector role. Idempotent —
3462+
returns `alreadyExisted: true` and mutates nothing when forwarding is already set up. The
3463+
assume-role `externalId` is the per-session value from bootstrap; the delivery destination is
3464+
org-scoped, resolved server-side from the site's IMS organization.
3465+
operationId: enableLlmoCloudFrontLogDelivery
3466+
parameters:
3467+
- $ref: './parameters.yaml#/siteId'
3468+
requestBody:
3469+
required: true
3470+
content:
3471+
application/json:
3472+
schema:
3473+
$ref: '#/cloudfront-distribution-request'
3474+
responses:
3475+
'200':
3476+
description: The CDN log-delivery result for the distribution.
3477+
content:
3478+
application/json:
3479+
schema:
3480+
type: object
3481+
properties:
3482+
created:
3483+
type: boolean
3484+
alreadyExisted:
3485+
type: boolean
3486+
deliverySourceName:
3487+
type: string
3488+
deliveryId:
3489+
type: string
3490+
'400':
3491+
$ref: './responses.yaml#/400'
3492+
'401':
3493+
$ref: './responses.yaml#/401'
3494+
'403':
3495+
$ref: './responses.yaml#/403'
3496+
'404':
3497+
$ref: './responses.yaml#/404'
3498+
'500':
3499+
$ref: './responses.yaml#/500'
3500+
security:
3501+
- api_key: [ ]
3502+
3503+
site-llmo-cloudfront-log-rescan:
3504+
post:
3505+
tags:
3506+
- llmo
3507+
summary: Re-scan and enable CDN log forwarding for all distributions
3508+
description: |
3509+
Idempotently enables CloudWatch Logs delivery for every CloudFront distribution in the
3510+
customer account. Intended for recovery/re-scan: e.g. after a new distribution is added or
3511+
a missed setup. Each distribution is attempted independently — one failure never aborts the
3512+
rest — and the response summarizes per-distribution outcomes.
3513+
operationId: rescanLlmoCloudFrontLogDelivery
3514+
parameters:
3515+
- $ref: './parameters.yaml#/siteId'
3516+
requestBody:
3517+
required: true
3518+
content:
3519+
application/json:
3520+
schema:
3521+
$ref: '#/cloudfront-connector-request'
3522+
responses:
3523+
'200':
3524+
description: Summary of the log-delivery re-scan across all distributions.
3525+
content:
3526+
application/json:
3527+
schema:
3528+
type: object
3529+
properties:
3530+
scanned:
3531+
type: integer
3532+
created:
3533+
type: integer
3534+
alreadyExisted:
3535+
type: integer
3536+
failed:
3537+
type: integer
3538+
distributions:
3539+
type: array
3540+
items:
3541+
type: object
3542+
'400':
3543+
$ref: './responses.yaml#/400'
3544+
'401':
3545+
$ref: './responses.yaml#/401'
3546+
'403':
3547+
$ref: './responses.yaml#/403'
3548+
'404':
3549+
$ref: './responses.yaml#/404'
3550+
'500':
3551+
$ref: './responses.yaml#/500'
3552+
security:
3553+
- api_key: [ ]
3554+
34543555
cloudfront-connector-request:
34553556
type: object
34563557
required:

0 commit comments

Comments
 (0)