diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index af3456c..d49023c 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -16,6 +16,10 @@ ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0 RUN echo "node ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/node \ && chmod 0440 /etc/sudoers.d/node +# Allow node user to access Docker socket (gid 999 is the typical docker group gid) +RUN groupadd -g 999 docker 2>/dev/null || true \ + && usermod -aG docker node + USER node RUN mkdir -p /home/node/.local/share/pnpm/store diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index e03ca1a..12ab73d 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -9,6 +9,7 @@ services: - ..:/workspace:cached - openarchflow-pnpm-store:/home/node/.local/share/pnpm/store - ${HOME}/.aws:/home/node/.aws:ro + - /var/run/docker.sock:/var/run/docker.sock environment: MINISTACK_ENDPOINT: http://ministack:4566 command: sleep infinity diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cccc71..71cfb7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -237,7 +237,7 @@ OpenArchFlow can now deploy your AWS architecture diagrams to a local AWS emulat **Browser-Direct Architecture** - All AWS SDK v3 calls go directly from the browser to `localhost:4566` — no Next.js API route proxy required. This means the panel works even when the app is hosted on Vercel (the user's browser calls their local MiniStack directly). -- MiniStack CORS support enables this without any extra configuration. +- When the app's origin differs from `localhost` (e.g., `app.openarchflow.cloud`), use the bundled CORS reverse-proxy (`docker-compose.ministack.yml` on port 4567) to add the missing CORS headers that MiniStack itself does not provide. ### Technical diff --git a/README.md b/README.md index f99caed..ac9086a 100644 --- a/README.md +++ b/README.md @@ -90,13 +90,34 @@ Scan a live AWS account and turn its running resources into a diagram in seconds Deploy your architecture diagram to a local AWS emulator ([MiniStack](https://ministack.dev)) running on `localhost:4566` — turning OpenArchFlow into a full **design → deploy → operate** platform with no cloud costs and no AWS account required. +#### Why a CORS proxy? + +When you access the **hosted app** at [app.openarchflow.cloud](https://app.openarchflow.cloud) from your browser and connect to MiniStack running on your local machine (`localhost:4566`), the browser enforces CORS (Cross-Origin Resource Sharing) policy. Your browser at `https://app.openarchflow.cloud` cannot directly fetch from `http://localhost:4566` without CORS headers. MiniStack doesn't provide global CORS configuration, so we use a lightweight nginx reverse-proxy to add these headers transparently. + +**You don't need the proxy if:** +- You run OpenArchFlow locally too (`localhost:3000`) — same origin, no CORS issue +- You're willing to disable CORS in your browser (insecure, not recommended) + **Getting started:** +**Option 1: OpenArchFlow running locally** (`localhost:3000`): ```bash docker run -p 4566:4566 ministackorg/ministack ``` +Then open `localhost:3000`, configure MiniStack endpoint as `http://localhost:4566`, and deploy. + +**Option 2: Using the hosted app** ([app.openarchflow.cloud](https://app.openarchflow.cloud)) with local MiniStack: +```bash +docker compose -f docker-compose.ministack.yml up -d +``` +This starts MiniStack **and** a local nginx sidecar that adds CORS headers, listening on `http://localhost:4567`. Then: +1. Open [app.openarchflow.cloud](https://app.openarchflow.cloud) +2. Click the **Rocket** icon → **Configure MiniStack** +3. Set **Endpoint URL** to `http://localhost:4567` (note: port `4567`, not `4566`) +4. Click **Test Connection** → should see "Connected" +5. Click **Deploy All** -Then click the **Rocket** icon in the toolbar, configure the endpoint, and hit **Deploy All**. +The nginx proxy (port 4567) forwards all requests to MiniStack (port 4566) and adds `Access-Control-Allow-Origin: *` headers, enabling browser access from any origin. **Supported AWS services:** @@ -122,9 +143,27 @@ Then click the **Rocket** icon in the toolbar, configure the endpoint, and hit * - **Per-node console** — click any deployed node to open its interactive console (S3 browser, Lambda invoker, SQS message reader, etc.). - **Simulation hybrid mode** — when a node is deployed, simulation traffic is routed to the real MiniStack resource and uses wall-clock latency instead of synthetic values. - **Traffic Source node** — generate configurable req/s traffic from the diagram canvas; live response icon shows last result. -- **Browser-direct** — all AWS SDK v3 calls go from your browser directly to `localhost:4566`. Works even when OpenArchFlow is hosted on Vercel. +- **Browser-direct** — all AWS SDK v3 calls go from your browser directly to `localhost:4566`. Works even when OpenArchFlow is hosted on Vercel (with the CORS proxy for remote access). - **Teardown** — delete all deployed resources in one action. +#### Troubleshooting: CORS errors when using the hosted app + +**Error:** `Access to fetch at 'http://localhost:4566/...' from origin 'https://app.openarchflow.cloud' has been blocked by CORS policy` + +**Solution:** Use the CORS proxy. MiniStack doesn't provide global CORS configuration, so the bundled nginx reverse-proxy adds the required headers transparently: + +```bash +# Start MiniStack with the CORS proxy sidecar +docker compose -f docker-compose.ministack.yml up -d + +# In the app, configure MiniStack endpoint as: http://localhost:4567 +# (note port 4567, not 4566) +``` + +The proxy (port 4567) forwards all requests to MiniStack (port 4566) and adds `Access-Control-Allow-Origin: *` headers. This only affects browser access; command-line tools (aws-cli, Terraform) can still talk directly to port 4566. + +**Why is this needed?** When you access the hosted app (`app.openarchflow.cloud`) from your browser, the browser enforces CORS policy. Your browser can reach `localhost:4566` on your machine, but only if the response includes appropriate CORS headers. The proxy adds these headers, enabling secure cross-origin access. + ### 🏗️ Terraform IaC Generation (NEW) - **HCL Code Generation**: Export your AWS architecture diagram directly as production-ready HashiCorp Terraform — `main.tf`, `variables.tf`, and `outputs.tf` generated from your nodes and edges. @@ -282,9 +321,16 @@ pnpm start ### 6. Deploy to Local AWS (MiniStack) -1. Start MiniStack: `docker run -p 4566:4566 -v /var/run/docker.sock:/var/run/docker.sock -e GLUE_DOCKER_IMAGE=ghcr.io/dmux/openarchflow/ministack_glue_libs_4.0.0_image_01:latest -e S3_PERSIST=1 ministackorg/ministack:full` -2. Click the **🚀 Rocket** icon in the toolbar -3. Click **Test Connection** — you should see "Connected" +1. Start MiniStack (see **MiniStack Local Deploy** section above for setup options) + - Basic: `docker run -p 4566:4566 ministackorg/ministack` + - With Glue support: `docker run -p 4566:4566 -v /var/run/docker.sock:/var/run/docker.sock -e GLUE_DOCKER_IMAGE=ghcr.io/dmux/openarchflow/ministack_glue_libs_4.0.0_image_01:latest -e S3_PERSIST=1 ministackorg/ministack:full` + - With CORS proxy (hosted app): `docker compose -f docker-compose.ministack.yml up -d` +2. In the app, click the **🚀 Rocket** icon in the toolbar +3. In the MiniStack Connection dialog: + - Set **Endpoint URL** to: + - `http://localhost:4566` if running OpenArchFlow locally + - `http://localhost:4567` if using the hosted app (with the CORS proxy) + - Click **Test Connection** to verify 4. Click **Deploy All** — nodes deploy in sequence with live status badges 5. Click any deployed node → **Open Console** to interact with the resource 6. Run a simulation — deployed nodes receive real traffic from the simulation engine diff --git a/docker-compose.ministack.yml b/docker-compose.ministack.yml new file mode 100644 index 0000000..cd8b734 --- /dev/null +++ b/docker-compose.ministack.yml @@ -0,0 +1,17 @@ +version: "3.8" + +services: + ministack: + image: ministackorg/ministack + ports: + - "4566:4566" + restart: unless-stopped + + ministack-cors-proxy: + build: + context: ./docker/ministack-cors-proxy + ports: + - "4567:4566" + depends_on: + - ministack + restart: unless-stopped diff --git a/docker/ministack-cors-proxy/Dockerfile b/docker/ministack-cors-proxy/Dockerfile new file mode 100644 index 0000000..aa9df54 --- /dev/null +++ b/docker/ministack-cors-proxy/Dockerfile @@ -0,0 +1,5 @@ +FROM nginx:1.27-alpine + +COPY nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 4566 diff --git a/docker/ministack-cors-proxy/nginx.conf b/docker/ministack-cors-proxy/nginx.conf new file mode 100644 index 0000000..79bb69e --- /dev/null +++ b/docker/ministack-cors-proxy/nginx.conf @@ -0,0 +1,73 @@ +# CORS-adding reverse proxy in front of MiniStack. +# MiniStack (ministackorg/ministack) does not expose a way to configure global +# CORS headers, so this proxy adds them to every response and short-circuits +# CORS preflight (OPTIONS) requests before they ever reach MiniStack. + +resolver 127.0.0.11 valid=10s; # Docker embedded DNS — re-resolve "ministack" + # at runtime instead of caching its IP forever, + # so a `docker compose restart ministack` doesn't + # require restarting this proxy too. + +server { + listen 4566; + server_name _; + + # --- Local-dev-friendly limits ----------------------------------------- + client_max_body_size 0; # unlimited: S3 PUTs and Lambda .zip uploads + client_body_timeout 300s; + proxy_connect_timeout 10s; + proxy_send_timeout 300s; + proxy_read_timeout 300s; # generous for Lambda cold starts / CWL polling + + # --- Stream bodies instead of buffering to disk ------------------------- + # Important for large S3 objects / Lambda zip uploads: buffering the whole + # request or response would add latency and a disk-size limit. + proxy_http_version 1.1; + proxy_request_buffering off; + proxy_buffering off; + + location / { + # --- CORS preflight short-circuit ----------------------------------- + # AWS SigV4 requests set Authorization / X-Amz-Date / X-Amz-Content-Sha256 + # / X-Amz-Security-Token / Content-Type headers. These are "non-simple" + # per the Fetch spec, so the browser sends an OPTIONS preflight first. + # MiniStack never needs to see these — answer them here directly. + if ($request_method = OPTIONS) { + add_header Access-Control-Allow-Origin "*" always; + add_header Access-Control-Allow-Methods "GET,POST,PUT,PATCH,DELETE,HEAD,OPTIONS" always; + # Reflect whatever headers the SDK says it's about to send, rather + # than hard-coding an exhaustive SigV4 header allowlist. + add_header Access-Control-Allow-Headers "$http_access_control_request_headers" always; + add_header Access-Control-Max-Age 600 always; + return 204; + } + + # --- Proxy the real request to MiniStack ---------------------------- + set $upstream http://ministack:4566; + proxy_pass $upstream; + + # Preserve the Host header EXACTLY as the browser/SDK sent it. + # The AWS SigV4 signature includes "host" as a signed header. MiniStack + # (like LocalStack) recomputes the signature against whatever Host + # header it actually receives, so it must see the same host:port the + # SDK signed (e.g. "localhost:4567") — not the upstream's internal + # name. nginx's default proxy_pass behavior would otherwise send + # "ministack:4566" here and break signature verification. + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Connection ""; + proxy_pass_request_headers on; + + # --- CORS headers on the real response ------------------------------- + # `always` also applies these on 4xx/5xx responses from MiniStack, so + # SDK error-handling code sees the real error instead of a generic + # CORS network failure masking it. + add_header Access-Control-Allow-Origin "*" always; + # Expose AWS-specific response headers (ETag, request IDs, checksums) + # that aren't on the browser's default CORS-safelisted header list — + # some AWS SDK v3 middleware reads these from the raw fetch Response. + add_header Access-Control-Expose-Headers "*" always; + } +} diff --git a/src/components/ministack/MiniStackConfigDialog.tsx b/src/components/ministack/MiniStackConfigDialog.tsx index 671953b..ad54aa9 100644 --- a/src/components/ministack/MiniStackConfigDialog.tsx +++ b/src/components/ministack/MiniStackConfigDialog.tsx @@ -64,7 +64,10 @@ export function MiniStackConfigDialog({ open, onClose }: MiniStackConfigDialogPr Configure the local AWS emulator endpoint. MiniStack must be running - at the specified address. + at the specified address. Seeing a CORS/network error on Test + Connection when using the hosted app? See the "MiniStack Local + Deploy" section in the README for the CORS proxy setup (use port + 4567 instead of 4566). diff --git a/src/lib/ministack/browser-actions.ts b/src/lib/ministack/browser-actions.ts index a0817b9..9d4c583 100644 --- a/src/lib/ministack/browser-actions.ts +++ b/src/lib/ministack/browser-actions.ts @@ -1,5 +1,6 @@ // All AWS SDK v3 operations — browser-compatible (no Node.js Buffer/fs/stream APIs). -// MiniStack supports CORS; import freely from any "use client" component. +// When the app's origin differs from localhost (e.g., app.openarchflow.cloud), +// use the bundled CORS proxy (docker-compose.ministack.yml) on port 4567. import { getS3Client, getSQSClient, getDynamoDBClient, getLambdaClient,