Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
76 changes: 76 additions & 0 deletions docs/LLMO-5566/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
---
ticket: LLMO-5566
repo: adobe/spacecat-api-service
branch: feature/LLMO-5566-cloudfront-log-delivery-assume-role
generated: 2026-06-26
---

# Implementation Plan: Automate CloudFront CDN Log Delivery with AssumeRole Setup (LLMO-5566)

## Problem Statement

SpaceCat's LLM Optimizer needs CDN access logs from customers' CloudFront distributions to power
traffic-analysis features. Previously there was no automated way to configure CloudWatch Logs
cross-account delivery from the customer's AWS account into Adobe's cdn-logs S3 bucket. Customers
also needed a guided wizard to wire up the prerequisite CloudFront configuration (origins, cache
behaviors, Lambda@Edge routing function).

## Solution Overview

Two parallel workstreams:

1. **CloudFront Edge Optimize Wizard** (15 endpoints) — an LLMO-admin-facing step-by-step wizard
that connects to the customer's AWS account via an assumed-role (STS AssumeRole) and configures
CloudFront distributions for edge optimization. Each step is a discrete, idempotent POST endpoint.

2. **CDN Log Delivery** (1 endpoint) — `POST /sites/:siteId/llmo/cdn-log-delivery` uses the same
connector role to create a CloudWatch Logs delivery-source in the customer's account and link it
to Adobe's cross-account delivery-destination, enabling automatic CDN access-log forwarding.

Both workstreams are gated by `isLLMOAdministrator()` and categorized as `INTERNAL_ROUTES` in the
FACS hybrid permission model (not surfaced on the external customer FACS API).

## Key Files Changed vs main

| File | Change |
|------|--------|
| `src/controllers/llmo/llmo.js` | +811 lines — 15 wizard endpoints + 1 CDN log delivery endpoint |
| `src/support/edge-optimize.js` | +1346 lines — CloudFront SDK operations (AssumeRole, origins, behaviors, Lambda@Edge) |
| `src/support/cdn-log-delivery.js` | +178 lines — CloudWatch Logs delivery-source + delivery creation |
| `src/routes/index.js` | +16 routes (15 wizard + cdn-log-delivery) |
| `src/routes/facs-capabilities.js` | +18 lines — routes registered in INTERNAL_ROUTES |
| `src/routes/required-capabilities.js` | +16 lines — routes registered in INTERNAL_ROUTES (S2S system) |
| `docs/openapi/llmo-api.yaml` | +1089 lines — full OpenAPI spec for all 16 new endpoints |
| `test/controllers/llmo/llmo.test.js` | +1595 lines — unit tests for all endpoints |
| `test/support/edge-optimize.test.js` | +1421 lines — unit tests for edge-optimize support layer |
| `test/support/cdn-log-delivery.test.js` | +182 lines — unit tests for CDN log delivery support |
| `test/e2e/llmo-cdn-log-delivery.e2e.js` | +476 lines — 4-tier e2e test suite |

## Auth and Permission Model

- All 16 endpoints require a valid SpaceCat session (JWT, IMS, or API key)
- `gateEdgeOptimizeWizard()` in `llmo.js` enforces: site must exist + user has site access +
`isLLMOAdministrator()` flag
- Routes are in `INTERNAL_ROUTES` (not `PRODUCTS_ROUTES`) — they are not part of the external
FACS customer permission surface

## E2E Test Strategy

Four tiers based on credential availability:

- **Tier 1** (always run): Input validation — 400 for missing/invalid body fields
- **Tier 2** (always run): Auth gate — 403 when called with a non-LLMO-admin API key
- **Tier 3** (LLMO_ADMIN_API_KEY required): Response shape — soft-fail AWS calls return
`{connected: false}` / `{checks: [{name, ok}]}`
- **Tier 4** (LLMO_ADMIN_API_KEY + TEST_AWS_ACCOUNT_ID + TEST_EXTERNAL_ID + TEST_DISTRIBUTION_ID):
Full AWS integration — real AssumeRole + distributions/origins/behaviors listing +
cdn-log-delivery idempotency

## Acceptance Criteria

- All 15 wizard endpoints + cdn-log-delivery reachable at correct paths
- All routes properly gated by `isLLMOAdministrator()`
- CDN log delivery is idempotent (returns `{alreadyExisted: true}` on repeat calls)
- FACS coverage invariant passes (`test/routes/facs-capabilities.test.js` — 26/26)
- Unit tests pass (`npm test`)
- OpenAPI spec validates (`npm run docs:lint`)
4 changes: 4 additions & 0 deletions docs/openapi/api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,10 @@ paths:
$ref: './llmo-api.yaml#/site-llmo-cloudfront-plan'
/sites/{siteId}/llmo/cdn-onboard/cloudfront/permissions:
$ref: './llmo-api.yaml#/site-llmo-cloudfront-permissions'
/sites/{siteId}/llmo/cdn-onboard/cloudfront/log-delivery:
$ref: './llmo-api.yaml#/site-llmo-cloudfront-log-delivery'
/sites/{siteId}/llmo/cdn-onboard/cloudfront/log-rescan:
$ref: './llmo-api.yaml#/site-llmo-cloudfront-log-rescan'
/sites/{siteId}/llmo/edge-optimize-status:
$ref: './llmo-api.yaml#/llmo-edge-optimize-status'
/sites/{siteId}/llmo/probes/edge-optimize:
Expand Down
101 changes: 101 additions & 0 deletions docs/openapi/llmo-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3451,6 +3451,107 @@ site-llmo-cloudfront-permissions:
security:
- api_key: [ ]

site-llmo-cloudfront-log-delivery:
post:
tags:
- llmo
summary: Enable CDN access-log forwarding for one CloudFront distribution
description: |
Enables CloudWatch Logs delivery of the selected CloudFront distribution's access logs to
Adobe's cross-account cdn-logs destination, via the customer's connector role. Idempotent —
returns `alreadyExisted: true` and mutates nothing when forwarding is already set up. The
assume-role `externalId` is the per-session value from bootstrap; the delivery destination is
org-scoped, resolved server-side from the site's IMS organization.
operationId: enableLlmoCloudFrontLogDelivery
parameters:
- $ref: './parameters.yaml#/siteId'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/cloudfront-distribution-request'
responses:
'200':
description: The CDN log-delivery result for the distribution.
content:
application/json:
schema:
type: object
properties:
created:
type: boolean
alreadyExisted:
type: boolean
deliverySourceName:
type: string
deliveryId:
type: string
'400':
$ref: './responses.yaml#/400'
'401':
$ref: './responses.yaml#/401'
'403':
$ref: './responses.yaml#/403'
'404':
$ref: './responses.yaml#/404'
'500':
$ref: './responses.yaml#/500'
security:
- api_key: [ ]

site-llmo-cloudfront-log-rescan:
post:
tags:
- llmo
summary: Re-scan and enable CDN log forwarding for all distributions
description: |
Idempotently enables CloudWatch Logs delivery for every CloudFront distribution in the
customer account. Intended for recovery/re-scan: e.g. after a new distribution is added or
a missed setup. Each distribution is attempted independently — one failure never aborts the
rest — and the response summarizes per-distribution outcomes.
operationId: rescanLlmoCloudFrontLogDelivery
parameters:
- $ref: './parameters.yaml#/siteId'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/cloudfront-connector-request'
responses:
'200':
description: Summary of the log-delivery re-scan across all distributions.
content:
application/json:
schema:
type: object
properties:
scanned:
type: integer
created:
type: integer
alreadyExisted:
type: integer
failed:
type: integer
distributions:
type: array
items:
type: object
'400':
$ref: './responses.yaml#/400'
'401':
$ref: './responses.yaml#/401'
'403':
$ref: './responses.yaml#/403'
'404':
$ref: './responses.yaml#/404'
'500':
$ref: './responses.yaml#/500'
security:
- api_key: [ ]

cloudfront-connector-request:
type: object
required:
Expand Down
Loading
Loading