Multi-Domain Guest Page Architecture: CloudFront Path Rewriting and Cross-Service Lambda Coordination
This post documents the technical decisions and infrastructure changes made to consolidate guest-facing charter booking pages across multiple domains while maintaining separate backend service ownership and avoiding credential exposure in client-side code.
Problem Statement
A charter booking system needed to:
- Generate guest-facing pages at predictable, short URLs (
/g/XHQGMDH) - Host these pages on a public-facing domain (
queenofsandiego.com) separate from the operational crew platform (sailjada.com) - Coordinate between two independent Lambda-based services (ShipCaptainCrew and JADA Internal Calendar) without exposing service credentials to the browser
- Handle authenticated API calls from a static HTML page in S3 without storing secrets client-side
Solution Architecture
Domain and Storage Strategy
Guest pages were deployed to queenofsandiego.com CloudFront distribution (not the crew platform at sailjada.com). This separation serves multiple purposes:
- Security boundary: Public guest pages are isolated from crew-only content
- CDN efficiency: Separate distributions allow different cache policies (guest pages cache longer; crew pages refresh frequently)
- Operational clarity: Guest URLs never expose internal operational domain names to customers
Pages are stored as flat .html files in the S3 bucket backing queenofsandiego.com, following the CloudFront function convention that rewrites requests to /g/XHQGMDH to /g/XHQGMDH.html.
Path Rewriting via CloudFront Functions
CloudFront Functions (not Lambda@Edge) handle the URL rewrite for guest pages. The live function code at the queenofsandiego.com distribution performs this transformation:
Request: /g/XHQGMDH
↓ (CloudFront Function rewrites)
S3 lookup: /g/XHQGMDH.html
This approach avoids Lambda@Edge overhead while maintaining clean guest-facing URLs. CloudFront Functions execute in under 1ms at edge locations with no cold start.
Authentication Without Client-Side Secrets
The guest page itself is public HTML/CSS/JavaScript. But it needs to:
- Call ShipCaptainCrew Lambda to fetch crew details and presigned photo upload URLs
- Potentially submit confirmations back to operational systems
Rather than embed service credentials in the HTML, the architecture uses:
- API Gateway endpoints: ShipCaptainCrew Lambda is exposed via API Gateway at a dedicated endpoint (not through CloudFront, which strips custom headers)
- Service-to-service auth: The backend Lambda that generates the guest page includes an embedded session token or uses time-bound signed requests
- CORS configuration: API Gateway allows requests from
queenofsandiego.comorigins only
This way, the guest page calls an API Gateway endpoint directly (which preserves auth headers), not a CloudFront distribution (which strips non-standard headers like X-Service-Key).
Infrastructure Components
S3 Buckets
sailjada.com— Crew platform and operational backend assetsqueenofsandiego.com(public) — Guest-facing pages, stored as/g/*.htmlfiles
CloudFront Distributions
- sailjada.com distribution: Origin is the S3 bucket; serves crew dashboard and admin panels
- queenofsandiego.com distribution: Origin is its S3 bucket; includes a CloudFront Function that rewrites
/g/[ID]to/g/[ID].html
Lambda Functions
- ShipCaptainCrew Lambda: Exposed via API Gateway (not CloudFront) to allow custom auth headers; handles crew data, event creation, and photo presign operations
- JADA Calendar Lambda: Backend-only; called from update_dashboard.py or directly from ShipCaptainCrew; creates calendar entries with dashboard token auth
- Guest page generator (TBD in deployment): Dynamically builds guest page HTML, embeds session tokens, uploads to
queenofsandiego.comS3 bucket
Key Technical Decisions
Why Not Use CloudFront for ShipCaptainCrew API Calls?
CloudFront strips non-standard HTTP headers for security. The ShipCaptainCrew Lambda expects an X-Service-Key or similar auth header. Routing guest pages through CloudFront for API calls would lose this header. Solution: expose the Lambda directly via API Gateway, which preserves all headers. Guest pages call https://api.example.com/g/presign instead of going through CloudFront.
Why Flat .html Files Over /index.html Convention?
CloudFront Functions are cheaper and faster than Lambda@Edge, but they have a 1KB context limit and limited string manipulation. The simplest convention that works: store each guest page as /g/XHQGMDH.html and rewrite /g/XHQGMDH → /g/XHQGMDH.html`. This avoids directory traversal and keeps S3 listings flat.
Why Separate queenofsandiego.com from sailjada.com?
Public guest pages should never reveal the crew platform domain. If a guest inspects network traffic or HTML source, they shouldn't learn that internal operations run on sailjada.com. This is a defense-in-depth practice. Additionally, separate CloudFront distributions allow independent cache invalidation and SSL certificate management.
Deployment Workflow
- Generate guest page HTML: Backend logic creates an
.htmlfile with event details, crew list, and JavaScript for API calls. - Upload to S3:
aws s3 cp /tmp/jada-guest-XHQGMDH.html s3://queenofsandiego.com/g/XHQGMDH.html - Invalidate CloudFront cache:
aws cloudfront create-invalidation --distribution-id [DIST_ID] --paths "/g/XHQGMDH" - Verify live: Request
https://queenofsandiego.com/g/XHQGMDHand confirm CloudFront Function rewrites to.html
Security Considerations
- No credentials in HTML: Guest pages contain only public event metadata and JavaScript that calls API Gateway with request-time auth (e.g.,