1
0

architecture.md 7.8 KB

Architecture

Audience: operators running IRDB, and engineers building replacement frontends. Read this first if you're new to the codebase.

System overview

IRDB is an IP reputation database — it ingests abuse reports about specific IP addresses, applies a decaying weighted score per category, and distributes tailored block lists to firewalls and proxies. Reporters (web servers, IDS, fail2ban-style agents) push events; each consumer (firewall, proxy) pulls a block list shaped by a named policy that decides which categories and thresholds count.

The system is shipped as a small Docker Compose stack. Reporters and consumers talk JSON to the api over an authenticated REST surface; human operators use a thin PHP UI that calls the same api with a service token plus an impersonation header. The split is deliberate: the UI is replaceable, the api is the contract.

Container topology

flowchart LR
    subgraph clients[Clients]
        rep[Reporters<br/>web/IDS/fail2ban]
        con[Consumers<br/>firewalls/proxies]
        adm[Admins<br/>browser]
    end

    subgraph stack[Compose stack]
        ui[ui<br/>PHP+Twig BFF<br/>:8080]
        api[api<br/>Slim+FrankenPHP<br/>:8081]
        migrate[migrate<br/>one-shot Phinx]
        sched[scheduler<br/>busybox crond]
        mysql[(mysql<br/>optional)]
    end

    db[(SQLite or MySQL)]

    rep -- POST /api/v1/report ----> api
    con -- GET /api/v1/blocklist --> api
    adm -- HTTPS --> ui
    ui -- service token + X-Acting-User-Id --> api
    sched -- POST /internal/jobs/tick --> api
    migrate --> db
    api --> db
    mysql -.-> db

Five services in compose; only two run continuously (api + ui). migrate is one-shot (restart: "no"); scheduler is opt-in via the compose.scheduler.yml overlay; mysql is opt-in via uncommented config. Single-host SQLite deployments run with just migrate + api

  • ui; the data volume is shared between migrate and api.

Where state lives

Where What
irdb-data Docker volume SQLite database file /data/irdb.sqlite, GeoIP MMDBs at /data/geoip/. Owned exclusively by the api container; migrate mounts it briefly to apply schema changes.
mysql-data Docker volume Optional MySQL /var/lib/mysql. Replaces the SQLite path when DB_DRIVER=mysql.
ui container's writable layer PHP file-backed sessions (/tmp per the FrankenPHP base). Tied to a single replica; sticky sessions required if scaling UI horizontally.
.env All secrets (service token, internal job token, OIDC client secret, MySQL password, local-admin Argon2id hash).
Browser localStorage Light/dark theme preference only. Session is in a Secure cookie.

Notably absent: the ui container has zero persistent state. Replace its image and nothing of operational importance is lost. The api owns everything.

Stable surfaces vs replaceable parts

The contract for future frontends, integrators, and replacement UIs. Anything in the "stable" column should not break across v1 minor releases.

Surface Stability Notes
/api/v1/* paths and request/response shapes stable Additive changes only within v1. Breaking changes ship as v2. OpenAPI is canonical.
Token kinds (reporter, consumer, admin, service) stable Strings are persisted in api_tokens.kind and used in audit logs.
RBAC roles (viewer, operator, admin) stable Persisted in users.role and api_tokens.role.
users shape and oidc_role_mappings semantics stable Future SPAs/native UIs upsert via /api/v1/auth/users/upsert-oidc the same way the BFF does today.
Service-token + X-Acting-User-Id impersonation stable The pattern any BFF replacement uses; the API doesn't trust the header without a service token.
Error envelope {error, details?} stable Validation errors include details; auth failures don't.
Audit actor_kind enum stable user, admin-token, reporter, consumer, system. Used by external SIEM exporters in future work.
Twig template names under ui/resources/views/ replaceable Tied to the current UI implementation; replacement UIs render however they like.
UI route paths under /app/* replaceable Browser-only; not part of any contract.
Internal class names, method signatures, file layout replaceable Refactoring is fair game.
/internal/jobs/* replaceable Scheduler-only; no public guarantee. Not in OpenAPI.
Twig globals, htmx attributes, Alpine components replaceable Implementation details of this UI.

Why this split

Backend-for-Frontend (BFF) pattern. The api is a pure JSON service: no HTML, no sessions, no cookies. The ui is a thin browser-facing process that owns OIDC handshakes, browser sessions, CSRF, and Twig rendering — but no business logic, no scoring, no database. Every admin page on the UI fetches from the api with the service token plus the acting user's id.

Why this matters:

  1. The UI is replaceable. A team that wants Vue, Svelte, or a Tauri desktop app can replace the ui container without touching the api. The contract is the OpenAPI document and the impersonation pattern. See frontend-development.md.

  2. No HTML in the api. Other clients (firewalls, fail2ban, monitoring) call the same admin endpoints the UI does. If the api started rendering Twig, those integrations would suddenly carry HTML they don't want.

  3. Auditing is honest. The api records the impersonated user as the actor (actor_kind=user) when called via the BFF, never the service token. An admin token used directly records as actor_kind=admin-token. Future external authentication paths can plug in alongside without breaking the audit trail.

  4. The api can scale independently. With MySQL, the api is stateless and can run as N replicas behind a load balancer; the job_locks table mediates between them. The UI is typically single-replica because of file-backed sessions.

The trade-off: every UI request makes one or more outbound calls to the api, which costs latency and an HTTP hop. In practice the containers run on the same Docker network and the per-request cost is sub-millisecond.

Where the rest of the docs live

  • api-overview.md — the public API surface, with worked examples.
  • auth-flows.md — every authentication flow (machine, BFF, OIDC, local admin), Entra setup, future user-token sketch.
  • frontend-development.md — the headline doc for replacement UIs.
  • api-reference.md — short. Pointer to OpenAPI plus the things OpenAPI doesn't cleanly express (rate limits, ETag semantics, impersonation header, response conventions).

The OpenAPI document itself is at api/public/openapi.yaml and served at /api/v1/openapi.yaml with a viewer at /api/docs.