Files
KNELAgentIdentityProvisioning/skills/agent-provisioning/SKILL.md
T

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)

  1. Unique password per identity. One identity, one password, used only for its Cloudron account. Shared passwords are banned (Redmine #942).
  2. Cloudron-only accounts. System access = SSO first-login. No local accounts, no admin-created users in the downstream systems.
  3. Never use reachableceo credentials. Items like creds/gitea-admin, creds/gitea, creds/redmine are the founder's personal accounts and carry RESTRICTED notes. If an auth test resolves to user reachableceo, stop — you are holding the founder's identity.
  4. 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.
  5. Secrets live only in the vault (KNELSecretsManager sm CLI) — never in repos, command output, logs, or transcripts. Mask credentials in every command's visible output.
  6. 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/cloudron works (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_seed in 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):

  1. scripts/cloudron-oidc-login.sh <username> <vault-item> — full OIDC login (PKCE + interaction + consent) yielding the identity's Bearer at /tmp/.oidc-bearer-<username>.
  2. scripts/cloudron-totp-enroll.sh <username> <vault-item> — reads the pending secret, enables with a computed code, stores the seed via sm 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.json with the session.
  • Gitea: Settings → Applications → new token with write scopes; or POST /api/v1/users/<login>/tokens using 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, and totp_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:

  1. Update the org table in TSYSGroupCorporate/TSYSGroupAIOS agents/global/AGENTS.md (branch + PR per the house-rules flow; the symlink serves it live once merged).
  2. Update reachableceo/org-buildout docs/org-chart.md (the rendered chart) in the same turn.
  3. 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).