diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index b4fcf5c..adde00c 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -87,7 +87,7 @@ The backend exposes 25 route groups under the `/api` prefix: ### Go Poller -- **Stack**: Go 1.24, go-routeros/v3, pgx/v5, nats.go +- **Stack**: Go 1.25, go-routeros/v3, pgx/v5, nats.go - **Polling model**: Synchronous per-device polling on a configurable interval (default 60s) - **Device communication**: RouterOS binary API over TLS (port 8729), InsecureSkipVerify for self-signed certs - **TLS fallback**: Three-tier strategy -- CA-verified -> InsecureSkipVerify -> plain API @@ -106,7 +106,7 @@ The backend exposes 25 route groups under the `/api` prefix: ### PostgreSQL 17 + TimescaleDB - **Image**: `timescale/timescaledb:2.17.2-pg17` -- **Row-Level Security (RLS)**: Enforces tenant isolation at the database level. All data tables have a `tenant_id` column; RLS policies filter by `current_setting('app.tenant_id')` +- **Row-Level Security (RLS)**: Enforces tenant isolation at the database level. All data tables have a `tenant_id` column; RLS policies filter by `current_setting('app.current_tenant')` - **Database roles**: - `postgres` (superuser) -- admin engine, auth/bootstrap, migrations - `app_user` (non-superuser) -- RLS-enforced, used by API for data routes @@ -140,11 +140,11 @@ The backend exposes 25 route groups under the `/api` prefix: ### OpenBao (HashiCorp Vault fork) - **Image**: `openbao/openbao:2.1` -- **Mode**: Dev server (auto-unsealed, in-memory storage) +- **Mode**: Persistent server with file storage backend (`/openbao/data`), mounted to the `openbao_data` Docker volume. Data survives container restarts. - **Transit secrets engine**: Provides envelope encryption for device credentials at rest - **Per-tenant keys**: Each tenant gets a dedicated Transit encryption key - **Init script**: `infrastructure/openbao/init.sh` enables Transit engine and creates initial keys -- **Dev token**: `dev-openbao-token` (must be replaced in production) +- **Token**: Set `OPENBAO_TOKEN` in `.env.prod`. The application rejects known-insecure defaults in production. - **Memory limit**: 256MB ### WireGuard @@ -239,8 +239,8 @@ Browser API PostgreSQL ## Multi-Tenancy Model - Every data table includes a `tenant_id` column -- PostgreSQL RLS policies filter rows by `current_setting('app.tenant_id')` -- The API sets tenant context (`SET app.tenant_id = ...`) on each database session +- PostgreSQL RLS policies filter rows by `current_setting('app.current_tenant')` +- The API sets tenant context (`SET app.current_tenant = ...`) on each database session - `super_admin` role has NULL `tenant_id` and can access all tenants - `poller_user` bypasses RLS intentionally (needs cross-tenant device access for polling) - Tenant isolation is enforced at the database level, not the application level -- even a compromised API cannot leak cross-tenant data through `app_user` connections @@ -315,7 +315,7 @@ docker-compose.override.yml Application services for dev (api, poller, frontend) docker compose up -d # Full stack including application services (api, poller, frontend) -docker compose up -d # override.yml is auto-loaded in dev +docker compose --profile full up -d # Build images sequentially to avoid OOM on low-RAM machines docker compose build api diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index 8ec5e97..cc29b42 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -1,6 +1,6 @@ # Configuration Reference -TOD uses Pydantic Settings for configuration. All values can be set via environment variables or a `.env` file in the backend working directory. +TOD uses Pydantic Settings for configuration. All values can be set via environment variables or an env file. In Docker Compose deployments, environment variables are loaded from `.env.prod` in the project root via `--env-file`. For local development without Docker, the backend also reads `backend/.env`. ## Environment Variables @@ -50,6 +50,8 @@ TOD uses Pydantic Settings for configuration. All values can be set via environm | `OPENBAO_ADDR` | `http://localhost:8200` | OpenBao Transit server address for per-tenant envelope encryption | | `OPENBAO_TOKEN` | *(insecure dev default)* | OpenBao authentication token. **Must be changed in production.** | +OpenBao is the key management service used to encrypt device credentials on a per-tenant basis. In Docker deployments, it runs as a container alongside the other services. + ### NATS | Variable | Default | Description | @@ -70,7 +72,7 @@ TOD uses Pydantic Settings for configuration. All values can be set via environm | `SMTP_PORT` | `587` | SMTP server port | | `SMTP_USER` | *(none)* | SMTP authentication username | | `SMTP_PASSWORD` | *(none)* | SMTP authentication password | -| `SMTP_USE_TLS` | `false` | Enable STARTTLS for SMTP connections | +| `SMTP_USE_TLS` | `false` | Enable STARTTLS for SMTP connections. If using port 587 (STARTTLS), set `SMTP_USE_TLS=true`. | | `SMTP_FROM_ADDRESS` | `noreply@the-other-dude.local` | Sender address for outbound emails | ### Firmware @@ -105,7 +107,7 @@ TOD uses Pydantic Settings for configuration. All values can be set via environm | Variable | Default | Description | |----------|---------|-------------| | `FIRST_ADMIN_EMAIL` | *(none)* | Email for the initial super_admin user. Only used if no users exist in the database. | -| `FIRST_ADMIN_PASSWORD` | *(none)* | Password for the initial super_admin user. The user is created with `must_upgrade_auth=True`, triggering SRP registration on first login. | +| `FIRST_ADMIN_PASSWORD` | *(none)* | Password for the initial super_admin user. On first login, you will be guided through a one-time security enrollment to set up zero-knowledge credentials. | ## Production Safety diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index 8c1839e..71c78bf 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -25,7 +25,7 @@ TOD (The Other Dude) is a containerized fleet management platform for RouterOS d ### 1. Clone and Configure ```bash -git clone tod +git clone https://github.com/staack/the-other-dude.git tod cd tod # Copy environment template @@ -84,7 +84,7 @@ curl http://localhost:8000/health curl http://localhost:8000/health/ready # Access the portal -open http://localhost +# Open http://localhost in a web browser ``` Log in with the `FIRST_ADMIN_EMAIL` and `FIRST_ADMIN_PASSWORD` credentials set in step 2. @@ -186,7 +186,7 @@ docker compose \ ``` - **Prometheus**: `http://localhost:9090` -- **Grafana**: `http://localhost:3001` (default: admin/admin) +- **Grafana**: `http://localhost:3001` (default: admin/admin — change the default password immediately on any networked host) ### Exported Metrics @@ -213,6 +213,9 @@ The API and poller export Prometheus metrics: ### Updating ```bash +# Back up the database before upgrading +docker compose exec postgres pg_dump -U postgres mikrotik > backup-$(date +%Y%m%d).sql + git pull docker compose -f docker-compose.yml -f docker-compose.prod.yml build api docker compose -f docker-compose.yml -f docker-compose.prod.yml build poller diff --git a/docs/SECURITY.md b/docs/SECURITY.md index 721ca9f..4d62697 100644 --- a/docs/SECURITY.md +++ b/docs/SECURITY.md @@ -41,7 +41,7 @@ Client Server Device credentials (RouterOS usernames and passwords) are encrypted at rest using envelope encryption: -- **Encryption algorithm:** AES-256-GCM (via Fernet symmetric encryption). +- **Encryption algorithm:** Fernet symmetric encryption (AES-128-CBC + HMAC-SHA256). - **Key management:** OpenBao Transit secrets engine provides the master encryption keys. - **Per-tenant isolation:** Each tenant has its own encryption key in OpenBao Transit. - **Envelope encryption:** Data is encrypted with a data encryption key (DEK), which is itself encrypted by the tenant's Transit key. @@ -87,7 +87,7 @@ TOD includes a per-tenant Internal Certificate Authority for managing TLS certif ## Remote Access Security -TOD v9.5 adds on-demand WinBox tunnels and browser-based SSH terminals for devices behind NAT. +TOD includes on-demand WinBox tunnels and browser-based SSH terminals for devices behind NAT. - **Single-use session tokens:** SSH sessions are initiated with a short-lived token stored in Redis (`GETDEL`, 120-second TTL). The token is consumed on first use and cannot be replayed. - **RBAC enforcement:** Opening a tunnel or starting an SSH session requires the `operator` role or higher. `viewer` accounts have no access to remote access features. @@ -97,10 +97,10 @@ TOD v9.5 adds on-demand WinBox tunnels and browser-based SSH terminals for devic ## Network Security -- **RouterOS communication:** All device communication uses the RouterOS binary API over TLS (port 8729). InsecureSkipVerify is enabled by default because RouterOS devices typically use self-signed certificates. +- **RouterOS communication:** All device communication uses the RouterOS binary API over TLS (port 8729). InsecureSkipVerify is enabled by default because RouterOS devices typically use self-signed certificates. To eliminate this risk, use the Internal Certificate Authority feature to issue verified TLS certificates to your devices. - **CORS enforcement:** Strict CORS policy in production, configured via `CORS_ORIGINS` environment variable. - **Rate limiting:** Authentication endpoints are rate-limited to 5 requests per minute per IP to prevent brute-force attacks. -- **Cookie security:** httpOnly cookies prevent JavaScript access to session tokens. The `Secure` flag is auto-detected based on whether CORS origins use HTTPS. +- **Cookie security:** httpOnly cookies prevent JavaScript access to session tokens. The `Secure` flag is auto-detected based on whether CORS origins use HTTPS. If you switch from HTTP to HTTPS, existing sessions will be invalidated — users will need to log in again. ## Data Protection diff --git a/docs/website/docs/manage-multiple-mikrotik-routers.html b/docs/website/docs/manage-multiple-mikrotik-routers.html index 06593b1..ec7d9d9 100644 --- a/docs/website/docs/manage-multiple-mikrotik-routers.html +++ b/docs/website/docs/manage-multiple-mikrotik-routers.html @@ -165,7 +165,7 @@

For teams evaluating self-hosted options, the stack is straightforward: