Policy rulings (#942): unique passwords, Cloudron-only SSO accounts, no reachableceo credentials, TOTP seeds MUST be captured to the vault. Cloudron admin API wrapper, cookie-jar OIDC login, RFC-6238-tested TOTP helper, per-system SSO/API references, incident log.
63 lines
3.1 KiB
Markdown
63 lines
3.1 KiB
Markdown
# Cloudron admin API + OIDC (verified 2026-09-08)
|
|
|
|
## Instance
|
|
|
|
- Dashboard: `https://my.knownelement.com` (admin surface; this host also
|
|
fronts the org's public vhosts — it is NOT the tailscale-only control box).
|
|
- API token: vault item `creds/cloudron`, password field, 64 chars, service
|
|
user `api-token`. Verified: reads full user directory.
|
|
|
|
## Routes (this is a newer Cloudron — older route knowledge may not apply)
|
|
|
|
- Users are addressed by `uid-<uuid>`, never username.
|
|
`GET /api/v1/users?limit=100` → list; `GET /api/v1/users/<uid>` → object
|
|
(`twoFactorAuthenticationEnabled` is null/absent on objects even when
|
|
enforced — the OIDC interaction is the source of truth).
|
|
- There is NO `POST /api/v1/login`. Dashboard auth is its own OIDC browser
|
|
flow; the SPA bundle (as of 2026-09-08) contains no login route.
|
|
- There is NO admin route to reset another user's 2FA (PUT/PATCH/DELETE on
|
|
`/users/<uid>` 2FA variants all 404; the dashboard UI only offers profile
|
|
self-service enrollment). Consequence: TOTP seeds not captured at
|
|
enrollment are unrecoverable without the founder.
|
|
- Other useful routes seen in the SPA bundle: `/api/v1/tokens`,
|
|
`/api/v1/oidc/clients`, `/api/v1/groups`, `/api/v1/eventlog`,
|
|
`/api/v1/app_passwords`. Verify behavior at runtime before relying on a
|
|
specific payload shape.
|
|
|
|
## OIDC interaction login (how identity SSO works)
|
|
|
|
The GLPI/Gitea/Discourse/Redmine SSO flows all redirect here:
|
|
|
|
1. `GET <app SSO start point>` → 302 to
|
|
`https://my.knownelement.com/openid/interaction/<uid>` (Cloudron runs a
|
|
node-oidc-provider at `/openid`). Cookies matter — keep a cookie jar.
|
|
2. `POST /openid/interaction/<uid>/login` with JSON
|
|
`{"username": "<login>", "password": "<unique password>"}`.
|
|
- Field name is **`username`** — `login` returns
|
|
`"A username must be non-empty string"` (400).
|
|
- Success → `{"redirectTo": "..."}` (or
|
|
`{"twoFactorRequired":true,"totpRequired":true}` if 2FA is enabled).
|
|
- Wrong credentials → 401 `{"status":"Unauthorized","message":"Username
|
|
and password does not match"}`.
|
|
3. `GET` the `redirectTo` (same cookie jar) → completes at the app.
|
|
|
|
Email-style usernames work (`tsgstaff-coo@turnsys.com`); bare usernames also
|
|
exist (`coo`). Identity emails follow `tsgstaff-<login>@turnsys.com`.
|
|
|
|
## 2FA
|
|
|
|
- TOTP enforced per user at the interaction step
|
|
(`twoFactorRequired`/`totpRequired` in the login response).
|
|
- Enrollment is profile self-service ONLY. Capture the base32 seed into the
|
|
vault (`sm setfield "<login> Cloudron" totp_seed <seed>`) at enrollment —
|
|
there is no admin reset and no recovery path.
|
|
- Policy RULED (founder, 2026-09-08): TOTP seeds MUST be stored in Bitwarden.
|
|
Enroll 2FA only when the seed is being captured to the vault item in the
|
|
same step. Legacy identities whose seeds were never captured (the whole
|
|
August batch) are reset-on-sight candidates: founder clears 2FA or hands
|
|
over the seed, then re-enroll with capture.
|
|
|
|
---
|
|
|
|
**Records:** Redmine #942 — https://projects.knownelement.com/issues/942 · Discourse docs home: https://community.turnsys.com (skill topic pending admin-key provisioning, tracked on #942)
|