Skip to content

feat(llmo): CloudFront CDN log delivery with assume-role setup (LLMO-5566)#2680

Open
claudiaboldis wants to merge 15 commits into
mainfrom
feature/LLMO-5566-cloudfront-log-delivery-assume-role
Open

feat(llmo): CloudFront CDN log delivery with assume-role setup (LLMO-5566)#2680
claudiaboldis wants to merge 15 commits into
mainfrom
feature/LLMO-5566-cloudfront-log-delivery-assume-role

Conversation

@claudiaboldis

Copy link
Copy Markdown

Summary

  • Adds server-side STS AssumeRole (connector role) so the LLMO UI CloudFront wizard never needs AWS credentials in the browser — all CloudFront and CloudWatch Logs calls are performed by the Lambda
  • Adds 5 read-only wizard endpoints (connect, distributions, prerequisites, origins, behaviors) that power the step-by-step CloudFront BYOCDN setup flow
  • Adds POST /sites/:siteId/llmo/cdn-log-delivery (diagram step 8) — idempotently creates a CloudWatch Logs delivery source in the customer's account and links it to Adobe's cross-account delivery destination, enabling CloudFront standard log push to the cdn-logs S3 bucket

New files

File Purpose
src/support/edge-optimize.js assumeConnectorRole (STS), listCloudFrontDistributions, getDistributionConfig, idempotent step-on-poll deploy orchestrator
src/support/cdn-log-delivery.js Provider-agnostic log-delivery registry; CloudFront path uses logs:PutDeliverySource + logs:CreateDelivery via the assumed role

Endpoints (all POST, all INTERNAL_ROUTES — admin/IMS-only)

  • POST /sites/:siteId/llmo/edge-optimize/connect
  • POST /sites/:siteId/llmo/edge-optimize/distributions
  • POST /sites/:siteId/llmo/edge-optimize/prerequisites
  • POST /sites/:siteId/llmo/edge-optimize/origins
  • POST /sites/:siteId/llmo/edge-optimize/behaviors
  • POST /sites/:siteId/llmo/cdn-log-delivery

⚠️ Cross-service dependencies (not in this PR — must land before prod)

  1. Connector-role IAM policy (customer-bootstrap-role.yaml in EDGE_OPTIMIZE_TEMPLATE_BUCKET) must add logs:PutDeliverySource, logs:CreateDelivery, logs:GetDeliverySource, logs:DescribeDeliveries — today the role only grants CloudFront perms
  2. spacecat-auth-service must provision the cdn-logs-<org> delivery destination + cross-account policy for the BYOCDN CloudFront path (the handler surfaces a clear "destination not provisioned" 400 if it's missing)

Test plan

# verify connector role assumable
curl -s -X POST "https://spacecat.experiencecloud.live/sites/$SITE_ID/llmo/edge-optimize/connect" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"accountId":"123456789012","externalId":"<externalId>"}'
# Expected: HTTP 200 { "connected": true }
# list distributions via assumed role
curl -s -X POST "https://spacecat.experiencecloud.live/sites/$SITE_ID/llmo/edge-optimize/distributions" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"accountId":"123456789012","externalId":"<externalId>"}'
# Expected: HTTP 200 { "distributions": [...] }
# enable log delivery (idempotent)
curl -s -X POST "https://spacecat.experiencecloud.live/sites/$SITE_ID/llmo/cdn-log-delivery" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"accountId":"123456789012","externalId":"<externalId>","distributionId":"EDFDVBD6EXAMPLE"}'
# Expected: HTTP 200 { "alreadyExisted": false, "deliveryId": "..." }
# Re-run: HTTP 200 { "alreadyExisted": true }

Related

🤖 Generated with Claude Code

@codecov

codecov Bot commented Jun 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@github-actions

Copy link
Copy Markdown

This PR will trigger a minor release when merged.

claudiaboldis pushed a commit that referenced this pull request Jun 26, 2026
…d, CF templates, setupType, rescan endpoint

- gateEdgeOptimizeWizard now derives externalId = toSafeAwsName(imsOrgId) server-side; FE no longer sends externalId
- getEdgeOptimizeBootstrapUrl accepts setupType ('log-only'|'log-and-oae') and selects the matching CF template key
- Add customer-bootstrap-role.yaml and customer-bootstrap-role-log-only.yaml CloudFormation templates
- Add POST /sites/:siteId/llmo/cdn-log-rescan (rescanCdnLogDelivery) for idempotent re-enabling of log delivery
- Remove 14 stale 'externalId missing' tests; add 9 tests for rescanCdnLogDelivery

Introduced by: #2680
claudiaboldis pushed a commit that referenced this pull request Jun 26, 2026
claudiaboldis pushed a commit that referenced this pull request Jun 26, 2026
…MO-5566)

- Remove dead !Organization early-return from gateEdgeOptimizeWizard
- Add test for custom EDGE_OPTIMIZE_ROLE_NAME env var path
- Add test for rejection with no .message (covers 'unknown error' fallback)

Introduced by: #2680
claudiaboldis pushed a commit that referenced this pull request Jun 26, 2026
…strapUrl (LLMO-5566)

- Add test for log-only setupType without EDGE_OPTIMIZE_TEMPLATE_KEY_LOG_ONLY (default fallback)
- Add test for missing IMS org ID in the bootstrap URL inline org check

Introduced by: #2680
@claudiaboldis claudiaboldis requested a review from ABHA61 June 26, 2026 22:09
claudiaboldis pushed a commit that referenced this pull request Jun 30, 2026
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
@claudiaboldis claudiaboldis force-pushed the feature/LLMO-5566-cloudfront-log-delivery-assume-role branch from f3bbb27 to 8cc66b0 Compare June 30, 2026 11:50
@ABHA61 ABHA61 requested a review from MysticatBot June 30, 2026 12:11

@MysticatBot MysticatBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @claudiaboldis,

Verdict: Request changes - two issues to address before merge (concurrency cap and HTTP status correctness).
Complexity: HIGH - large diff; API surface + new dependency.
Changes: Adds CDN log delivery endpoints (single-distribution and bulk rescan) using STS AssumeRole to create CloudWatch Logs delivery sources in customer accounts (13 files).
Note: Recommend a human read before merge - this change modifies the OpenAPI contract (docs/openapi/). The bot review is a complement to, not a replacement for, a human read here.

Must fix before merge

  1. [Important] Unbounded concurrency in rescanCdnLogDelivery will throttle on large accounts - src/controllers/llmo/llmo-cloudfront.js:1228. Promise.allSettled fires createCdnLogDelivery for ALL distributions in parallel with no concurrency cap. Each call issues 1-3 CloudWatch Logs API calls. A customer with 50+ distributions will hit CloudWatch Logs per-account throttling (10-25 TPS), causing most calls to fail with ThrottlingException. Fix: add a concurrency limiter (e.g. p-limit(5)) or batch distributions in small groups.
  2. [Important] Missing server config returns 400 (client error) instead of 500 - src/controllers/llmo/llmo-cloudfront.js:1164. When CDN_LOG_DELIVERY_DEST_ACCOUNT_ID is not set, badRequest(...) tells the caller their input is wrong when the server is misconfigured. Fix: return internalServerError(...) instead (same applies in rescanCdnLogDelivery at line ~1218).
Non-blocking (3): minor issues and suggestions
  • nit: docs/LLMO-5566/plan.md references src/controllers/llmo/llmo.js and 16 endpoints, but the actual controller is llmo-cloudfront.js and this PR delivers 2 endpoints - the plan is misleading from day one
  • suggestion: Both new handlers diverge from the existing mutationErrorResponse pattern used by sibling endpoints in this controller (which surfaces actionable AWS errors like AccessDeniedException and ThrottlingException as 400s instead of generic 500) - consider using mutationErrorResponse in the outer catch for consistency - src/controllers/llmo/llmo-cloudfront.js:1270
  • suggestion: rescanCdnLogDelivery response includes raw outcome.reason?.message per distribution, which can expose internal AWS error details (ARNs, role names) to the caller - consider sanitizing to error category only - src/controllers/llmo/llmo-cloudfront.js:1244

@MysticatBot MysticatBot added ai-reviewed Reviewed by AI complexity:high AI-assessed PR complexity: HIGH needs-human-review AI reviewer recommends a human read before merge labels Jun 30, 2026
@MysticatBot

Copy link
Copy Markdown

Mysticat review failed: Claude CLI crashed (exit 1): stderr= stdout={"type":"result","subtype":"error_max_budget_usd","duration_ms":628407,"duration_api_ms":1527224,"is_error":true,"num_turns":38,"stop_reason":"end_turn","session_id":"7e869476-18cc-497e-b372-17f8889bac58","total_cost_usd":10.07213945,"usage":{"input_tokens":19,"cache_creation_input_tokens":224486,"c

@ABHA61 ABHA61 requested a review from MysticatBot June 30, 2026 12:26
claudiaboldis pushed a commit that referenced this pull request Jun 30, 2026
- rescanCdnLogDelivery: cap parallel createCdnLogDelivery calls in batches of 5 to avoid
  CloudWatch Logs per-account throttling on large accounts
- missing CDN_LOG_DELIVERY_DEST_ACCOUNT_ID now returns 500 (server misconfig), not 400
- outer catch uses mutationErrorResponse so AWS AccessDenied/Throttling surface as 4xx,
  consistent with sibling CloudFront endpoints
- per-distribution rescan errors report the AWS error category (name) only, never the raw
  message (avoids leaking ARNs/role names)
- rewrite docs/LLMO-5566/plan.md to reflect the re-homed log-delivery design

Introduced by: #2680
claudiaboldis pushed a commit that referenced this pull request Jun 30, 2026
…-5566)

Address MysticatBot non-blocking suggestions on PR #2680:
- buildDeliverySourceName now hash-truncates names over 60 chars (CloudWatch Logs limit)
  using a deterministic sha256 suffix, so long IMS org / resource ids stay valid and the
  name remains stable across calls (idempotency depends on it)
- document why deliveryId may be undefined after a ConflictException race (eventual
  consistency; delivery is enabled regardless, so no retry loop)

Introduced by: #2680
@ABHA61

ABHA61 commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Reviewed the two new CDN log-delivery endpoints (enableCdnLogDelivery, rescanCdnLogDelivery), the cdn-log-delivery.js support module, wiring, tests, and OpenAPI — and cross-checked against the public CloudFront log-forwarding docs. Structure, routing, capability gating, client return shapes, OpenAPI (no drift), and the core idempotency logic all verified clean; temp AWS credentials / externalId / role ARNs are never logged or returned. Items worth addressing, grouped by severity:

# Sev · type Finding Loc
1 MED · correctness Idempotency matches on delivery-source name only, ignoring the destination ARN — an existing delivery to a different/older destination is reported alreadyExisted and never re-pointed, so if Adobe's destination ARN ever changes, logs silently keep flowing to the old place. cdn-log-delivery.js:157
2 MED · correctness PutDeliverySource throwing ConflictException (distribution already bound to another delivery source — e.g. a customer who set it up manually first) is not handled: enable returns 500, rescan marks it failed. The public CLI doc explicitly warns this exact error can occur, so it's a realistic idempotency break. cdn-log-delivery.js:191
3 MED · correctness/UX A valid-format but nonexistent/wrong-account distributionId makes PutDeliverySource throw ResourceNotFoundException, which the handler maps to "Adobe log destination is not provisioned — run cdn-logs provisioning first," sending ops to chase a nonexistent provisioning gap. llmo-cloudfront.js:1191
4 MED · security enableCdnLogDelivery enables forwarding for any caller-supplied distributionId in the account with no check that the distribution serves this site. In an AWS account shared across two IMS orgs, org X's admin can route org Y's distribution access logs into org X's cdn-logs bucket (confused-deputy / cross-org capture). The existing assertDistributionServesSite helper (with its allowDomainMismatch override) closes this — and also fixes #3. llmo-cloudfront.js:1153
5 MED · correctness/UX The AWS-error categorization set is CloudFront-only; CloudWatch delivery errors — ServiceQuotaExceededException (deliveries-per-account cap, likely on rescan), ValidationException, ConflictException, ExpiredTokenException — fall through to a generic 500 instead of an actionable 4xx. llmo-cloudfront.js:237
6 MED · operational CDN_LOG_DELIVERY_DEST_ACCOUNT_ID is a new required env var with no in-repo reference (absent from .env.example/README/config), and it isn't listed as a provisioning follow-up. If unset on deploy, both endpoints hard-500 for every call until it's added out-of-band. llmo-cloudfront.js:1173
7 MED · test-fidelity Tests assert only the result of createCdnLogDelivery, never the AWS command inputs (resourceArn, logType, recordFields, suffixPath, region, credentials) — a typo in any of these ships green and only surfaces at live onboarding. test/support/cdn-log-delivery.test.js:115
8 MED · test-fidelity Controller tests stub the helpers and never assert their args — passing the customer accountId as adobeAccountId, swapping imsOrgId, or handing over wrong credentials would misroute logs with all tests green. test/controllers/llmo/llmo-cloudfront.test.js:2346
9 LOW-MED · efficiency DescribeDeliveriesCommand is passed a deliverySourceName filter the API doesn't accept (SDK input is only nextToken/limit); it's silently dropped, so every call paginates all account deliveries and relies on a client-side .find. Correct today, but O(N×total) during rescan, and the comment misrepresents it as server-filtered. cdn-log-delivery.js:154
10 LOW-MED · security On categorized errors, enable returns the raw AWS message in the 400 body — for AccessDenied that includes Adobe's cross-account destination ARN (the dest account id) and the assumed-role ARN. The per-distribution rescan summary already avoids this by returning the error name only; enable should match. llmo-cloudfront.js:1205
11 LOW-MED · test-fidelity The "AccessDenied" test constructs new Error('AccessDeniedException: …') whose .name is Error, so it hits the 500 fallback, not the 4xx categorization branch — the test asserts 500 and passes, leaving the categorization path unexercised. test/…/llmo-cloudfront.test.js:2424
12 LOW-MED · UX Rescan has no ResourceNotFoundException short-circuit: if the destination isn't provisioned, every distribution fails and it returns 200 with all-failed and opaque per-item codes — no actionable message, unlike enable. llmo-cloudfront.js:1267
13 LOW-MED · scalability Rescan runs the whole account in one synchronous request reusing one set of assumed credentials (STS session is 15 min). A large account can exceed the ~60s gateway timeout and/or the 15-min credential lifetime, failing late batches with no summary returned. llmo-cloudfront.js:1243
14 LOW-MED · cross-service toSafeAwsName must match spacecat-auth-service byte-for-byte and only strips @AdobeOrg/@; a non-standard imsOrgId (dots/spaces) would produce an invalid source name or a divergent destination ARN (surfacing as the misleading #3 message). Standard IMS org ids are fine. cdn-log-delivery.js:58

Nits (optional, non-blocking):

  • accountId is derived via replace(/\D/g,'') + length-12 rather than a strict /^\d{12}$/, so embedded junk silently coerces to a 12-digit id (bounded by STS trust). llmo-cloudfront.js:183
  • findExistingDelivery's do…while(nextToken) has no max-page / seen-token guard (infinite-loop risk if the API echoes a stable token). cdn-log-delivery.js:151
  • Untested reachable branches: partial-state fall-through (source exists, no delivery → created:true) and empty-distributions rescan. cdn-log-delivery.test.js:134
  • Defense-in-depth: nothing server-side binds the client-supplied externalId/account to the site's org (STS bounds it; hardening = persist externalId at bootstrap and validate the account against the org). llmo-cloudfront.js:1179
  • ResourceAlreadyExistsException in the CreateDelivery catch is dead code (CreateDelivery signals a dup via ConflictException, which is handled); the >60-char source-name hash-truncation is effectively unreachable for real ids; the two methods duplicate ~20 lines of preamble (a small shared helper would prevent drift); enable's ResourceNotFoundException branch writes no audit line; a new CloudWatchLogsClient is built per createCdnLogDelivery call.

Please confirm:

  • IAM permissions — the code uses logs:PutDeliverySource, logs:CreateDelivery, logs:GetDeliverySource, and logs:DescribeDeliveries. Please confirm the bootstrap connector-role template grants all four (the public console doc lists only the first two), and that the template PR lands with or before this — otherwise the idempotency calls fail with AccessDenied.
  • Shared-client placement — the new CloudWatch log-delivery control-plane (cdn-log-delivery.js) sits in api-service, while the connector-role AWS operations (assumeConnectorRole, CloudFrontEdgeClient) live in @adobe/spacecat-shared-tokowaka-client. It's the same class of connector-role operation and other consumers may need to (re)enable delivery, so worth deciding whether it belongs in the shared client (that also keeps the toSafeAwsName byte-for-byte contract in one place). Related direction: the CloudFront control-plane currently lives in spacecat-shared-tokowaka-client, but longer-term it should move into a dedicated cloudfront-client package — mirroring the existing cloudflare-client — and this log-delivery code should follow to that same home. Fine as a fast follow-up.

claudiaboldis pushed a commit that referenced this pull request Jul 16, 2026
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
claudiaboldis pushed a commit that referenced this pull request Jul 16, 2026
- rescanCdnLogDelivery: cap parallel createCdnLogDelivery calls in batches of 5 to avoid
  CloudWatch Logs per-account throttling on large accounts
- missing CDN_LOG_DELIVERY_DEST_ACCOUNT_ID now returns 500 (server misconfig), not 400
- outer catch uses mutationErrorResponse so AWS AccessDenied/Throttling surface as 4xx,
  consistent with sibling CloudFront endpoints
- per-distribution rescan errors report the AWS error category (name) only, never the raw
  message (avoids leaking ARNs/role names)
- rewrite docs/LLMO-5566/plan.md to reflect the re-homed log-delivery design

Introduced by: #2680
claudiaboldis pushed a commit that referenced this pull request Jul 16, 2026
Addresses MysticatBot review on PR #2680:
- cdn-log-delivery: catch ConflictException/ResourceAlreadyExistsException from CreateDelivery
  (TOCTOU between concurrent enable/rescan for the same distribution) and return
  { created:false, alreadyExisted:true } with the winning delivery id, preserving idempotency
  instead of surfacing a 500
- add integration tests (test/it/postgres + shared) for the log-delivery and log-rescan
  endpoints covering the LLMO-admin access gate (404/403) and request-body validation (400),
  mirroring the llmo-cloudflare-onboarding IT pattern

Introduced by: #2680
@claudiaboldis claudiaboldis force-pushed the feature/LLMO-5566-cloudfront-log-delivery-assume-role branch from 533a216 to 9669a50 Compare July 16, 2026 07:46
claudiaboldis pushed a commit that referenced this pull request Jul 16, 2026
…LMO-5566)

Close the codecov/patch gap from the TOCTOU fix: add tests for
- CreateDelivery rejecting with a non-conflict error (rethrow path)
- conflict where the raced delivery can't be found (findExistingDelivery returns undefined)

Introduced by: #2680
claudiaboldis pushed a commit that referenced this pull request Jul 16, 2026
…-5566)

Address MysticatBot non-blocking suggestions on PR #2680:
- buildDeliverySourceName now hash-truncates names over 60 chars (CloudWatch Logs limit)
  using a deterministic sha256 suffix, so long IMS org / resource ids stay valid and the
  name remains stable across calls (idempotency depends on it)
- document why deliveryId may be undefined after a ConflictException race (eventual
  consistency; delivery is enabled regardless, so no retry loop)

Introduced by: #2680
claudiaboldis pushed a commit that referenced this pull request Jul 16, 2026
Security, correctness and test-fidelity fixes:

- assertDistributionServesSite guard in enableCdnLogDelivery (#4/#3)
- findExistingDelivery validates destination ARN, not just source name (#1)
- PutDeliverySource ConflictException surfaces as DeliverySourceConflict 4xx (#2)
- Remove invalid deliverySourceName filter from DescribeDeliveries SDK call (#9)
- Add CW Logs errors to CATEGORIZED_AWS_ERRORS (#5)
- mutationErrorResponse returns error name only, not full message (#10)
- rescanCdnLogDelivery: all-ResourceNotFoundException short-circuit (#12)
- CDN_LOG_DELIVERY_DEST_ACCOUNT_ID documented in .env.example (#6)
- AccessDenied test: properly named error, split 400/500 cases (#11)
- findExistingDelivery stubs include deliveryDestinationArn
- New test: PutDeliverySource ConflictException -> DeliverySourceConflict

Also fix pre-existing TS errors from main (ts-ignore DrsClient missing methods
in brands.js; null->undefined coercion in rest-transport.js).

Introduced by: #2680
claudiaboldis pushed a commit that referenced this pull request Jul 16, 2026
- Strict 12-digit accountId validation (reject non-digit chars outright)
- toSafeAwsName guard: throw on IMS org IDs that produce invalid AWS names
- Remove dead ResourceAlreadyExistsException from CreateDelivery catch
- Add 50-page guard to findExistingDelivery paginator
- Audit log on ResourceNotFoundException path in enableCdnLogDelivery
- CDN_LOG_RESCAN_MAX_DISTRIBUTIONS cap in rescanCdnLogDelivery (truncated+totalFound)
- Assert AWS command inputs (put/create) in cdn-log-delivery tests
- Partial-state recovery test (source exists, no delivery -> created:true)
- empty-distributions rescan test + max-distributions cap test

Introduced by: #2680

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
cboldis and others added 8 commits July 16, 2026 10:50
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
- rescanCdnLogDelivery: cap parallel createCdnLogDelivery calls in batches of 5 to avoid
  CloudWatch Logs per-account throttling on large accounts
- missing CDN_LOG_DELIVERY_DEST_ACCOUNT_ID now returns 500 (server misconfig), not 400
- outer catch uses mutationErrorResponse so AWS AccessDenied/Throttling surface as 4xx,
  consistent with sibling CloudFront endpoints
- per-distribution rescan errors report the AWS error category (name) only, never the raw
  message (avoids leaking ARNs/role names)
- rewrite docs/LLMO-5566/plan.md to reflect the re-homed log-delivery design

Introduced by: #2680
Addresses MysticatBot review on PR #2680:
- cdn-log-delivery: catch ConflictException/ResourceAlreadyExistsException from CreateDelivery
  (TOCTOU between concurrent enable/rescan for the same distribution) and return
  { created:false, alreadyExisted:true } with the winning delivery id, preserving idempotency
  instead of surfacing a 500
- add integration tests (test/it/postgres + shared) for the log-delivery and log-rescan
  endpoints covering the LLMO-admin access gate (404/403) and request-body validation (400),
  mirroring the llmo-cloudflare-onboarding IT pattern

Introduced by: #2680
…LMO-5566)

Close the codecov/patch gap from the TOCTOU fix: add tests for
- CreateDelivery rejecting with a non-conflict error (rethrow path)
- conflict where the raced delivery can't be found (findExistingDelivery returns undefined)

Introduced by: #2680
…-5566)

Address MysticatBot non-blocking suggestions on PR #2680:
- buildDeliverySourceName now hash-truncates names over 60 chars (CloudWatch Logs limit)
  using a deterministic sha256 suffix, so long IMS org / resource ids stay valid and the
  name remains stable across calls (idempotency depends on it)
- document why deliveryId may be undefined after a ConflictException race (eventual
  consistency; delivery is enabled regardless, so no retry loop)

Introduced by: #2680
Security, correctness and test-fidelity fixes:

- assertDistributionServesSite guard in enableCdnLogDelivery (#4/#3)
- findExistingDelivery validates destination ARN, not just source name (#1)
- PutDeliverySource ConflictException surfaces as DeliverySourceConflict 4xx (#2)
- Remove invalid deliverySourceName filter from DescribeDeliveries SDK call (#9)
- Add CW Logs errors to CATEGORIZED_AWS_ERRORS (#5)
- mutationErrorResponse returns error name only, not full message (#10)
- rescanCdnLogDelivery: all-ResourceNotFoundException short-circuit (#12)
- CDN_LOG_DELIVERY_DEST_ACCOUNT_ID documented in .env.example (#6)
- AccessDenied test: properly named error, split 400/500 cases (#11)
- findExistingDelivery stubs include deliveryDestinationArn
- New test: PutDeliverySource ConflictException -> DeliverySourceConflict

Also fix pre-existing TS errors from main (ts-ignore DrsClient missing methods
in brands.js; null->undefined coercion in rest-transport.js).

Introduced by: #2680
- Strict 12-digit accountId validation (reject non-digit chars outright)
- toSafeAwsName guard: throw on IMS org IDs that produce invalid AWS names
- Remove dead ResourceAlreadyExistsException from CreateDelivery catch
- Add 50-page guard to findExistingDelivery paginator
- Audit log on ResourceNotFoundException path in enableCdnLogDelivery
- CDN_LOG_RESCAN_MAX_DISTRIBUTIONS cap in rescanCdnLogDelivery (truncated+totalFound)
- Assert AWS command inputs (put/create) in cdn-log-delivery tests
- Partial-state recovery test (source exists, no delivery -> created:true)
- empty-distributions rescan test + max-distributions cap test

Introduced by: #2680

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…er-side derivation

externalId is now derived server-side from the site's IMS org id (resolveConnectorExternalId),
so the client no longer supplies it. Remove the two tests that expected a 400 when externalId
was absent from the request body, and align the imsOrgId assertion with TEST_IMS_ORG_ID.

Introduced by: N/A
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@claudiaboldis claudiaboldis force-pushed the feature/LLMO-5566-cloudfront-log-delivery-assume-role branch from 9669a50 to b6d1aca Compare July 16, 2026 08:15
@claudiaboldis

Copy link
Copy Markdown
Author

IAM permissions — confirmed. Both customer-bootstrap-role.yaml and customer-bootstrap-role-log-only.yaml already grant all four permissions: logs:PutDeliverySource, logs:CreateDelivery, logs:GetDeliverySource, and logs:DescribeDeliveries. The templates are included in this PR (added in the same commit), so they land together — no sequencing risk.

Shared-client placement — deferred, tracked. Agreed that cdn-log-delivery.js belongs in a shared package long-term. Moving it now would block this PR on a shared-client release cycle and add unrelated churn. The plan is to follow the CloudFront control-plane when it migrates to a dedicated cloudfront-client package (mirroring cloudflare-client), and cdn-log-delivery.js + toSafeAwsName move there at that point — keeping the byte-for-byte contract in one place. Will track as a follow-up ticket.

cboldis and others added 3 commits July 16, 2026 11:49
The previous fix used `?? undefined` to satisfy TypeScript, but `null ?? undefined`
evaluates to `undefined`, which JSON-serializes as a missing key — breaking the
null-promotion contract. Cast to `any` instead so the null is preserved on the wire
while TypeScript is satisfied.

Introduced by: N/A
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…elivery (LLMO-5566)

Adds two customer-facing CloudFormation templates that provision the cross-account
IAM role required for CDN log delivery:
- customer-bootstrap-role.yaml: full edge-optimize + log delivery permissions
- customer-bootstrap-role-log-only.yaml: log delivery permissions only

Both grant logs:PutDeliverySource, logs:CreateDelivery, logs:GetDeliverySource,
and logs:DescribeDeliveries — all four operations used by enableCdnLogDelivery
and rescanCdnLogDelivery.

Introduced by: #2680
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…d (LLMO-5566)

Mirrors the unit test fix: externalId is no longer accepted from the client
(it is derived server-side from the site's IMS org id), so the IT tests that
expected a 400 for a missing externalId are removed for both log-delivery and
log-rescan.

Introduced by: N/A
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
cboldis and others added 4 commits July 16, 2026 14:40
- buildDeliverySourceName: throws when org id produces empty/invalid AWS name segment
- createCdnLogDelivery: rethrows non-conflict error from PutDeliverySource
- enableCdnLogDelivery: returns 400 when distribution does not serve the site (guard.error path)
- rescanCdnLogDelivery: returns 400 when all distributions fail with ResourceNotFoundException

Introduced by: #2680

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-reviewed Reviewed by AI complexity:high AI-assessed PR complexity: HIGH needs-human-review AI reviewer recommends a human read before merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants