Commit Graph
17 Commits
Author SHA1 Message Date
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 6d89f16610 feat: consolidate proven Discourse/Redmine flows into provision-agent.py
Replaced the stale session-1 provision_discourse/provision_redmine with
the flows proven on vp-techops this session. Both are now parameterized
by the agent manifest (username derived from name, hyphens stripped,
overridable via username: field).

Discourse: login modal -> OpenID button -> signup on first login ->
RSA User API key flow (PKCS1v15 decrypt, JSON payload).

Redmine: KNEL Cloud SSO button -> consent -> Show/Reset on the API
access key section via targeted DOM traversal.

Added docs/JOURNAL.md with all working selectors, flows, gotchas, and
verification results so future sessions do not rediscover them.
2026-08-14 08:23:37 -05:00
TSYS Group COO f414b0b7ff docs: update STATUS.md -- Redmine SSO+API key complete, no blockers 2026-08-13 23:41:15 -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 fcd484ff97 docs: update STATUS.md with session 2 progress
Completed: BW sync fix, Cloudron 2FA, Discourse SSO+API key, Gitea token.
Blocked: Redmine SSO needs Cloudron app access granted by admin.
2026-08-13 21:30:03 -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 c0eb1b383b feat: Gitea SSO + token generation working end-to-end
Working flows:
- Cloudron panel login (Pankow Vue UI: keyboard.type + role=button)
- Gitea SSO via Cloudron OIDC (redirects, auto-consent, authenticated)
- Gitea API token generation (JS-based form fill for hidden elements)
- Token extraction from flash-info message (regex for 40-char hex)
- Token verified via Gitea API (user=vptechops)
- Token stored in Bitwarden as "vp-techops Gitea"

Issues remaining:
- Redmine SSO: OIDC consent completes but redirects back to login page
  (likely Redmine OAuth config or user sync issue)
- Discourse: SSO button not found (needs different selector)
- 2FA: enable button not found on Cloudron profile page
  (TOTP section exists but button selector needs investigation)
- Gitea: stale token cleanup needed (old duplicate from failed runs)

Key pattern established for Cloudron SSO across all apps:
  1. cloudron_panel_login() to establish session
  2. sso_login() clicks app-specific SSO button
  3. OIDC handles auth automatically (session already active)
  4. Redirect back to app authenticated

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-13 18:37:20 -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
TSYS Group COO 9b4502f55d fix: critical bugs in agent identity provisioning [#442]
Email domain bug (would have caused all provisioning to fail):
- Cloudron email default was tsys-cloudron.knel.net (the dashboard host)
  instead of turnsys.com (the actual identity domain). Fixed in 3 places.
- Added explicit cloudron_email field to all agents in agents.yaml.example.

Other fixes:
- STATE_DIR.mkdir() moved from module level to main() so --dry-run and
  --help work outside the container.
- IndexError guard: password_inputs[0] crashes if zero fields found.
- State file save moved to finally block so partial results survive
  provisioning failures.
- Exception in provision_agent no longer re-raised (was preventing state
  file write and summary reporting).
- BW item_exists no longer swallows network/session errors as 'not found'
  (was causing duplicate credential creation).
- Redundant -u flag in bw generate (-uluns → -ulns).
- Dockerfile: npx install with || true → npm install -g (silent failure
  would cause runtime 'bw: command not found').
- Added .dockerignore to prevent .env/agents.yaml/state from entering image.

💘 Generated with Crush

Assisted-by: Crush:glm-5.2
2026-08-13 11:21:38 -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