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

8.8 KiB
Raw Permalink Blame History

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

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

./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

./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:

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

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:

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

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.