sub-aligator/README.md
SW 8c69c1731d feat: JSON REST API /api/v1 with DB-managed Bearer tokens
- New endpoints: GET /api/v1/pools, GET /api/v1/pools/{slug}/next,
  GET/POST /api/v1/allocations, GET /api/v1/allocations/{id},
  POST /api/v1/allocations/{id}/release
- Bearer token auth; only SHA-256 hashes stored, plaintext shown once
- Token management GUI at /tokens (create, revoke, delete) linked in footer
- API actions audited with actor api:<token name>
- Fix latent transaction leak: replace manual BEGIN IMMEDIATE/COMMIT with
  begin_with(), which rolls back on drop; previously a failed allocation
  (e.g. disallowed prefix or full pool) returned a connection with an open
  transaction to the pool, causing 'database is locked' on later writes
- README: API documentation with curl examples
2026-10-09 14:25:17 +02:00

242 lines
8.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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:<token name>`.
### 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.