shoc-frontend-new/infra/cdk
Alexandre Brandizzi 8bd89a1f9d feat(infra): proxy /api through CloudFront to the backend
The SPA is served over HTTPS by CloudFront but the backend
(console.seahavenind.com) is HTTP-only, so direct API calls would be blocked
as mixed content. Add a CloudFront /api/* behavior that proxies to the backend
over HTTP (browser <-> CloudFront is HTTPS; CloudFront <-> origin is HTTP) and
set VITE_API_URL=/api (same-origin).

Because distribution-level customErrorResponses are global and would rewrite
real /api 403/404s into the SPA shell, replace them with a viewer-request
CloudFront Function scoped to the S3 (default) behavior that rewrites
extensionless paths to /index.html. /api/* carries no function association.

Backend host is configurable via `-c apiOriginDomain=<host>` (default
console.seahavenind.com).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 14:17:27 -03:00
..
bin feat(infra): proxy /api through CloudFront to the backend 2026-07-02 14:17:27 -03:00
lib feat(infra): proxy /api through CloudFront to the backend 2026-07-02 14:17:27 -03:00
cdk.json ci(deploy): add AWS S3 + CloudFront CD pipeline 2026-06-29 13:39:53 -03:00
package-lock.json ci(deploy): add AWS S3 + CloudFront CD pipeline 2026-06-29 13:39:53 -03:00
package.json ci(deploy): add AWS S3 + CloudFront CD pipeline 2026-06-29 13:39:53 -03:00
README.md feat(infra): proxy /api through CloudFront to the backend 2026-07-02 14:17:27 -03:00
tsconfig.json ci(deploy): add AWS S3 + CloudFront CD pipeline 2026-06-29 13:39:53 -03:00

Infrastructure & CI/CD — SeaHaven SHOC frontend

AWS hosting for the Vite SPA, defined as an AWS CDK app local to this repo, deployed through the org's reusable GitHub Actions workflow.

  • Hosting: private S3 bucket (origin) + CloudFront (CDN, HTTPS, SPA routing)
  • API: CloudFront proxies /api/* to the HTTP-only backend, so the HTTPS SPA calls it same-origin (no mixed-content blocking). VITE_API_URL=/api.
  • Auth: GitHub Actions → AWS via OIDC (no long-lived keys)
  • CD workflow: .github/workflows/deploy.yml is a thin caller of the org's Sea-Haven-Industries/.github → cd-cdk.yaml. That workflow runs cdk deploy (provisions infra) then scripts/deploy-web.sh (builds + uploads the SPA).
  • Infra is local to this repo (CDK in infra/cdk); the deploy role is created by this stack, not added to the central oidc-deploy-roles.yaml.
  • Environments: dev only today, deployed on push to the dev branch.
infra/cdk/
  bin/app.ts            entry point (reads -c context)
  lib/frontend-stack.ts S3 + CloudFront + OAC + OIDC deploy role
scripts/deploy-web.sh   build SPA -> s3 sync -> CloudFront invalidation
.github/workflows/
  ci.yml                quality gates (lint / build / test / e2e)
  deploy.yml            caller of the org reusable cd-cdk.yaml (push to dev)

What the stack creates

Resource Purpose
S3 bucket seahaven-shoc-frontend-dev private origin (BLOCK_ALL, SSE, OAC-only reads)
CloudFront distribution HTTPS, gzip/br; default behavior → S3, /api/* → backend (HTTP origin)
CloudFront Function (viewer request) SPA routing: rewrites extensionless paths to /index.html (scoped to the S3 behavior, so it never touches /api)
IAM role githubdeploy-shoc-frontend-new-dev assumed by GitHub Actions via OIDC, scoped to repo:Sea-Haven-Industries/shoc-frontend-new:ref:refs/heads/dev

The whole cd-cdk.yaml job runs as that role, so it holds: sts:AssumeRole on cdk-hnb659fds-* (for cdk deploy), cloudformation:DescribeStacks (cd-cdk's pre-flight/health-check + output reads), read/write on the bucket (s3 sync), and cloudfront:CreateInvalidation (cache bust). The OIDC provider is a singleton account resource — the stack only imports it (created in step 2), so cdk destroy can't delete a resource shared by other roles.


One-time setup (run by a human with admin AWS creds)

1. Authenticate to the AWS account

aws configure            # or: aws sso login --profile <admin>
aws sts get-caller-identity     # confirm the right account + region (us-east-1)

2. Ensure the GitHub OIDC provider exists (once per account)

aws iam list-open-id-connect-providers
# If none ends in token.actions.githubusercontent.com, create it (thumbprint is
# no longer required — AWS validates GitHub against its own trust store):
aws iam create-open-id-connect-provider \
  --url https://token.actions.githubusercontent.com \
  --client-id-list sts.amazonaws.com

3. CDK bootstrap (once per account/region)

cd infra/cdk
npm ci
npx cdk bootstrap aws://<ACCOUNT_ID>/us-east-1

4. Confirm the backend API origin

The app calls its API same-origin at /api (VITE_API_URL=/api in .env.production), and CloudFront proxies /api/* to the backend over HTTP. The backend host defaults to console.seahavenind.com; override it if the dev API lives elsewhere:

# default is fine for dev; otherwise:
npx cdk deploy -c apiOriginDomain=<dev-api-host>

No mixed-content risk: the browser talks HTTPS to CloudFront, and CloudFront talks HTTP to the origin.

5. First deploy (locally, with admin creds)

The deploy role doesn't exist until the first cdk deploy, so bootstrap it locally. This provisions infra + the role:

cd infra/cdk
npx cdk deploy

Note the DeployRoleArn output. Then push the first content (or just push to dev and let CI do everything from here on):

# from repo root, optional manual first content publish:
STACK_NAME=shoc-frontend-dev AWS_REGION=us-east-1 bash scripts/deploy-web.sh

6. Set the one GitHub secret

cd-cdk.yaml takes the role ARN as a secret (not a variable):

REPO=Sea-Haven-Industries/shoc-frontend-new
gh secret set AWS_DEPLOY_ROLE_ARN --repo "$REPO" \
  --body "arn:aws:iam::<acct>:role/githubdeploy-shoc-frontend-new-dev"

(Or Settings → Secrets and variables → Actions → Secrets.)

7. From now on: push to dev

git push origin dev

ci.yml runs the quality gates and deploy.yml calls cd-cdk.yaml, which runs cdk deploy then scripts/deploy-web.sh. Watch the Actions tab, then open the SiteUrl output.

First-run verification: this first push is what actually exercises the role's permissions and the OIDC trust through the reusable workflow (the local bootstrap used admin creds and tested none of that). Watch for credential/OIDC errors and a green post-deploy step.


Adding staging / prod later

Separate accounts: deploy this stack there with -c envName=staging, -c deployBranch=<branch>, and -c apiOriginDomain=<that-env's-api-host>; set that repo's AWS_DEPLOY_ROLE_ARN secret; and add a job to deploy.yml. Because the SPA calls /api same-origin, the API host is per-environment CloudFront config (the apiOriginDomain context) — the build output is identical across environments, so nothing env-specific gets baked into vite build.

Notes

  • Teardown: npx cdk destroy. The bucket uses RemovalPolicy.DESTROY + autoDeleteObjects (dev artifacts are reproducible) — change this for prod.
  • CI and CD both fire on push to dev in parallel; a red-CI commit still deploys (matches the org's push-time-CD model). Gating deploy on CI is a follow-up, not part of enabling CICD.
  • Local npm is pinned to v6; the committed package-lock.json is lockfileVersion 1. CI (Node 24 / npm 11) reads it fine via npm ci.