docs: comprehensive ARCHITECTURE, DEPLOYMENT, API, and THREAT_MODEL
ARCHITECTURE.md - Full component map with ASCII diagram - Complete session lifecycle (init + heartbeat + failure path) - Cryptographic protocol spec (hash chain formula, canonical JSON) - Stack machine instruction set table with stack effects - Behavioral validation thresholds - SQLite schema, threat model summary, module reference DEPLOYMENT.md - Build instructions (WASM + server + convenience script) - native binary, systemd (with hardened sandbox notes), Docker - nginx, Nginx Proxy Manager, and HAProxy reverse proxy configs - Integration options (sidecar vs proxy-only) - Full configuration table with all constants - Observability (RUST_LOG levels), health check, security checklist API.md - Full /init and /hb request/response schemas with field tables - Canonical signing payload specification - Complete validation rules table (all 13 rejection conditions) - Hash chain byte-level specification - WASM exported function reference THREAT_MODEL.md - Four attacker profiles (script kiddie → sophisticated adversary) - Eight attack vectors with mitigations (replay, forgery, hijack, DoS…) - Explicit out-of-scope limitations - Operational security notes (CORS, TLS, log level, SQLite)
This commit is contained in:
1 parent
851d3b4876
commit
4b27a342d1
4 files changed
+1017
-256
No files matched your search
+326
-16
@@ -1,36 +1,346 @@
|
||||
# Deployment
|
||||
# ChronoSeal — Deployment Guide
|
||||
|
||||
## Native
|
||||
## Prerequisites
|
||||
|
||||
| Tool | Minimum version | Purpose |
|
||||
|---|---|---|
|
||||
| Rust | 1.87 stable | Server + WASM compilation |
|
||||
| wasm-pack | 0.13 | WASM build and packaging |
|
||||
| Docker + Compose | 24 / 2.x | Container deployment |
|
||||
| nginx / NPM / HAProxy | any | TLS termination, reverse proxy |
|
||||
|
||||
Install Rust: https://rustup.rs
|
||||
Install wasm-pack: `cargo install wasm-pack`
|
||||
|
||||
---
|
||||
|
||||
## Build
|
||||
|
||||
### 1. Build the WASM module
|
||||
|
||||
```bash
|
||||
wasm-pack build wasm --target web --release
|
||||
mv wasm/pkg frontend/pkg
|
||||
```
|
||||
|
||||
This produces `frontend/pkg/antibot_wasm.js` and `frontend/pkg/antibot_wasm_bg.wasm`,
|
||||
which are loaded by `frontend/main.js` at runtime.
|
||||
|
||||
### 2. Build the server
|
||||
|
||||
```bash
|
||||
cargo build -p server --release
|
||||
```
|
||||
|
||||
Binary output: `target/release/server`
|
||||
|
||||
### 3. Build both (convenience script)
|
||||
|
||||
```bash
|
||||
bash scripts/build.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Running
|
||||
|
||||
### Development
|
||||
|
||||
```bash
|
||||
bash scripts/dev.sh
|
||||
```
|
||||
|
||||
Runs the server with `cargo run --release`. The server serves the `frontend/`
|
||||
directory statically at `/` via tower-http `ServeDir`.
|
||||
|
||||
Open `http://localhost:3000` in a browser. Open DevTools console — heartbeats
|
||||
should appear every 12–25 seconds. No visible UI is rendered; the protection
|
||||
is entirely silent.
|
||||
|
||||
### Production (native binary)
|
||||
|
||||
```bash
|
||||
cargo build -p server --release
|
||||
sudo cp target/release/server /usr/local/bin/chronoseal
|
||||
```
|
||||
|
||||
## systemd
|
||||
Set environment variables before running:
|
||||
|
||||
```bash
|
||||
sudo cp chronoseal.service /etc/systemd/system/
|
||||
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable chronoseal
|
||||
sudo systemctl start chronoseal
|
||||
export RUST_LOG=info # or warn for quieter output
|
||||
chronoseal
|
||||
```
|
||||
|
||||
The server binds to `0.0.0.0:3000` by default. Place behind a reverse proxy
|
||||
for TLS — do not expose port 3000 directly.
|
||||
|
||||
---
|
||||
|
||||
## systemd
|
||||
|
||||
### Service file
|
||||
|
||||
The provided `chronoseal.service` includes hardened systemd sandboxing:
|
||||
|
||||
```
|
||||
NoNewPrivileges=true
|
||||
PrivateTmp=true
|
||||
ProtectSystem=strict
|
||||
ProtectHome=true
|
||||
ProtectKernelTunables=true
|
||||
ProtectKernelModules=true
|
||||
ProtectControlGroups=true
|
||||
MemoryDenyWriteExecute=true
|
||||
RestrictRealtime=true
|
||||
RestrictSUIDSGID=true
|
||||
LockPersonality=true
|
||||
SystemCallArchitectures=native
|
||||
```
|
||||
|
||||
### Install
|
||||
|
||||
```bash
|
||||
# Create a dedicated system user
|
||||
sudo useradd --system --no-create-home --shell /usr/sbin/nologin chronoseal
|
||||
|
||||
# Install binary and frontend
|
||||
sudo cp target/release/server /usr/local/bin/chronoseal
|
||||
sudo mkdir -p /opt/chronoseal/frontend
|
||||
sudo cp -r frontend/ /opt/chronoseal/frontend/
|
||||
sudo chown -R chronoseal:chronoseal /opt/chronoseal
|
||||
|
||||
# Install and enable service
|
||||
sudo cp chronoseal.service /etc/systemd/system/
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now chronoseal
|
||||
```
|
||||
|
||||
### Verify
|
||||
|
||||
```bash
|
||||
sudo systemctl status chronoseal
|
||||
journalctl -u chronoseal -f
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Docker
|
||||
|
||||
### Build and run
|
||||
|
||||
```bash
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
### docker-compose.yml overview
|
||||
|
||||
```yaml
|
||||
services:
|
||||
chronoseal:
|
||||
build: .
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
RUST_LOG: info
|
||||
tmpfs:
|
||||
- /tmp
|
||||
```
|
||||
|
||||
The `tmpfs` mount ensures the in-memory SQLite database is never written to
|
||||
disk, even if Docker's storage driver were to flush the container filesystem.
|
||||
|
||||
### Dockerfile stages
|
||||
|
||||
The Dockerfile uses a two-stage build:
|
||||
|
||||
1. `rust:1.87-bookworm` — compiles the server binary
|
||||
2. `debian:bookworm-slim` — minimal runtime image with only `ca-certificates`
|
||||
|
||||
The WASM module and frontend must be built separately (wasm-pack requires a
|
||||
browser toolchain not present in the server image) and mounted or copied into
|
||||
the container at `/opt/chronoseal/frontend/`.
|
||||
|
||||
```bash
|
||||
# Build WASM first
|
||||
wasm-pack build wasm --target web --release
|
||||
mv wasm/pkg frontend/pkg
|
||||
|
||||
# Then build and run the container
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
Or mount the pre-built frontend as a volume:
|
||||
|
||||
```yaml
|
||||
volumes:
|
||||
- ./frontend:/opt/chronoseal/frontend:ro
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Reverse Proxy
|
||||
|
||||
Recommended:
|
||||
- nginx
|
||||
- Nginx Proxy Manager
|
||||
- HAProxy
|
||||
ChronoSeal must be served over HTTPS. The heartbeat payload contains a
|
||||
timestamp; if traffic is observable in plaintext, timing attacks become
|
||||
easier. TLS 1.3 is strongly recommended.
|
||||
|
||||
Enable:
|
||||
- HTTP/2
|
||||
- TLS 1.3
|
||||
- aggressive timeout policies
|
||||
### nginx
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name your.domain.com;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/your.domain.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/your.domain.com/privkey.pem;
|
||||
ssl_protocols TLSv1.3;
|
||||
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
|
||||
|
||||
# Tight timeouts — heartbeat interval is 12–25s
|
||||
proxy_read_timeout 35s;
|
||||
proxy_send_timeout 10s;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:3000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $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;
|
||||
}
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name your.domain.com;
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
```
|
||||
|
||||
### Nginx Proxy Manager
|
||||
|
||||
1. Add a new Proxy Host pointing to `http://chronoseal:3000`
|
||||
2. Enable SSL, Request Let's Encrypt certificate
|
||||
3. Enable HTTP/2, Force SSL
|
||||
4. Under Advanced, add:
|
||||
```
|
||||
proxy_read_timeout 35s;
|
||||
proxy_send_timeout 10s;
|
||||
```
|
||||
|
||||
### HAProxy
|
||||
|
||||
```haproxy
|
||||
frontend https_front
|
||||
bind *:443 ssl crt /etc/haproxy/certs/your.domain.pem alpn h2,http/1.1
|
||||
default_backend chronoseal_back
|
||||
|
||||
backend chronoseal_back
|
||||
server chronoseal 127.0.0.1:3000 check
|
||||
timeout connect 5s
|
||||
timeout server 35s
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Integration into an Existing Site
|
||||
|
||||
ChronoSeal is designed to run as a sidecar — its `/init` and `/hb` endpoints
|
||||
can be proxied from any existing web server. The frontend assets (`pkg/`) need
|
||||
to be served from the same origin as the protected page (or CORS must be
|
||||
configured).
|
||||
|
||||
### Option A — Serve everything from ChronoSeal
|
||||
|
||||
ChronoSeal serves `frontend/` statically. Put your protected HTML inside
|
||||
`frontend/` and let ChronoSeal serve it directly.
|
||||
|
||||
### Option B — Proxy only the API endpoints
|
||||
|
||||
Keep your existing server. Proxy `/init` and `/hb` to ChronoSeal, and serve
|
||||
the WASM and JS assets from your CDN or existing static file server.
|
||||
|
||||
```nginx
|
||||
# On your existing server:
|
||||
location ~ ^/(init|hb)$ {
|
||||
proxy_pass http://127.0.0.1:3000;
|
||||
}
|
||||
```
|
||||
|
||||
Add to your protected pages:
|
||||
|
||||
```html
|
||||
<script type="module" src="/pkg/antibot_wasm.js"></script>
|
||||
<script type="module" src="/main.js"></script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration
|
||||
|
||||
All parameters are in `shared/src/constants.rs`. Recompile after changes.
|
||||
|
||||
| Constant | Default | Notes |
|
||||
|---|---|---|
|
||||
| `SESSION_ID_LEN` | 32 bytes | 256-bit entropy — do not reduce |
|
||||
| `SALT_LEN` | 16 bytes | Per-heartbeat salt |
|
||||
| `HEARTBEAT_MIN_INTERVAL_MS` | 12 000 ms | Increase to reduce server load |
|
||||
| `HEARTBEAT_MAX_INTERVAL_MS` | 25 000 ms | Jitter upper bound |
|
||||
| `EXPIRATION_MINUTES` | 30 min | Session TTL after last heartbeat |
|
||||
| `RATE_LIMIT_COUNT` | 5 | Max heartbeats per window per session |
|
||||
| `RATE_LIMIT_WINDOW_SECS` | 10 s | Rate limit window |
|
||||
| `MAX_TIMESTAMP_DRIFT_MS` | 30 000 ms | Anti-replay window; account for NTP skew |
|
||||
| `MIN_MOUSE_TOTAL_DIST` | 10.0 px | Lower for low-activity pages |
|
||||
| `MAX_MOUSE_AVG_SPEED` | 2.0 px/ms | Raise if legitimate users are rejected |
|
||||
| `MIN_PAUSE_COUNT` | 1 | Minimum natural pause events |
|
||||
|
||||
---
|
||||
|
||||
## Observability
|
||||
|
||||
ChronoSeal uses `tracing` with `tracing-subscriber`. Log levels:
|
||||
|
||||
| Level | Events |
|
||||
|---|---|
|
||||
| `INFO` | Server start, request method + path + status |
|
||||
| `WARN` | Heartbeat validation failures (with session ID and reason) |
|
||||
| `DEBUG` | Rate limit hits |
|
||||
|
||||
```bash
|
||||
RUST_LOG=info chronoseal # production
|
||||
RUST_LOG=debug chronoseal # development
|
||||
RUST_LOG=warn chronoseal # minimal output
|
||||
```
|
||||
|
||||
Log format is plain text to stdout. Pipe to `journald`, `fluentd`, or any
|
||||
log aggregator via stdout capture.
|
||||
|
||||
---
|
||||
|
||||
## Health Check
|
||||
|
||||
The server has no dedicated `/health` endpoint. Use a TCP check on port 3000,
|
||||
or a lightweight HTTP check on `GET /` (which serves `index.html`).
|
||||
|
||||
```bash
|
||||
# Docker health check (add to docker-compose.yml if needed)
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-sf", "http://localhost:3000/"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security Checklist
|
||||
|
||||
- [ ] TLS 1.3 enabled, TLS 1.0/1.1 disabled
|
||||
- [ ] HTTP/2 enabled
|
||||
- [ ] Port 3000 not exposed to the public internet (only via reverse proxy)
|
||||
- [ ] `RUST_LOG=warn` or `info` in production (not `debug` — session IDs appear in logs)
|
||||
- [ ] systemd service running as `chronoseal` user with hardened sandbox
|
||||
- [ ] `MemoryDenyWriteExecute=true` in service file (prevents JIT in process)
|
||||
- [ ] CORS `CorsLayer::permissive()` replaced with origin-restricted policy for production
|
||||
- [ ] Frontend assets served over the same HTTPS origin as protected pages
|
||||
Reference in new issue
Block a user