Flight-booking app scaffolded from the source running on the GCP host, plus the four-screen frontend and three OWASP attack endpoints (SQLi, reflected XSS, path traversal) built and locally verified against a live uvicorn/vite dev pair. Includes deploy.sh (local build -> ship over ssh -> swap on host) and docs/ carried over from the handoff.
88 lines
4.3 KiB
Markdown
88 lines
4.3 KiB
Markdown
# Architecture
|
|
|
|
## Overview
|
|
|
|
Single GCP `e2-micro` running three rootless Podman containers under user `matt`.
|
|
Imperva Cloud WAF sits in front of everything. The origin is network-isolated so that
|
|
the only things that can reach the app are Imperva's edge nodes.
|
|
|
|
```
|
|
Visitor ──TLS──▶ Imperva Cloud WAF ──TLS (re-encrypt)──▶ Caddy :443 ──HTTP──▶ backend/frontend
|
|
(terminates, (self-signed cert)
|
|
inspects,
|
|
re-encrypts)
|
|
```
|
|
|
|
## The two TLS legs (this is the crux of the design)
|
|
|
|
There are two independent TLS connections, and they use different certificates. Mixing
|
|
them up is the single easiest way to break this setup.
|
|
|
|
**Leg 1 — Visitor → Imperva (edge).**
|
|
Imperva presents a publicly-trusted certificate for `flytoouter.space` to browsers.
|
|
This is Imperva's *managed* cert (auto-provisioned by Imperva). It must be publicly
|
|
trusted or every visitor gets a browser warning. We do **not** touch this with our
|
|
self-signed cert.
|
|
|
|
**Leg 2 — Imperva → origin (re-encryption).**
|
|
Imperva opens a *second* TLS connection back to the GCP origin and inspects/re-encrypts
|
|
before forwarding. Caddy serves this leg on `:443` with a **self-signed certificate**.
|
|
Imperva is the only client that ever sees this cert, so self-signed is fine and
|
|
produces no visitor-facing warnings.
|
|
|
|
DNS for `flytoouter.space` must point at **Imperva**, not at `35.203.147.44`. If it
|
|
resolves straight to the origin, browsers try to negotiate TLS with the self-signed
|
|
origin cert and fail.
|
|
|
|
## Request flow through the origin
|
|
|
|
Caddy (`caddy-proxy`) is the only container bound to a public port. It terminates the
|
|
re-encryption TLS and reverse-proxies over plain HTTP to the app containers via the
|
|
`host.containers.internal` alias on the default rootless bridge network:
|
|
|
|
- `handle /api/*` → `host.containers.internal:8000` (FastAPI backend)
|
|
- `handle /*` → `host.containers.internal:8080` (Vue frontend)
|
|
|
|
`handle` (not `handle_path`) is used so the `/api` prefix is preserved for the backend
|
|
routes (`/api/v1/users`, etc.).
|
|
|
|
## Container stack
|
|
|
|
| Container | Image | Port | Role |
|
|
|----------------|------------------------------------|-------------|--------------------------------------------------|
|
|
| `caddy-proxy` | `docker.io/library/caddy:2-alpine` | `443:443` | TLS termination (re-encryption leg) + reverse proxy |
|
|
| `backend-api` | custom Python 3.11-slim (FastAPI) | `8000:8000` | REST API + local SQLite `demo.db` |
|
|
| `frontend-vue` | custom multi-stage (Node build → Nginx) | `8080:8080` | Vue SPA (flight booking UI) |
|
|
|
|
Caddy currently serves only `:443`. There is intentionally no `:80` site block (see
|
|
DECISIONS.md — a stray `:80` block under `auto_https off` caused the `ambiguous site
|
|
definition` failure). If Imperva needs an HTTP health check, an `:80` block can be
|
|
added back as a separate, non-conflicting listener.
|
|
|
|
## Network / edge security
|
|
|
|
- **Origin target tag:** `imperva-origin`.
|
|
- **`allow-imperva-waf`:** HTTP/HTTPS ingress restricted to Imperva's published IPv4
|
|
CIDR blocks (e.g. `199.83.128.0/21`, `198.143.32.0/19`, …). Confirm `tcp:443` is in
|
|
the allowed ports since the origin path is now 443-only.
|
|
- **`allow-home-ssh`:** SSH (22) locked to the operator's static residential IP.
|
|
- All default open (`0.0.0.0/0`) rules purged.
|
|
|
|
Because the origin only answers Imperva CIDRs, the deliberately-vulnerable endpoints
|
|
are unreachable directly. Every attack must pass through the WAF — which is exactly why
|
|
they register as blocked events in the portal.
|
|
|
|
## Host hardening (from original build)
|
|
|
|
- `/swapfile` mounted via `/etc/fstab` to mitigate OOM kills on the burstable e2-micro.
|
|
- `loginctl enable-linger matt` so rootless containers persist across reboots.
|
|
- `sysctl net.ipv4.ip_unprivileged_port_start=80` to let rootless Caddy bind low ports.
|
|
|
|
## Cert details (origin, Leg 2)
|
|
|
|
- Self-signed, RSA 2048, 3650-day validity.
|
|
- **SAN covers both** `DNS:flytoouter.space` and `IP:35.203.147.44`, so whichever way
|
|
Imperva addresses the backend, the cert matches.
|
|
- Files live on the host at `~/imperva-proxy/certs/{origin.crt,origin.key}` and are
|
|
bind-mounted read-only into Caddy at `/etc/caddy/certs/`.
|