Skip to content

Latest commit

 

History

24 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Monocle CloudFront Integration

Monocle assess/enforce edge protection for Amazon CloudFront, deployed click-to-deploy from the Monocle dashboard into the customer's own AWS account (cross-account IAM role). The customer pays AWS for the edge compute, exactly as Fastly and Cloudflare customers pay for Compute and Workers.

The shared contract lives in @spur.us/monocle-edge-core; the Cloudflare Worker is the reference implementation. This plugin is the CloudFront port of that contract, with the differences the platform forces listed below.

Architecture

Two runtimes split the work (see src/):

CloudFront Function (src/function/index.js) Lambda@Edge (src/lambda/)
Trigger viewer-request on the default behavior and every customer behavior origin-request on the /__mcl/* behaviors only
Job the guard ladder: host check, path canonicalization, verdict cookie, crawler and allow-list passes, breaker, refusal shells /__mcl/state, /__mcl/verify (Policy call and cookie minting), the challenge, resubmit and block pages, the resident script, the hourly crawler refresh
Cost and latency sub-millisecond, runs on every request runs only inside the challenge flow

Flow: a visitor without a valid decision opens an assessed page. The Function answers a 503 shell that navigates to /__mcl/challenge?return=<path>. The challenge page runs Monocle, POSTs the assessment to /__mcl/verify, the Lambda calls the Policy API and mints an HMAC-sealed __Host-mcl_c cookie (allow one hour, block ten minutes), the page reloads the return path, and the Function passes the request through to cache and origin untouched. Enforced paths refuse a cookieless request by shape (challenge shell, resubmit shell, challenge JSON, or an empty 403 for WebSockets) and answer a block verdict with the customer's block page, redirect or JSON.

Verified traffic and every uncovered path keep CloudFront's edge caching, because a viewer-request Function can return request.

Platform constraints this design encodes

  • CloudFront Functions (runtime 2.0): 10 KB source limit (build.mjs and test/function.test.ts enforce it on the stripped artifact), no network, no request body, crypto is createHmac/createHash only. So the cookie seal is HMAC-SHA256 over the edge core's v2 envelope, not AES-GCM, and the challenge page is not inlined: the Function serves a 300-byte shell that navigates to tier two. No injection is possible, so injection is always off on CloudFront.
  • Lambda@Edge (restrictions): us-east-1 only, numbered versions only, no environment variables. Secrets are baked as config.json into the deployment zip; everything a dashboard save can change is read from the KeyValueStore. The origin-request trigger is used because it allows 30 seconds and 1 MB bodies; viewer-request's 5 seconds cannot hold a Policy call.
  • KeyValueStore (quotas): values at most 1 KB, so long values chunk into .1, .2, … continuation keys. Updates apply without redeploying, so path, block-page and policy edits are live within seconds.
  • A cache behavior takes one function per event type: the Function and the Lambda live on different behaviors, and the dashboard pre-flights existing viewer-request associations before deploying.
  • Free flat-rate-plan distributions cannot attach a KVS-backed CloudFront Function (plan feature matrix). Customers need a Pro+ plan or classic pay-as-you-go.

Behaviors the dashboard attaches

Path pattern Cache policy Lambda Purpose
/__mcl/*/mcl.js CachingOptimized origin-request The resident script for the manual include; cached per segment
/__mcl/challenge CachingOptimized origin-request The visible check page; static, reads return client-side
/__mcl/* CachingDisabled origin-request, IncludeBody state, verify, blocked, resubmit

All three use the AllViewerExceptHostHeader origin-request policy so the Lambda sees the viewer's Origin, Cookie and Sec-Fetch headers.

Session tracking differs from the Worker in one place. The Worker tags the core URL with cpd=<sid> when it serves the challenge page or resident script; here both are cached and identical for every visitor, so GET /__mcl/state returns {hint, degraded, sid}, mints the session cookie when tracking is on and none is held, and the scripts append cpd themselves before loading the core. The session cookie is attribution only.

KeyValueStore keys (read by the Function; cv, cfg and hosts by the Lambda too)

Key Value Writer
v 2 dashboard
g generation the dashboard last wrote dashboard
hosts JSON array with the lowercase hostname the deployment protects (the dashboard writes one) dashboard

| k, kp | sealing key hex; previous key during rotation | dashboard | | cv | clearance version, 64 lowercase hex | dashboard | | id | deployment id, the cookie audience | dashboard | | cfg | JSON {session_tracking, block_page, custom_domain} | dashboard | | ips | packed allow_ips ranges | dashboard | | w | wildcard-segment patterns [{p, e}], at most 100 | dashboard | | p:<path> | e enforced exact, a assessed exact | dashboard | | s:<prefix> | e enforced subtree, a assessed subtree | dashboard | | bots | packed crawler ranges plus expiresAt | Lambda, hourly | | brk | breaker open-until epoch seconds | Lambda, on transition |

Route resolution is a walk: for /a/b/c the Function reads p:/a/b/c, then s:/a/b/c, s:/a/b, s:/a, s:/, then scans w. Enforcement applies if any e matches; assessment if any a matches. There is no specificity contest between the two, as in the edge core.

Lambda config.json (baked at deploy)

{
	"secretKey": "<monocle secret key>",
	"cookieSecret": "<hex sealing key>",
	"cookieSecretPrevious": "<hex, optional during rotation>",
	"publishableKey": "<monocle publishable key>",
	"deploymentId": "<app id>",
	"kvsArn": "arn:aws:cloudfront::<account>:key-value-store/<id>"
}

The Lambda's execution role needs cloudfront-keyvaluestore:DescribeKeyValueStore, GetKey and UpdateKeys on that store. An EventBridge Scheduler rule invokes the same function hourly with {"refresh":"crawlers"} to rewrite bots.

Develop

npm install
npm test        # vitest: the 10 KB size gate, the strict-v3 path corpus run through the
                # Function, the cross-pin that the Function opens what the Lambda mints,
                # and the endpoint contract
npm run build   # dist/function/index.js (stripped, size-checked, contract banner)
                # dist/lambda/index.js  (esbuild CJS bundle for node20, contract banner)

Both artifacts begin with // Monocle edge contract: 2; the dashboard refuses artifacts without it.

About

Monocle edge assessment and policy-based blocking for Amazon CloudFront. A viewer-request CloudFront Function serves uncookied visitors an interstitial and validates an HMAC-signed session cookie; a Lambda@Edge (/__mcl/verify) evaluates the Monocle policy and mints the cookie. Protected paths and block config are read from a KeyValueStore.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages