Commit Graph
14 Commits
Author SHA1 Message Date
vptechops a4a54f553e feat: full app provisioning for all COO agents + race-hardened BW helper
App credentials now flow for every agent: shared handle_oidc_interaction
helper fills the per-app OIDC login form/TOTP/consent (the panel session
is not shared across app clients -- each may demand fresh credentials),
and the Discourse signup clears the prefilled username field before
typing (prefill+typed concatenation exceeded the 20-char cap and failed
validation silently).

BW helper hardened against the sync races observed across concurrent
containers: get_item_id re-syncs stale caches, create/edit retry with
backoff and post-write sync. This class of failure was mine -- the
login-path fix from session 3 left read paths on stale caches.

Final validated matrix (validate-all-logins.py, fresh-context logins
plus live API checks): 9/10 fully green; vp-compliance blocked on a
corrupt stored password (Cloudron admin reset needed). Redmine access
still Cloudron-denied for vp-secops, svp-knel, vp-techcompliance,
vp-facilities ("You do not have access" at the OIDC interaction).
2026-08-14 13:53:01 -05:00
vptechops 1b8bd843d9 fix: get_totp exact-name resolution + all-agent login validator
bw's name search is fuzzy: every agent email contains "coo"
(tsgstaff-coo-*), so `bw get totp "coo Cloudron"` matched 10 items and
errored. get_totp now resolves via get_item_id (exact-name filter)
first, mirroring get_item_password.

Added validate-all-logins.py: per-agent fresh-browser-context login
(shared contexts carry session cookies and hide the login form),
asserts password+TOTP round-trip, then verifies every stored API
credential against its system. First full run: 9/10 PASS.

Known failure: vp-compliance stored password does not match the
account ("Incorrect username or password" pre-TOTP) -- enrollment
typed a different value than stored. Needs Cloudron admin reset,
then update_item and re-validate.
2026-08-14 11:48:06 -05:00
vptechops 87298df1ba fix: Cloudron enrollment + 2FA for fresh invite accounts
Invite acceptance (verified live on 8 agents): Pankow forms need
click + keyboard.type (fill() silently no-ops), submit button is
<div role="button"> "Set up" which starts disabled until the form
is valid.

2FA enablement: fresh accounts land on setupaccount.html ("Your
account is ready") and hash navigation cannot leave that page --
load the panel root first. Also match "Set up" (forced-enrollment
screen says "Set up passkey", profile page says "Setup").

Verified in the 8-agent run: coo/svp-knel/svp-tctc/vp-investing/
vp-trading/vp-compliance enrolled with TOTP. vp-secops and
vp-techcompliance ran pre-fix and need the --enable-2fa second pass.
2026-08-14 10:26:05 -05:00
TSYS Group COO 5e1d043890 feat: merge-invites.py -- wire Charles's invite file into the manifest
Parses the loose invites file (name,url per line; blank lines and
comments skipped), normalizes names (vpsecops -> vp-secops), extracts
email/username/displayName from the invite URL query params, and
merges into agents.yaml. Unknown agents are appended phase1-only.

Duplicate invite tokens are skipped loudly (stale copy-paste guard)
while valid entries still merge. Verified live: caught the svp-knel
line reusing coo's token, merged 5 agents, appended vp-investing and
vp-trading.
2026-08-14 09:31:45 -05:00
TSYS Group COO 2258e1bd04 feat: Redmine SSO login + API key extraction working
Redmine SSO flow: Cloudron login -> click "Continue with KNEL Cloud"
button (#login-oauth-submit-1) -> authenticated. Required Cloudron
admin to grant vp-techops access to the Redmine app first.

API key was already present but hidden. Click "Show" in the
.api-key-actions section to reveal it from the #api-access-key
pre element. Key verified via X-Redmine-API-Key header.
2026-08-13 23:41:00 -05:00
TSYS Group COO 2d01a9f962 feat: Discourse SSO + User API key extraction working
Discourse SSO flow: Cloudron login -> click "Log In" -> click OpenID
Connect button -> complete signup (enter username) -> logged in.

User API key generated via Discourse RSA-based flow:
1. Generate RSA keypair, submit public key
2. Authorize request on Discourse
3. Capture encrypted payload from POST response
4. Decrypt with PKCS1v15 padding (Discourse uses this, not OAEP)
5. Parse JSON to extract the key field

API key verified working: User-Api-Key header returns 30 topics from
/latest.json. Key stored in Bitwarden as "vp-techops Discourse".

Redmine SSO is blocked: Cloudron returns "You do not have access" --
the vp-techops user needs app access granted by Cloudron admin.

Also added cryptography==44.0.1 to requirements for RSA operations.
2026-08-13 21:25:29 -05:00
TSYS Group COO be2f607839 feat: enable Cloudron TOTP 2FA for vp-techops (CMMC compliance)
Cloudron's 2FA enrollment flow discovered via comprehensive DOM dump:
1. Profile page -> click "Setup" to start 2FA
2. Cloudron defaults to Passkey -> click "switchToTotp"
3. TOTP secret appears as base32 text -> extract via regex
4. Enter code in #totpTokenInput -> click Enable

Fixed wrong selectors in cloudron_panel_login: the OIDC TOTP field is
#inputTotpToken (not #inputTotp as previously assumed). Verified full
2FA round-trip: password login -> TOTP prompt -> code entry -> #/apps.

2FA is now enabled on the vp-techops Cloudron account with TOTP secret
stored in Bitwarden. This removes a hard blocker for CMMC L3 compliance.
2026-08-13 21:01:22 -05:00
TSYS Group COO f633a10f80 fix: resolve BW state sync issue -- add sync() to login lifecycle
The provisioner's BitwardenHelper.login() was missing the critical
`bw sync` step that the host wrapper includes. Without syncing after
login, the container's local vault cache was empty/stale, causing items
to vanish between container runs. Added sync() call at end of login()
and before list_items().

Also fixed container UID/GID to match host user (1002:1002) for proper
bind-mount access, and added source-code volume mounts for fast iteration.

Verified with 5-phase cross-container persistence test (create in
container A, verify in fresh container B, update in C, confirm in D).
2026-08-13 20:49:39 -05:00
TSYS Group COO f638105614 fix: credential lifecycle safety -- update_item, duplicate prevention, tests
CRITICAL FIX: The --enable-2fa flow was creating duplicate BW items
instead of updating in place, which led to ambiguous item resolution
and data integrity issues. This was a severe failure in core
credential lifecycle operations.

Changes:
- bw_helper.py: Complete rewrite with safety guarantees
  - update_item(): modifies existing item in place by ID, preserves
    all fields not being updated
  - create_item(): refuses to create duplicates (raises if item exists)
  - get_item_id(): resolves name to ID, raises on ambiguous matches
  - get_item(): returns full item JSON
  - NO delete_item method exists by design -- credential deletion
    is a manual operation only
- provision-agent.py: --enable-2fa now uses update_item() instead
  of create_item() to add TOTP to existing credentials
- Dockerfile: non-root user with correct BW state directory ownership
- docker-compose.yml: bind mount for BW state (proper permissions)
- test_bw_helper.py: 10 tests covering full lifecycle
  (create, read, duplicate rejection, update password, update TOTP,
  field preservation, no-delete verification)
  Tests 1-4 verified passing against live Vaultwarden instance.
- requirements.txt: added pytest

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-13 14:47:57 -05:00
TSYS Group COO 04ece5234a feat: non-root container, reusable SSO flow, OIDC access detection
Security:
- Container now runs as non-root user 'provision' (CMMC/STIG audit
  requirement). Root execution would fail security audits.
- BW state persisted in named volume at /home/provision/.config/
  Bitwarden CLI to avoid slow re-auth on every run.

SSO refactoring:
- cloudron_panel_login(): establishes Cloudron panel session once,
  shared across all subsequent app SSO flows
- sso_login(): clicks app-specific SSO/OAuth button, handles Cloudron
  OIDC login + consent redirect, properly detects failures
- Detects "You do not have access" OIDC rejections (access control
  issue, not a selector bug)
- Per-app SSO button selectors passed as parameters for extensibility
  to future Cloudron apps (Dolibarr, Paperless, Firefly, etc.)

Debug:
- _debug_dump() captures screenshot + DOM at failure points
- Added dumps at Gitea token, Redmine key, and SSO failure locations

Current blocker: vp-techops Cloudron user not yet granted access to
Gitea/Redmine/Discourse apps. Cloudron 2FA confirmation pending.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-13 14:01:22 -05:00
TSYS Group COO 3569a09afd fix: Cloudron selectors, docker-compose env_file, CLOUDRON_BASE URL
- Fix Cloudron invite form selectors to match real page IDs
  (#inputUsername, #inputDisplayName, #inputPassword, #inputPasswordRepeat)
- Fix docker-compose.yml: use env_file instead of ${VAR} interpolation
  (password contains $ chars that docker-compose corrupts)
- Update CLOUDRON_BASE from tsys-cloudron.knel.net to my.knownelement.com

Phase 1 Cloudron enrollment now works end-to-end: invite accepted,
password set, TOTP extracted, credential stored in Bitwarden.
Phase 2 (Gitea/Redmine/Discourse) needs selector updates for
the current UI versions of each system.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-13 13:20:43 -05:00
TSYS Group COO 8c90d6809b feat: replace Node.js bw CLI with native Rust binary + fix module name
Replace npm-based @bitwarden/cli with the pre-compiled native Rust bw
binary (v2026.7.0) to eliminate Node.js from the credential management
layer for CMMC/ITAR/STIG audit readiness.

Changes:
- Dockerfile: download native bw binary instead of npm install; add
  python3-pip for Playwright dependencies
- bw_helper.py: renamed from bw-helper.py (Python can't import hyphens);
  added BW_SERVER config for self-hosted instance; use --passwordfile
  for unlock (more reliable with native binary); removed TOTP from
  login flow (API key auth does not require it)
- provision-agent.py: pass BW_SERVER env var to BitwardenHelper
- docker-compose.yml: add BW_SERVER env var
- .env.example: add BW_SERVER, document TOTP as optional

Verified: dry-run passes, bw status/auth/generate all work inside
the provisioner container against pwvault.turnsys.com.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-13 13:00:53 -05:00
TSYS Group COO 7534964c13 fix: handle 2FA on the Bitwarden account during API login [#442]
bw login --apikey prompts for a TOTP code when 2FA is enabled on the
BW account. The previous code didn't pass one, so it would hang or
fail. Now generates a TOTP from BW_TOTP_SECRET and passes via --code.

Changes:
- BitwardenHelper.__init__ accepts totp_secret param
- login() generates a pyotp code and passes --code when secret is set
- provision-agent.py passes BW_TOTP_SECRET from environment
- docker-compose.yml and .env.example updated for the new var
- BW_PASSWORD removed from the login env (only needed for unlock via stdin)

The BW account's own TOTP secret lives in ~/.config/bw/env alongside
the other BW access info — the one exception (can't store BW's 2FA in
BW itself).

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-13 12:14:16 -05:00
mrcharles 8ce279276f feat: initial agent identity provisioning automation [#442]
Playwright-based tool for enrolling AI agent identities in Cloudron,
generating API keys via SSO (Gitea/Discourse/Redmine), and storing
all credentials in Bitwarden per-agent collections.

- provision-agent.py: main Playwright automation (Cloudron enroll,
  SSO login, API key generation, verification)
- bw-helper.py: Bitwarden CLI wrapper (password gen, item CRUD,
  TOTP, session management)
- Dockerfile: Playwright v1.52.0 + bw CLI + Python deps
- agents.yaml.example: manifest template for Q3/Q4 agents
- TSYSGroupAIOS framework adopted (hooks, rules engine, Makefile)

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-13 08:59:14 -05:00