# SUB-ALIGATOR A simple internal web application for tracking and automatically allocating IPv4 subnets. ## Purpose This application is intended solely for internal tracking and automatic allocation of IPv4 subnets. It is not a general IPAM, monitoring tool, device inventory, VLAN manager, or network topology viewer. It is designed to run only inside a LAN/VPN. **The application does not include authentication** and must be placed behind a firewall/reverse proxy in a trusted network. ## Features - Manage one or more IP pools manually through the web UI. - First-run setup wizard: create the first pool before allocating subnets. - Automatic subnet allocation using a **first-fit** algorithm for prefixes /20 through /30. - Atomic allocation writes using SQLite `BEGIN IMMEDIATE` transactions. - Retro TUI/terminal-style web UI, server-side rendered with Askama. - Dynamic preview of the next free subnet via HTMX. - Allocation history with filtering, search, and pagination. - Release subnets (status `released`) with a mandatory note, directly from the history table or detail page. - Released subnets free up address space and can be reused by the first-fit algorithm. - Delete pools without losing their allocations: the subnets stay in history and keep blocking the address range if the same or an overlapping pool is added again. - Light and dark (amber terminal) theme, switchable in the page header and remembered per browser. - JSON REST API (`/api/v1/`) with Bearer token authentication; tokens are managed in the web UI. - Audit log of all operations. - Markdown, CSV, and JSON exports. - SQLite migrations run automatically on startup. - Backup and restore scripts. ## Architecture - **Rust** edition 2024 - **Axum** + **Tokio** - **SQLx** with **SQLite** - **Askama** for HTML templates - **HTMX** from a local static file - Custom CSS without external frameworks - CIDR/IP calculations using `ipnet` ## Quick start ```bash cd sub-aligator cp .env.example .env docker compose up -d --build ``` Open a browser at: ``` http://SERVER_IP:8080 ``` On first launch you will be asked to create the first pool. The default port can be changed in `.env` via the `APP_PORT` variable. ## Persistent data Docker Compose creates local directories: - `./data` – SQLite database - `./exports` – generated exports (Markdown, CSV, JSON) - `./backups` – database backups These directories are bind-mounted into the container, so data persists even if the container is removed. ## Backup and restore ### Backup ```bash ./scripts/backup.sh ``` The script creates a consistent database backup using `sqlite3 .backup` with a UTC timestamp in the filename. Backups are stored in `./backups`. ### Restore ```bash ./scripts/restore.sh ./backups/subnet-allocator.db.20240930T120000Z.backup ``` **Warning:** restore overwrites the current database. Stop the container and back up the current state before restoring. ### Automating backups On the host you can use a systemd timer or cron. Example cron entry: ```cron 0 2 * * * /path/to/sub-aligator/scripts/backup.sh >> /var/log/subnet-allocator-backup.log 2>&1 ``` ## Exports After every successful allocation or release, read-only exports are regenerated: - `/app/exports/allocations.md` - `/app/exports/allocations.csv` - `/app/exports/allocations.json` They are also available for download via endpoints: - `GET /exports/allocations.md` - `GET /exports/allocations.csv` - `GET /exports/allocations.json` The Markdown export contains a separate section for each pool with a table of active and reserved allocations. CSV and JSON include all allocations, including released ones. ## REST API The application provides a JSON REST API under `/api/v1/` for integrations. All API requests are authenticated with a Bearer token: ``` Authorization: Bearer sa_... ``` ### Managing API tokens Tokens are managed in the web UI on the **API tokens** page (link in the footer): - **Create** – enter a descriptive name (e.g. `monitoring-integration`). The plaintext token is shown **only once** right after creation; only its SHA-256 hash is stored in the database. - **Revoke** – disables the token immediately. The record is kept for audit purposes. - **Delete** – permanently removes a revoked token. Every API action is recorded in the audit log with the actor `api:`. ### Endpoints | Method | Path | Description | |--------|------|-------------| | `GET` | `/api/v1/pools` | List enabled pools with allocation counts | | `GET` | `/api/v1/pools/{slug}/next?prefix_length=30` | Preview the next free subnet (informational only) | | `GET` | `/api/v1/allocations` | List allocations; filters: `status`, `pool`, `q`, `limit` (max 500), `offset` | | `GET` | `/api/v1/allocations/{id}` | Allocation detail including address range | | `POST` | `/api/v1/allocations` | Allocate the next free subnet | | `POST` | `/api/v1/allocations/{id}/release` | Release an allocation | The allocation is always recalculated inside a write transaction – the preview from `/next` may differ from the actually allocated subnet under concurrent use. ### Examples ```bash TOKEN="sa_your_token_here" BASE="http://SERVER_IP:8080" # List pools curl -s -H "Authorization: Bearer $TOKEN" $BASE/api/v1/pools # Preview the next free /30 in pool "vpn" curl -s -H "Authorization: Bearer $TOKEN" "$BASE/api/v1/pools/vpn/next?prefix_length=30" # Allocate the next free /30 (note is mandatory, min. 3 characters) curl -s -X POST -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"pool_slug":"vpn","prefix_length":30,"note":"customer X edge","ticket":"INC-123"}' \ $BASE/api/v1/allocations # List active allocations in pool "vpn" curl -s -H "Authorization: Bearer $TOKEN" "$BASE/api/v1/allocations?status=active&pool=vpn" # Release allocation 42 (release note is mandatory) curl -s -X POST -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"release_note":"no longer needed"}' \ $BASE/api/v1/allocations/42/release ``` Errors are returned as `{"error":"message"}` with an appropriate status code (`400` invalid request, `401` missing/invalid token, `404` not found, `409` pool full or allocation already released). ## Security notes - The web UI **has no authentication**. Use it only behind a VPN/LAN/firewall. - The REST API requires a Bearer token; only token hashes (SHA-256) are stored. - CSRF tokens are required for POST forms in the web UI. - Security headers are set: `X-Content-Type-Options`, `X-Frame-Options`, `Referrer-Policy`, `Content-Security-Policy`. - The application has no default Internet exposure; port binding is handled by Docker Compose. - `/healthz` returns only `{"status":"ok"}`. ## Adding authentication later via Caddy + Authentik The application is prepared for authentication through a trusted reverse proxy. The function `get_actor_from_request()` in `src/audit.rs` currently returns `local`, but can be updated to read a header such as `X-Authentik-Username`. Example Caddy configuration with Authentik: ```caddy subnet.example.com { reverse_proxy /outpost.goauthentik.io/* http://authentik:9000 forward_auth http://authentik:9000 { uri /outpost.goauthentik.io/auth/caddy copy_headers X-Authentik-Username X-Authentik-Groups X-Authentik-Email } reverse_proxy subnet-allocator:8080 } ``` After updating `get_actor_from_request()`, the audit log will record the real user. ## First-fit allocation example 1. First request `/30` receives `10.91.0.0/30`. 2. Next request `/24` receives `10.91.1.0/24` (avoids the occupied `/30`). 3. Another request `/30` fills the gap and receives `10.91.0.4/30`. The algorithm always respects CIDR alignment and finds the first free space large enough. ## Endpoints - `GET /` – main page - `GET /healthz` – healthcheck - `GET /setup` – first-run pool setup - `GET /pools` – pool management - `POST /pools/new` – create a new pool - `POST /pools/{id}/delete` – delete a pool (allocations are kept) - `GET /tokens` – API token management - `POST /tokens/new` – create an API token - `POST /tokens/{id}/revoke` – revoke an API token - `POST /tokens/{id}/delete` – delete a revoked API token - `GET /partials/pools/{slug}/preview?prefix_length=24` – HTMX preview - `POST /allocations` – create an allocation - `GET /allocations/{id}` – allocation detail - `POST /allocations/{id}/release` – release an allocation - `GET /exports/allocations.{md,csv,json}` – exports - `GET|POST /api/v1/...` – JSON REST API (see above) ## Troubleshooting ### Container does not start ```bash docker compose logs -f subnet-allocator ``` ### Database is locked If the process terminates unexpectedly, a `-wal` or `-shm` file may remain. Stop the container and check `./data`. ### Exports are not generated Check the permissions of the `./exports` directory and the `APP_EXPORTS_DIR` variable. ## Licence MIT. See the `LICENSE` file.