11 KiB
name, description
| name | description |
|---|---|
| agent-provisioning | Provision TSYS Group agent identities end-to-end: Cloudron account (unique password, TOTP with seed captured to the TSGCOO vault), SSO first-login into Redmine / Gitea / Discourse / GLPI, per-system API key minting, vault item schema, and verification. Use whenever asked to "stand up" an identity or agent, create/fix Cloudron users, wire SSO, debug identity logins or 2FA, or store identity credentials — even if the word "provisioning" is not used. Policy-critical: encodes founder rulings #942 (unique passwords, Cloudron-only accounts, no reachableceo credentials). |
Agent identity provisioning (TSYS Group)
Stand up an agent identity so it can operate its own accounts in Cloudron and the four SSO-wired systems, with every credential landing in the TSGCOO Bitwarden vault. The Cloudron is the ONLY identity source: never create local accounts in Redmine, Gitea, Discourse, or GLPI — the system account comes into existence when the identity completes its first SSO login there.
Non-negotiable policy (founder rulings — violating these invalidates the work)
- Unique password per identity. One identity, one password, used only for its Cloudron account. Shared passwords are banned (Redmine #942).
- Cloudron-only accounts. System access = SSO first-login. No local accounts, no admin-created users in the downstream systems.
- Never use reachableceo credentials. Items like
creds/gitea-admin,creds/gitea,creds/redmineare the founder's personal accounts and carry RESTRICTED notes. If an auth test resolves to userreachableceo, stop — you are holding the founder's identity. - TOTP seeds MUST be stored in Bitwarden (founder ruling 2026-09-08). Enroll TOTP and write the base32 seed to the vault item in the same breath — a seed that is not in the vault is treated as lost, because there is no admin 2FA reset on this Cloudron and no other record exists.
- Secrets live only in the vault (KNELSecretsManager
smCLI) — never in repos, command output, logs, or transcripts. Mask credentials in every command's visible output. - Bookkeeping in the same turn: Redmine ticket on the provisioning program (#942 or its successor), cross-linked; vault item created;STATUS updated.
Scope check
Identity stand-up is the COO C2 lane's assigned program (founder directive, 2026-09-08). If your chat runs in another lane (SRE, security, OAM), confirm the founder assigned the stand-up to you before executing, or route a ticket to the COO C2 lane instead.
Admin lever: Cloudron API token
creds/cloudron holds a working admin API token (service user api-token,
64 chars). Use it as Authorization: Bearer $PASSWORD. Do NOT log into the
dashboard interactively for admin work — the token is the compliant path.
scripts/cloudron-api.sh GET /users?limit=100 # list users (note uid- UUIDs)
scripts/cloudron-api.sh GET /users/<uid> # fetch one user object
Route notes (this Cloudron, 2026-09): users are addressed by uid-… UUIDs,
not usernames; there is NO /api/v1/login (dashboard auth is its own OIDC
flow, client cid-webadmin, PKCE S256); admin CAN clear a user's TOTP via
POST /api/v1/users/<uid>/totp_disable (empty JSON body) — use it when a
seed was lost, then re-enroll with capture. Admin password set:
POST /api/v1/users/<uid>/password {"password":"…"}.
Procedure
0. Prerequisites
- Vault reachable:
sm get creds/cloudronworks (see references/vault.md). - For a NEW Cloudron user: the founder generates the invite, or you create the user via the admin token if the founder has authorized self-service.
- A Redmine ticket to record the work (create one if none exists).
1. Cloudron account (unique password)
Verify the account exists and matches the identity email pattern
(tsgstaff-<login>@turnsys.com for COO-line identities). Generate the unique
password (32 chars), set it via the admin API, and store it in the vault item
login password field:
scripts/cloudron-api.sh POST /users/<uid>/password '{"password":"<32-char>"}'
sm setfield "<login> Cloudron" password "<32-char>"
2. TOTP 2FA — enroll and CAPTURE THE SEED
Two cases:
- Seed lost / never captured (TOTP enforced, no
totp_seedin vault): clear first —scripts/cloudron-api.sh POST /users/<uid>/totp_disable— then proceed to enroll fresh. - No 2FA yet: enroll directly.
Enrollment runs in the identity's own dashboard session (the totp routes are self-scoped — an admin token gets 404s there):
scripts/cloudron-oidc-login.sh <username> <vault-item>— full OIDC login (PKCE + interaction + consent) yielding the identity's Bearer at/tmp/.oidc-bearer-<username>.scripts/cloudron-totp-enroll.sh <username> <vault-item>— reads the pending secret, enables with a computed code, stores the seed viasm setfield, and verifies end-to-end.
If the API confirm path is rejected, fall back to the dashboard UI in the
browser: the forced "Enable TOTP" dialog shows the seed; read it, compute
with scripts/totp.sh, fill the Token field and submit. CRITICAL: the
pending secret REGENERATES every time the dialog re-renders — always read
the seed and submit the code within the same window, and verify enrollment
by a real TOTP login (a totpToken in a login is silently ignored for users
without 2FA — "success" there proves nothing).
If the identity already has 2FA enforced and no seed exists anywhere: STOP and
ask the founder. There is no programmatic recovery (verified 2026-09-08).
Compute codes offline when needed: scripts/totp.sh <seed>.
3. TOTP enrollment (in the identity's dashboard session)
scripts/cloudron-oidc-login.sh <username> <vault-item> # OIDC login -> Bearer
scripts/cloudron-totp-enroll.sh <username> <vault-item> # enroll + seed + verify
If the API enable path is rejected, fall back to the dashboard UI dialog (see step 2's note). The verify pass inside the scripts performs a real TOTP login and is the ONLY trustworthy success signal.
4. SSO first-login into each system
The interactive login is scriptable with a cookie jar — no browser needed.
scripts/oidc-login.sh <system> <login> runs the whole flow and leaves a
session cookie jar at /tmp/oidc-<login>-<system>.txt. Entry points:
| System | SSO start point | After login |
|---|---|---|
| GLPI | https://cmdb.knownelement.com/plugins/singlesignon/front/callback.php/provider/1?remember=1 |
account auto-provisioned |
| Gitea | https://git.knownelement.com/user/oauth2/cloudron |
account auto-provisioned |
| Discourse | https://community.turnsys.com/auth/openid_connect |
account auto-provisioned |
| Redmine | https://projects.knownelement.com/oauth |
account auto-provisioned (if the bounce in references/systems.md is hit, see that file) |
Key gotcha (cost a debugging session — do not rediscover): the OIDC
interaction login is POST /openid/interaction/<uid>/login with JSON body
{"username": "...", "password": "...", "totpToken": "..."} — the field is
username, not login, and the response is {"redirectTo": ...} on
success.
5. Mint per-system API keys (as the identity, via its session)
- Redmine: My account → API key (visible to self); also returned by
GET /users/current.jsonwith the session. - Gitea: Settings → Applications → new token with write scopes; or
POST /api/v1/users/<login>/tokensusing the session (requires basic auth of the same user for token endpoints). - Discourse: user API key flow; or admin-generated key if an admin lever is ever provided (none currently is — do not use founder keys).
- GLPI: My account → API token (personal token); pair with the app token
from
creds/glpi(password field) for REST calls.
Store each key in the vault item immediately (schema below).
6. Vault item schema — creds/<login>
One item per identity (NOT per system), created as a LOGIN item (never a
note — sm set makes notes; sm convert <name> converts, but REGENERATE
the password via the admin API afterwards because convert drops field
values):
- login username = Cloudron username; password = the unique Cloudron password
- URI =
https://my.knownelement.com - fields:
email,organization,role,reports_to,created,services, plus per-system:redmine_api_key,gitea_token,discourse_user_api_key,glpi_user_token, andtotp_seed
Historical items that predate this schema (e.g. sec-*, sre-* shared-
password items) are remediation targets under #942 — upgrade them when the
owner lane assigns the work, do not replicate their shape.
7. Update the org chart (MANDATORY — an uncharted identity is an incomplete task)
After provisioning changes the identity estate:
- Update the org table in
TSYSGroupCorporate/TSYSGroupAIOSagents/global/AGENTS.md(branch + PR per the house-rules flow; the symlink serves it live once merged). - Update
reachableceo/org-buildoutdocs/org-chart.md(the rendered chart) in the same turn. - Note the change on the provisioning ticket.
Details: references/org-chart-update.md.
7. Verify the trail
Prove each system knows the identity (expected: 200 + the identity's own name; a 200 from the wrong identity is a FAIL):
curl -sf -H "X-Redmine-API-Key: $KEY" https://projects.knownelement.com/users/current.json | jq .user.login
curl -sf -H "Authorization: token $TOKEN" https://git.knownelement.com/api/v1/user | jq .login
Then update the Redmine ticket (full URLs, [agent] <your-identity> note) and
the workspace STATUS/memory.
References — read before touching the corresponding system
references/cloudron.md— admin API routes, OIDC interaction flow, 2FA.references/systems.md— per-system SSO/API details and known breakage.references/vault.md— sm CLI entry points, gotchas, fallbacks.references/org-chart-update.md— the mandatory post-provisioning chart sync.references/incident-log.md— what went wrong on 2026-09-07/08 and why, so the same failures are not re-derived.
Scripts
scripts/cloudron-api.sh METHOD PATH [JSON]— authenticated admin call.scripts/cloudron-oidc-login.sh <username> [vault-item]— full OIDC dashboard login (PKCE + interaction + consent); Bearer at/tmp/.oidc-bearer-<username>.scripts/cloudron-totp-enroll.sh <username> [vault-item]— enroll TOTP, capture the seed to the vault, verify end-to-end.scripts/oidc-login.sh <glpi|gitea|discourse|redmine> <login>— system SSO login via cookie jar.scripts/totp.sh <base32-seed>— current 6-digit TOTP code (pure bash, RFC-7636-vector tested).