Control Plane
The FastAPI application under control-plane/src/cirrus/app.py provides the authoritative interface for configuring Cirrus CDN. This chapter explains the application lifecycle, authentication model, key routers.
Application Lifecycle
control-plane/src/cirrus/app.py boots a FastAPI instance with a custom lifespan handler:
- Startup – Loads CNAME settings (
cname/settings.py), connects to Redis usingredis.asyncio, and starts theCnameService, which in turn spawns the hidden master DNS server and subscribes tocdn:cname:dirtynotifications. - Shutdown – Gracefully stops the CNAME service and closes the async Redis connection.
Routers are mounted beneath /api/v1 and grouped by domain:
- Authentication (
api/auth.py) - User administration (
api/users.py) - Node management (
api/nodes.py) - Domain management (
api/domains.py) - ACME automation (
api/acme.py) - Cache purge (
api/purge.py) - Service tokens (
api/tokens.py)
Static frontend assets mount at the root path after API routers, ensuring API endpoints take precedence.
Authentication & Authorization
control-plane/src/cirrus/core/security.py implements the security model:
- Sessions – The
/auth/loginendpoint verifies submitted credentials against the stored Argon2 hash (verify_password), stores in-memory sessions keyed by a random token, and drops a cookie (SESSION_COOKIE_NAME). Sessions refresh TTL on each authenticated request. By default, cookies are HTTP-only and adoptSameSite=lax. - Bearer Tokens – Requests with
Authorization: Bearer <token>are checked against two sources: theMASTER_API_TOKENenvironment variable (treated as a superuser) and hashed service tokens persisted in Redis (core/tokens.py). - Access Enforcement –
require_userdetermines whether authentication is required. If no users or tokens exist, the system permits unauthenticated access (development bootstrap). - Service Tokens –
core/tokens.pystores tokens using SHA-256 hashes, suffix metadata (the last two characters for operator visibility), creation timestamps, and optional creator fields.
The request state captures the auth method and token ID, which downstream routers can inspect to enforce stricter policies (e.g., master-only endpoints in api/tokens.py).
- Session Cookies
- Bearer Tokens
- Master Token
The MASTER_API_TOKEN grants superuser access; restrict its scope and lifetime.
Domain Management
control-plane/src/cirrus/api/domains.py implements CRUD operations for domain configurations:
- Create (
POST /api/v1/domains/{domain}) – Validates uniqueness, persists theDomainConfpayload as JSON incdn:dom:{domain}, adds the domain tocdn:domains, and initializes ACME metadata if DNS-01 is enabled. Broadcastscdn:cname:dirtyto trigger DNS rebuild. - Read (
GET /api/v1/domains/{domain}) – Returns the JSON configuration, raising404if absent or500if stored data is malformed. - Update (
PUT /api/v1/domains/{domain}) – Replaces configuration, updates ACME state (enabling/disabling as necessary), and republishes the dirty event. - Sub-resources –
PUTendpoints allow targeted updates:/cache-rules: toggles cache enablement and rule definitions./upstreams: replaces origin list./upstream-headers: sets headers forwarded to origins./cert: stores manually uploaded PEM material for the TLS handshake layer.
- Delete – Removes domain configuration, certificates, and ACME locks; announces DNS rebuild.
- CNAME Resolution (
GET /{domain}/cname) – Computes access FQDN, selects serving nodes via rendezvous hashing, and returns assigned replica metadata. This endpoint requires CNAME support to be enabled in the environment (CNAME_BASE_DOMAIN).
DomainConf Schema
The DomainConf model (api/schemas.py) contains:
origins: An ordered list ofUpstreamrecords (scheme, host, port, weight).upstream_headers: Key-value overrides applied per request to the origin.cache_enable: top-level cache toggle; defaults totrue.slice_enable: top-level slice toggle; defaults tofalse.rules: cache rule list (TTL, methods, header filters, min uses, 206 policy).use_acme_dns01: Toggle for ACME automation; when true, the API ensurescdn:acme:{domain}exists.acme_cname_target: Optional override for the expected_acme-challengeCNAME.
Node Management
control-plane/src/cirrus/api/nodes.py offers a declarative PUT /api/v1/nodes endpoint:
- Accepts a list of
NodeDefinitionobjects (schemas.py) with validated IPv4/IPv6 addresses. - Ensures IDs are unique within the payload.
- Removes nodes absent from the submitted list.
- Preserves operational metadata such as
active,health_fails, andhealth_succs, which are maintained by Celery health checks. - Broadcasts a zone rebuild request upon completion.
Nodes are stored as Redis hashes with fields:
| Field | Description |
|---|---|
name | Friendly tag for operators. |
a / aaaa | Canonicalized IPv4/IPv6 addresses (empty string if unset). |
active | "1" or "0"; drives inclusion in DNS records. |
health_fails, health_succs | Counters used to determine state transitions. |
User Administration
control-plane/src/cirrus/api/users.py exposes CRUD operations for operator accounts:
GET /users– Lists usernames with email, status, and last login timestamp (default"Never"). Metadata is read fromcdn:user:{username}hashes.POST /users– Adds user tocdn:usersset and stores hashed password, email, and status.PUT /users/{username}– Updates email, password (re-hashing), and status; absent users yield404.DELETE /users/{username}– Removes from Redis set and deletes the hash.
These operations rely on require_user, ensuring only authenticated operators can manage accounts.
Authentication Endpoints
control-plane/src/cirrus/api/auth.py provides:
/auth/login– Validates credentials, writes session cookies, updateslast_login./auth/logout– Removes session cookie and deletes session from in-memory map./auth/me– Returns the current username, auth method (session,token, ormaster), and token ID if present./auth/change-password– Verifies current password, updates to new hash, and forces logout by clearing the session.
Feature toggles, such as ENABLE_PASSWORD_AUTH, are controlled via environment variables (see the Appendices for a full list).
ACME APIs
control-plane/src/cirrus/api/acme.py resides under the /domains namespace:
GET /{domain}/acme– Combines domain configuration with ACME registry details. If ACME is enabled, the handler ensures the domain has acme-dns credentials by callingensure_acme_registered(acme_common.py).POST /{domain}/acme– Queues a Celery task to issue/renew a certificate. Guarded by Redis locks (cdn:acme:lock:{domain},cdn:acme:task:{domain}) to prevent concurrent requests.DELETE /{domain}/acme– Clears ACME status and locks, allowing a fresh registration workflow.
Errors are surfaced with relevant HTTP status codes (e.g., 400 when ACME DNS-01 is disabled, 409 for concurrent tasks).
Purge Endpoint
control-plane/src/cirrus/api/purge.py exposes a simple POST /api/v1/purge endpoint:
- Accepts
domainandpathparameters (query string). - Normalizes the path to ensure it starts with
/. - Publishes a JSON payload to the configured purge channel (
CDN_PURGE_CHANNEL, defaultcdn:purge). - OpenResty workers subscribed to this channel issue the actual PURGE requests (see Data Plane).
Service Token Management
control-plane/src/cirrus/api/tokens.py restricts token operations to the master token:
GET /auth/tokens– Lists stored tokens without revealing the full secret.POST /auth/tokens– Creates a token with a random suffix, stores hashed value, and returns the cleartext token once to the caller.DELETE /auth/tokens/{token_id}– Removes the token and its hash index.
Tokens allow external automation to call the control plane without user sessions.
Integration Interfaces
- OpenAPI & Client Generation – FastAPI serves interactive docs at
/docsby default; use this to generate SDKs for CI/CD or BI tools. - Service Tokens – Create scoped automation credentials via
/auth/tokensand useAuthorization: Bearer <token>in requests. - Webhooks (pattern) – While not built-in, external systems can poll status endpoints (e.g., ACME state) or subscribe to Redis channels (
cdn:purge,cdn:cname:dirty) for event-driven workflows. - Log/Metric Correlation – Use the
hostfield to correlate API calls with OpenResty metrics and access logs for end-to-end tracing in your observability platform.
Error Handling & Logging
- FastAPI automatically wraps unhandled exceptions into 500 responses; the code deliberately raises
HTTPExceptionwith meaningful messages for foreseeable error states (conflicts, invalid config). - ACME routes log significant events to
uvicorn.errorfor queueing (acme_queued) while the Celery worker emits progress/failure events (acme_start,acme_done,acme_fail) on thecirrus.acmelogger. - DNS service startup errors are logged and propagated to fail-fast.
Development & Testing
- Tests under
control-plane/tests/usepytestwith asynchronous fixtures (seecontrol-plane/tests/helpers/). Thejust quicktestrecipe skips long-running tests (ACME, DNS health). - Enabling
ENABLE_TEST_ENDPOINTS=true(as indocker-compose.yml) exposes helper endpoints under/__test__/to trigger ACME scans or CNAME rebuilds without waiting on schedules.
The control plane API enforces the data contracts that subsequent sections rely on. See Automation & Certificates for the automation tasks that act on this state, DNS & Traffic Engineering for how the DNS subsystem consumes domain and node data, and Data Plane for how OpenResty interprets the same configuration at request time.