Initial commit: Imperva WAF demo lab

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.
This commit is contained in:
matt committed 2026-07-29 11:08:56 -07:00
commit 4d60fdd139
28 files changed
+2609

No files matched your search

+87
View File
@@ -0,0 +1,87 @@
# 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/`.