Skip to main content

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 using redis.asyncio, and starts the CnameService, which in turn spawns the hidden master DNS server and subscribes to cdn:cname:dirty notifications.
  • 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/login endpoint 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 adopt SameSite=lax.
  • Bearer Tokens – Requests with Authorization: Bearer <token> are checked against two sources: the MASTER_API_TOKEN environment variable (treated as a superuser) and hashed service tokens persisted in Redis (core/tokens.py).
  • Access Enforcementrequire_user determines whether authentication is required. If no users or tokens exist, the system permits unauthenticated access (development bootstrap).
  • Service Tokenscore/tokens.py stores 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).

Domain Management

control-plane/src/cirrus/api/domains.py implements CRUD operations for domain configurations:

  • Create (POST /api/v1/domains/{domain}) – Validates uniqueness, persists the DomainConf payload as JSON in cdn:dom:{domain}, adds the domain to cdn:domains, and initializes ACME metadata if DNS-01 is enabled. Broadcasts cdn:cname:dirty to trigger DNS rebuild.
  • Read (GET /api/v1/domains/{domain}) – Returns the JSON configuration, raising 404 if absent or 500 if 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-resourcesPUT endpoints 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 of Upstream records (scheme, host, port, weight).
  • upstream_headers: Key-value overrides applied per request to the origin.
  • cache_enable: top-level cache toggle; defaults to true.
  • slice_enable: top-level slice toggle; defaults to false.
  • rules: cache rule list (TTL, methods, header filters, min uses, 206 policy).
  • use_acme_dns01: Toggle for ACME automation; when true, the API ensures cdn:acme:{domain} exists.
  • acme_cname_target: Optional override for the expected _acme-challenge CNAME.

Node Management

control-plane/src/cirrus/api/nodes.py offers a declarative PUT /api/v1/nodes endpoint:

  • Accepts a list of NodeDefinition objects (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, and health_succs, which are maintained by Celery health checks.
  • Broadcasts a zone rebuild request upon completion.

Nodes are stored as Redis hashes with fields:

FieldDescription
nameFriendly tag for operators.
a / aaaaCanonicalized IPv4/IPv6 addresses (empty string if unset).
active"1" or "0"; drives inclusion in DNS records.
health_fails, health_succsCounters 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 from cdn:user:{username} hashes.
  • POST /users – Adds user to cdn:users set and stores hashed password, email, and status.
  • PUT /users/{username} – Updates email, password (re-hashing), and status; absent users yield 404.
  • 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, updates last_login.
  • /auth/logout – Removes session cookie and deletes session from in-memory map.
  • /auth/me – Returns the current username, auth method (session, token, or master), 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 calling ensure_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 domain and path parameters (query string).
  • Normalizes the path to ensure it starts with /.
  • Publishes a JSON payload to the configured purge channel (CDN_PURGE_CHANNEL, default cdn: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 /docs by default; use this to generate SDKs for CI/CD or BI tools.
  • Service Tokens – Create scoped automation credentials via /auth/tokens and use Authorization: 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 host field 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 HTTPException with meaningful messages for foreseeable error states (conflicts, invalid config).
  • ACME routes log significant events to uvicorn.error for queueing (acme_queued) while the Celery worker emits progress/failure events (acme_start, acme_done, acme_fail) on the cirrus.acme logger.
  • DNS service startup errors are logged and propagated to fail-fast.

Development & Testing

  • Tests under control-plane/tests/ use pytest with asynchronous fixtures (see control-plane/tests/helpers/). The just quicktest recipe skips long-running tests (ACME, DNS health).
  • Enabling ENABLE_TEST_ENDPOINTS=true (as in docker-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.