Radius¶
radius-ctrl is the RADIUS control plane: a multi-tenant management layer over FreeRADIUS, for WPA/802.1X and captive-portal authentication. It is one of three products on the platform, with the edge gateways (nexus) and Monsieur Wifi.
- Architecture — components, multi-tenancy, the auth request path, deployment topology.
- Database — the FreeRADIUS + tenancy + auth + PKI schema.
Components¶
| Component | Stack | Role |
|---|---|---|
| API | .NET 10 (ASP.NET Core) | Control plane. REST under /api/v1, manages orgs, NAS clients, users, sessions, and certificates. |
| FreeRADIUS | FreeRADIUS 3.2.10 | The RADIUS server itself (auth/acct on UDP 1812/1813). Reads its data from the shared Postgres. |
| SPA | Vite + Preact + openapi-fetch |
Admin UI. Zitadel OIDC (PKCE), calls the API cross-origin. |
| Database | PostgreSQL (radius-<env> on the shared RDB) |
Standard FreeRADIUS tables (radcheck, radacct, nas, …) plus the tenancy/auth/PKI tables the control plane owns. |
The API is the control plane; FreeRADIUS is the data plane. They never call each other — they meet in the database. The API manages rows (NAS clients, user passwords, reply attributes); FreeRADIUS reads those rows to answer Access-Requests and writes accounting rows back.
API surface¶
REST, Authorization: Bearer <Zitadel access token>, routes under /api/v1:
| Route | Purpose |
|---|---|
GET /auth/me |
Resolves the caller's identity and org memberships. There is no login endpoint — Zitadel issues every token, this API only validates. |
GET/POST /orgs, PUT /orgs/{slug}/owner |
List/create organizations (platform-admin only) and transfer an org's registered ownership. |
GET/POST/DELETE /admins, GET /admins/candidates |
Platform-admin grants, managed from the UI. Platform-admin only. |
/orgs/{slug}/nas |
NAS clients (the network devices that send Access-Requests). |
/orgs/{slug}/users |
Per-org RADIUS end users (password + certificate identities). |
/orgs/{slug}/members |
Who can administer the org: GET/POST/DELETE, POST members/invite (create the Zitadel account too), PATCH members/{subject} (change role), GET members/candidates (directory search). |
/orgs/{slug}/sessions |
Live/historical sessions from radacct. |
/orgs/{slug}/auth-log |
Authentication attempts from radpostauth. |
/orgs/{slug}/pki |
Per-org CAs (cas, cas/{id}/certificate, cas/{id}/crl), cas/{id}/server-bundle, issued certificates and certificates/{id}/revoke, and cas/{id}/enroll — issues a client cert packaged for any OS (.p12, PEM files, Apple .mobileconfig, ChromeOS .onc, Windows/Linux bundles). |
GET /provisioning/trust-bundle |
Token-guarded. Every active org CA and current CRL, pulled by the FreeRADIUS VM's ca-sync.sh timer. |
Every org-scoped route runs through ResolveOrgFilter, which loads the {orgSlug} org and checks the caller's access before the controller runs — so controllers only ever see an org the caller may touch, and scope every query to it.