- BW access info is the ONLY secret on disk; everything else in BW
- reachableceo session stays as Charles's personal assistant
- TSGCOO session runs the provisioning + holds BW access info
- Discourse admin via system username (not reachableceo)
- reachableceo will handle ticket transitions to new agents
💘 Generated with Crush
Assisted-by: Crush:glm-5.2
19 KiB
AI Agent Identity Bootstrapping — Provisioning Plan
Status: DRAFT — architecture confirmed, awaiting user prerequisites
Date: 2026-08-13
Goal: Stand up dedicated AI agent identities with proper attribution, RBAC, and audit trails. Agents own their own credentials in Bitwarden (no shared ~/.creds/ env files).
1. Confirmed Architecture
┌─────────────────────────────────────────────────────────┐
│ CLOUDRON (tsys-cloudron.knel.net) │
│ Identity root — IdP for ALL apps except Uptime Kuma │
│ │
│ Agent identities (each a separate Cloudron user): │
│ vp-techops, vp-secops, vp-techcompliance (Q3) │
│ coo, svp-knel, svp-tctc, vp-finance, ... (Q4) │
└────────────┬────────────────────────────────────────────┘
│ Cloudron SSO (auto-provisions identity)
▼
┌─────────────────────────────────────────────────────────┐
│ CLOUDRON-MANAGED APPS │
│ Gitea · Discourse · Redmine · + all other apps │
│ │
│ Each agent SSOs in → generates its OWN API keys → │
│ stores in Bitwarden. Agents are fully independent. │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ BITWARDEN (dedicated "COO" account) │
│ Credential vault — RCEO owns all creds/orgs/collections │
│ │
│ Collections: vp-techops/, vp-secops/, shared/, etc. │
│ Each item: service URL, username, password, API key, │
│ TOTP secret, SSH private key │
│ │
│ bw CLI authenticates via client_id/client_secret │
│ (machine-to-machine, non-interactive) │
└─────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────┐
│ COO LINUX ACCOUNT (orchestration layer) │
│ Currently: reachableceo's workstation │
│ Soon: dedicated hardened VM on PFVCluster │
│ │
│ This is where Charles/AJ interact with agents. │
│ Agents are "personalities" (Crush/Hermes sessions) │
│ that run here, each sourcing its own creds from BW. │
│ │
│ NO key material on disk. SSH via BW SSH agent. │
└─────────────────────────────────────────────────────────┘
Key principles
- Cloudron is the identity root. One invite = identity everywhere (SSO auto-provisions).
- Bitwarden is the ONLY credential store. The only secret that lives on disk is the BW access info itself (client_id, client_secret, master password). Everything else — Discourse API keys, Redmine keys, Gitea tokens, SSH private keys, TOTP secrets, app credentials — lives in Bitwarden exclusively. No
~/.creds/*.envfiles. No key material on disk. - Agents own their credentials. Each agent generates its own API keys after SSO login, stores them in Bitwarden. Agents are fully independent — each is a separate Cloudron user with its own SSO sessions, API keys, and Bitwarden collection.
- The
cooLinux account is the orchestration layer — where humans (Charles/AJ) launch and interact with agent sessions. Not where agents "live" — agents live as Cloudron identities. - SSH via BW SSH agent. No private keys on disk. Aligns with SecOps target: only iPad enclave key + Bitwarden key.
- reachableceo's personal Crush session stays as-is — Charles's personal assistant for reviewing tickets, personal projects, and transitioning work to the new agents on his behalf. The TSGCOO account + new agents are an additional layer, not a replacement.
2. The Credential Migration (~/.creds/ → Bitwarden)
Current state (14 credential files)
~/.creds/
├── beszel.env ├── librenms.env ├── redmine.env
├── discourse.env ├── oxidized.env ├── switch-creds.env
├── grafana.env ├── phpipam.env ├── technitium.env
├── pushover.env ├── prometheus.env ├── unifi-creds.env
└── uptime-kuma.env
All CLIs invoked as: docker run --rm --env-file ~/.creds/<service>.env <cli-image> ...
All using reachableceo's API keys. No per-agent credential separation.
Target state
Bitwarden org: "TSYS Group AI Agents"
├── vp-techops/ (collection)
│ ├── Cloudron (login + TOTP)
│ ├── Gitea (API token)
│ ├── Discourse (API key)
│ ├── Redmine (API key)
│ ├── SSH key (Ed25519 private key)
│ └── <infra creds> (LibreNMS, Grafana, etc. — scoped to TechOps)
├── vp-secops/
│ ├── Cloudron / Gitea / Discourse / Redmine / SSH
│ └── <security creds>
├── vp-techcompliance/
│ └── ...
├── shared/ (fleet-wide creds all agents need)
│ ├── Technitium DNS
│ ├── Pushover
│ ├── Switch credentials
│ └── Beszel
└── coo/ (Q4)
└── ...
Migration path (2 stages)
Stage 1 — Agent provisioning. New agent identities are created in Cloudron, generate their own API keys via SSO, and store everything in Bitwarden. They source credentials exclusively via bw-run.sh. The ~/.creds/ files are NOT used by agents — only by reachableceo's personal session.
Stage 2 — Full migration (Redmine #440, Aug 19). reachableceo's ~/.creds/ files are migrated into Bitwarden too. After this, the ONLY secret on disk is the BW access info (client_id/client_secret/master password). All CLI invocations use bw-run.sh. The ~/.creds/ directory is deleted.
Principle: the only thing outside Bitwarden is Bitwarden's own access information. Everything else — API keys, passwords, SSH keys, TOTP secrets — lives in BW exclusively.
The credential sourcing layer
A wrapper script that replaces the --env-file ~/.creds/ pattern:
#!/usr/bin/env bash
# bw-run.sh — run a CLI command with credentials sourced from Bitwarden
# Usage: bw-run.sh <agent> <service> <image> [args...]
# Example: bw-run.sh vp-techops redmine redmine-cli:latest list --assigned-to-me -p 55
set -euo pipefail
AGENT="$1"; SERVICE="$2"; IMAGE="$3"; shift 3
# Ensure BW session is active
BW_SESSION="${BW_SESSION:-$(bw unlock --raw 2>/dev/null)}" || true
export BW_SESSION
# Fetch credential item from Bitwarden
ITEM_NAME="${AGENT} ${SERVICE}"
ENV_JSON=$(bw get item "$ITEM_NAME" | jq -r '
.login |
"URL=\(.uris[0].uri // "")\n" +
"USERNAME=\(.username // "")\n" +
"PASSWORD=\(.password // "")\n" +
(.fields[]? | "\(.name)=\(.value)\n")
')
# Write to temp env file (cleaned up on exit)
ENVFILE=$(mktemp /tmp/bw-${AGENT}-${SERVICE}-XXXXXX.env)
trap 'rm -f "$ENVFILE"' EXIT
echo -e "$ENV_JSON" > "$ENVFILE"
# Run the CLI with BW-sourced credentials
docker run --rm --env-file "$ENVFILE" "$IMAGE" "$@"
This preserves the existing CLI invocation pattern (docker run --env-file ... <image>) while sourcing credentials from Bitwarden instead of static files. Agents call bw-run.sh vp-techops redmine ... instead of docker run --env-file ~/.creds/redmine.env ....
What needs to change in each project
| Project | Change | Effort |
|---|---|---|
| TSYSGroupAIOS (meta) | Add bw-run.sh to scripts/. Update BASELINE-PROMPT.md credential references. Document the BW credential model. |
Med |
| PFVCluster | Update AGENTS.md CLI examples to use bw-run.sh. Add agent-profile system. Update scripts/check-rules.sh to enforce no ~/.creds/ refs in new code. |
Med |
| KNEL-AIMiddleware | Update tooling-cli/*/AGENTS.md to document BW sourcing. Update KNELCredsManager to be the BW integration point. |
Med |
| All CLI containers | No image changes needed — they still read env vars. The sourcing layer (bw-run.sh) is outside the container. |
None |
3. Identity Roster
Q3 — TechOps Agents (stand up NOW)
| Agent | Cloudron user | Redmine scope | Gitea scope | Discourse scope |
|---|---|---|---|---|
| vp-techops | vp-techops@ | 55, 59 | KNEL, TechnicalOperations | 74, 20 |
| vp-secops | vp-secops@ | 55 (security) | KNEL | 74 + new VP SecOps cat |
| vp-techcompliance | vp-techcompliance@ | 55 (compliance) | KNEL | 75 |
Q4 — Business Agents (enroll in Cloudron now, activate in Q4)
| Agent | Redmine scope |
|---|---|
| coo | 53, 62, 77 |
| svp-knel | 62, 55 (read) |
| svp-tctc | 15 (TCTC), 31 (RedWFO) |
| vp-finance, vp-accounting, vp-investing, vp-treasury, vp-trading | 15 (TCTC) |
Recommendation: Enroll ALL identities in Cloudron during Q3 (cheap — just invite acceptance). Only provision API keys + system access for the 3 Q3 agents. Q4 agents get activated when business ops work begins.
4. Provisioning Pipeline
Phase 0 — Prerequisites (USER provides)
| # | Item | Detail |
|---|---|---|
| 1 | Linux coo account |
useradd -m -s /bin/bash coo; add to docker group; mkdir ~/.ssh ~/.creds ~/projects |
| 2 | Bitwarden "COO" account | Dedicated BW account. RCEO owns the org. Create collections per agent. |
| 3 | BW API credentials | client_id + client_secret for non-interactive bw login --apikey. Place in a file the agent can read. |
| 4 | Cloudron invite manifest | Text file, one line per identity: `agent-name |
| 5 | Discourse admin key (for VP SecOps category) | Current API user (trust-4) cannot create categories. User creates VP SecOps category via web UI OR provides admin key. |
Phase 1 — Cloudron Identity Enrollment (Playwright automation)
For each identity in the manifest:
1. Launch Playwright Docker (mcr.microsoft.com/playwright:v1.52.0-noble)
2. Navigate to Cloudron invite link
3. Generate strong password (bw generate -uluns --length 32)
4. Fill password fields, submit → identity created
5. Enable 2FA:
a. Navigate to Cloudron account security settings
b. Initiate TOTP enrollment
c. Extract TOTP secret from QR code
d. Store TOTP secret in Bitwarden
e. Confirm with current TOTP code (bw code from stored secret)
6. Store Cloudron credential in Bitwarden:
- Collection: <agent-name>
- Name: "<agent-name> Cloudron"
- URL: https://tsys-cloudron.knel.net
- Username: <agent-name>@<domain>
- Password: <generated>
- TOTP: <secret>
Result: Agent identity exists in Cloudron. SSO works for all managed apps. No per-system account creation needed — SSO handles it.
Phase 2 — API Key Generation (agent logs in via SSO, creates keys)
For each agent, for each system (Gitea, Discourse, Redmine):
1. Launch Playwright
2. Navigate to the app URL → Cloudron SSO redirect
3. Complete SSO login (use agent's Cloudron creds from BW + TOTP from BW)
4. First-time SSO → account auto-provisioned in the app
5. Navigate to API key / access token settings:
- Gitea: Settings → Applications → Generate New Token (scopes: api, repo, read:org)
- Discourse: Preferences → API Keys → (may need admin to create user API key)
- Redmine: My Account → API access key → Show
6. Copy the generated key
7. Store in Bitwarden:
- Collection: <agent-name>
- Name: "<agent-name> <SystemName>"
- URL: <system URL>
- Password field: <API key>
8. Verify: use the key to call the system's API (curl or CLI)
After Phase 2, each agent has:
- Cloudron identity (login + TOTP) in BW
- Gitea API token in BW
- Discourse API key in BW
- Redmine API key in BW
- Ability to operate independently in all three systems
Phase 3 — SSH Key Provisioning
1. Generate Ed25519 keypair per agent: ssh-keygen -t ed25519 -f /tmp/<agent>-ed25519 -N ""
2. Store PRIVATE key in Bitwarden (as "Secure Note" or BW SSH Agent item)
3. Deploy PUBLIC key to fleet via KNELIAC:
- Add to inventory/group_vars/all.yml managed_users
- Run: ansible-playbook playbooks/setup_new_system.yml -t security_ssh --limit <targets>
4. Delete private key from /tmp (lives only in BW)
5. Configure BW SSH Agent on coo account: export SSH_AUTH_SOCK=... (bw agent)
Phase 4 — Agent Profile System (on coo Linux account)
Each agent has a profile that sets its identity context:
# ~/agents/vp-techops/profile.sh
export AGENT_NAME="vp-techops"
export AGENT_DISPLAY="VP TechOps"
export AGENT_EMAIL="vp-techops@turnsys.com"
export AGENT_BW_COLLECTION="vp-techops"
export GIT_AUTHOR_NAME="VP TechOps"
export GIT_COMMITTER_NAME="VP TechOps"
export GIT_AUTHOR_EMAIL="$AGENT_EMAIL"
export GIT_COMMITTER_EMAIL="$AGENT_EMAIL"
export REDMINE_PROJECT_SCOPE="55,59"
export GITEA_ORG_SCOPE="KNEL,TechnicalOperations"
export DISCOURSE_CAT_SCOPE="74,20"
# SSH: BW agent provides the key for this agent
Switching agent context:
source ~/agents/vp-techops/profile.sh # become vp-techops
source ~/agents/vp-secops/profile.sh # become vp-secops
Phase 5 — Operational Environment
1. Clone repos into /home/coo/projects/ (or the future hardened VM)
2. Copy/adopt TSYSGroupAIOS framework (check-rules.sh, hooks, etc.)
3. Install bw-run.sh credential sourcing layer
4. Set up per-agent Crush config directories:
~/.config/crush/vp-techops/
~/.config/crush/vp-secops/
5. Configure tea CLI per agent (separate logins = separate tokens)
6. Install git hooks (ticket-gate, pre-commit, pre-push)
7. Smoke test per agent:
- Create a Redmine ticket (appears as agent identity)
- Edit a Discourse topic (appears as agent identity)
- Open a Gitea PR (appears as agent identity)
- SSH to a host (appears in auth log as agent's key)
5. Playwright Automation Design
The Playwright script handles the browser-driven steps (Phases 1-2). Containerized — no browser on host.
provision-agent/
├── Dockerfile # Based on mcr.microsoft.com/playwright:v1.52.0-noble
├── provision-agent.py # Main script
├── bw-helper.py # Bitwarden CLI wrapper (gen password, store item, get TOTP)
└── agents.yaml # The manifest (invite links + agent config)
Flow:
for agent in manifest:
# Phase 1: Cloudron enrollment
browser → invite_link
fill password (bw generate)
enable 2FA → extract TOTP secret → store in BW
# Phase 2: API keys (per system)
for system in [gitea, discourse, redmine]:
browser → system_url → SSO redirect → login with BW creds + TOTP
navigate to API key page → generate → copy
store in BW
# Verify
for system in [gitea, discourse, redmine]:
api_call(system, key_from_bw) → assert success
Security: Playwright runs in Docker, no persistent browser state. Credentials are generated/stored via bw CLI (mounted into container). No credentials in browser memory after the script exits.
6. Security & Compliance Posture
| Control | How this design satisfies it |
|---|---|
| CMMC IA-2(1) (MFA for all accounts) | Every agent identity has TOTP via Bitwarden |
| CMMC AU-2/AU-12 (audit events) | Every action attributable to specific agent identity (Redmine, Discourse, Gitea, SSH auth logs) |
| CMMC AC-2/AC-3 (least privilege) | Each agent scoped to its Redmine projects, Gitea orgs, Discourse categories, and fleet SSH access |
| CMMC IA-5(1) (authenticator management) | No key material on disk. All in Bitwarden. SSH via BW agent. |
| Shared credential elimination | Each agent has its own API keys and SSH keys — no more single key on "every single system" |
| ITAR/SCIF alignment | Per-identity access control + audit trail for classified-adjacent systems |
7. Implementation Timeline
| Week | What | Who |
|---|---|---|
| Week 1 | User provides Phase 0 prerequisites. Agent builds Playwright automation + bw-run.sh. Executes Phase 1-2 for 3 Q3 agents. | User + Agent |
| Week 2 | Phase 3 (SSH keys via KNELIAC). Phase 4-5 (agent profiles, coo env, smoke tests). Enroll Q4 identities in Cloudron (Phase 1 only). | Agent |
| Week 3+ | Agents begin operating from own identities on P1-P9 work. Charles reviews as himself. Agents @mention Charles in Redmine/Discourse. Agents open PRs for review. | Agents + Charles |
8. What I Need From You to Start
Done from this session (reachableceo — Charles's personal assistant)
- Playwright automation repo built and pushed (
TSYSGroupCorporate/agent-identity-provisioning) bw-run.shcredential sourcing layer built and pushed (TSYSGroupAIOS/scripts/bw-run.sh)- Redmine tickets filed (#440 BW migration, #441 taxonomy audit, #442 provisioning)
- Ticket transitions — once agents are provisioned, I (reachableceo session) will transition tickets to the new agent identities on Charles's behalf
To be done from the TSGCOO session (the new operational layer)
- TSGCOO Linux account is created. Set up the Crush session there.
- BW access info — place ONLY the BW client_id/client_secret/master password in the TSGCOO account (e.g.,
~/.config/bw/env). This is the ONLY secret on disk. Everything else comes from BW at runtime. - Discourse admin access — set
DISCOURSE_API_USERNAME=systemin the TSGCOO session's environment (sourced from BW, not a static file). Thesystemuser has admin privileges for category creation. - Cloudron invites — generate invite links for vp-techops, vp-secops, vp-techcompliance. Put them in
agents.yaml. - Run the provisioning —
docker compose up --buildfrom theagent-identity-provisioningrepo. - VP SecOps category — the vp-techops agent (or the TSGCOO session) creates the Discourse VP SecOps category using the
systemadmin credentials.
reachableceo session role (unchanged)
The reachableceo Crush session continues as Charles's personal assistant — reviewing tickets, working personal projects, and handling the transition of work to the new agents. It is not replaced by the TSGCOO layer; it operates alongside it.