Concepts

Domain model

Tiyi's domain is small on purpose. Seven first-class resources cover every operator action, every CLI command, and every UI page. Once these click, the rest of the product is obvious.

The seven resources

Site
An HTTP host Tiyi proxies. Carries TLS settings, an upstream pool reference, and an attached WAF policy. The smallest unit you enable or disable.
Upstream pool
A set of backends behind a site. Round-robin or weighted load balancing, SNI-aware health checks, and reuse across multiple sites.
Certificate
A TLS keypair. Either uploaded (self-signed, CA-bought, enterprise-signed) or ACME-issued (HTTP-01 or DNS-01). Encrypted at rest with the KEK.
WAF policy
The compiled artifact Coraza enforces per request. CRS Core layer + HTTP policy + limits + rule tuning + IP lists + custom rules + plugins + rate limits + exclusions.
Node
A data plane running Caddy + Coraza. Every deployment has one built-in local node; licensed deployments can add remote Agents that receive signed bundles from the Controller.
Telemetry rollup
Counters and Top-K samples produced by the open-bucket pipeline. Drives the dashboard, the Top-K explorer, and the Prometheus exporter.
Audit row
One hash-chained record per mutation, attributed to the JWT subject. The compliance surface — every change has a tamper-evident receipt.

Sites and upstream pools

A site binds a hostname to TLS settings, an upstream pool, and a WAF policy. The applier translates each enabled site into a Caddy server block at apply time — Caddy JSON is a compile target, not a storage format.

Upstream pools are detached from sites by default. One pool can back many sites; deleting a pool that's in use is rejected with UPSTREAM_IN_USE and a list of referencing sites. Sites can also use an inline upstream URL for ad-hoc cases that don't need pool sharing.

Access log recording mode

Each site has a recording_mode field that controls access-log volume:

Security events (Coraza rule hits) are always written regardless of access-log mode.

Certificates

TLS material is a first-class resource — never inline on the site. Tiyi supports four issuance paths, all encrypted at rest with a 32-byte KEK loaded from crypto.kek_file:

The dashboard exposes certs_expiring_14d / certs_expiring_30d / acme_renewals_failed_24h / acme_orders_pending as KPIs. Two default alerts are seeded on first boot: Certificates Expiring Soon and ACME Renewal Failures.

WAF policy

A policy is a structured domain object. At apply time Tiyi compiles Coraza SecLang for CRS and custom rules, and a separate compressed native IP-list artifact for CIDR/geo:CC matching. Both compilers are deterministic. The 12 tunable layers (in the order they apply):

  1. CRS Core — paranoia level, blocking and executing PL, inbound/outbound thresholds, sampling.
  2. HTTP Policy — methods, versions, content types, charsets, restricted extensions/headers, method override.
  3. Limits — six numeric fields: max request body, max response body, etc., with CRS-default placeholders.
  4. Rule tuning — per-rule overrides: DEFAULT, DISABLE, LOG_ONLY, SCORE_OVERRIDE, optionally scoped to a single site.
  5. IP lists — allow / deny / monitor lists. Entries can be CIDR or geo:CC pseudo-entries (alpha-2/alpha-3 country codes plus PRIVATE / LOOPBACK). Country and CIDR expansions compile into the native tiyi_iplist handler ahead of Coraza; they are never expanded into SecLang. Overlapping allow bindings take precedence over deny.
  6. Custom rules — operator-authored SecLang or visual-spec rules in the 8000000–8999999 ID range.
  7. Plugins — CRS rule-exclusion packages (WordPress, Drupal, Nextcloud, phpBB, phpMyAdmin, XenForo, cPanel, DokuWiki). Offline archive upload supported.
  8. Rate limits — per-endpoint and client-scope buckets with block or log actions.
  9. Exclusions — phase-1 tx.crs_exclusions_<slug> opt-ins, optionally scoped with @beginsWith.
  10. Preview — read-only SecLang dump for the current revision.
  11. Versions — snapshot per save, side-by-side diff between any two revisions.
  12. Test lab — fire fixture requests through the compiled bundle without touching the live data plane.

Per-site overlays layer scalar fields (paranoia, thresholds, sampling) plus site-scoped rule overrides, custom rules, IP-list bindings, and rate-limit endpoints on top of an attached policy without forking it.

Local node and remote Agents

The Controller always owns one built-in local data plane. It applies the same signed, validated configuration in-process, but it is not enrolled, persisted, heartbeated, or commanded as an Agent. The Nodes page shows it first as This machine · Local · Built in.

Remote Agents hold one long-lived ConnectRPC stream to the configured Controller URL. Each Agent verifies the bundle signature against its pinned ed25519 public key, rejects replay or reordering, applies the accepted bundle to local Caddy, and reports the result. If the Controller is unavailable, the Agent reconnects with backoff while continuing to proxy with its last accepted bundle.

One topology, no role switch. Adding or removing remote Agents changes the node count only. The Controller does not change modes or roles, and remote continuity does not make the Controller highly available.

Telemetry

Telemetry runs as a parallel pipeline that ends per-event SQL writes. The path:

  1. Sharded ingress ring — log-forwarder events fan into per-shard rings keyed by site.
  2. 10-second open-bucket aggregator — per-shard worker accumulates counters into a 10-second bucket.
  3. Misra-Gries Top-K — bounded summary for high-cardinality dimensions (client IP, path, UA, ASN, attack tag) with an __other__ bucket so SUM(*) equals true traffic.
  4. Per-day SQLite partitions at logs/rollup_10s/YYYY-MM-DD.db; downsamplers fold these into rollup_hourrollup_dayrollup_month idempotently.
  5. Sample rings — per-IP LRU + global rings with an append-only circular-file WAL for replay-on-boot.
  6. API tree — per-site URL prefix tree with bounded node and depth guards; idle leaves evict to api_tree_archive.
  7. Reads — REST API at /api/v1/telemetry/* and a Prometheus exporter at /metrics on the local admin socket.

Audit chain

Every mutation (site, upstream, cert, policy, IP-list binding, rate-limit endpoint, trust-profile change, custom rule, alert rule, etc.) appends one row to audit_event. Each row stores:

The Vben Admin Verify chain button walks the chain and confirms each link. SIEM egress with siem.filter.include_audit forwards every audit row to your Splunk / Elastic / Sentinel pipeline.


That's the whole model. Every CLI command, every UI page, every RPC operates on one of these seven resources. The rest of these docs reference them by name without re-defining.