228 lines
11 KiB
Markdown
228 lines
11 KiB
Markdown
---
|
|
name: agent-provisioning
|
|
description: >
|
|
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.
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
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)
|
|
|
|
```bash
|
|
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):
|
|
|
|
```bash
|
|
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).
|