From ac819fda4113c8ced9ce21491007f30258d7ea9a Mon Sep 17 00:00:00 2001 From: Derek Date: Thu, 27 Aug 2026 17:18:01 +1000 Subject: [PATCH] fix: stop inventing an Arcane password and keep the seeded login The role minted its own admin password, so the credentials on a provisioned box matched neither upstream's documentation nor anything a developer would guess. The login is now exactly what Arcane ships with, and the role sets neither the username nor the password. What it still does is retire the forced first-login prompt, which is the part worth automating. That needs a password submitted, because only a COMPLETED change clears the flag -- an admin-side user update sets the password and leaves it standing. So the change submits the seeded password as both the old and the new value: the flag drops and the credentials do not move. Upstream's default `strong` policy rejects its own seeded password (twelve characters, but no uppercase and no digit), so the policy is relaxed to `basic` first. Eight characters remains the floor regardless -- the request schema rejects anything shorter with a 422 before the policy is read, which is why a short password is not reachable by configuration at all. The admin is now located by its global-admin flag rather than by the name `arcane`, so a host whose admin was renamed is still found. soe_arcane_admin_password is gone. The one knob left is soe_arcane_password_policy, for putting the character-class requirements back. Verified by destroying the container, its volume and the stack directory on three hosts and installing clean. Each came up with username `arcane`, failed=0 and rescued=0, and a real login as arcane/arcane-admin returns 200 with requiresPasswordChange false from /api/auth/me -- the endpoint the UI itself calls, and the flag its root layout gates the change dialog on. --- README.md | 2 +- ansible/roles/soe/defaults/main.yml | 22 ++++++++------------- ansible/roles/soe/tasks/arcane.yml | 30 +++++++++++++++++------------ 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 8d9c6bd..8a2fb9b 100644 --- a/README.md +++ b/README.md @@ -175,7 +175,7 @@ reports success -- the settings simply land where nobody sees them. - `contributor`: hyperi-ci and the tools its checks drive (semgrep, alint), gitleaks, trivy, hadolint, pip-audit, ansible-lint, pre-commit, act - `soe` / `soe-gui`: HyperI org policy: VPN clients, Claude Code, Slack, LibreOffice, RDP client, telemetry-disable, auto-updates, GNOME taskbar - `power-profile` (off by default, and deliberately not in `soe`): sleep, idle and lid policy, selected per machine. `always-on` (the default profile) never idle-suspends on mains power and does not sleep when the lid shuts -- for a repurposed laptop doing build work, or a desktop that has to answer ssh. `vm` never sleeps or suspends at all, for an unattended RDP guest that nobody can walk over and wake. Battery behaviour stays stock under `always-on`, because a machine that will not sleep in a bag cooks itself. Profiles are data files, so adding one is adding a file -- see [roles/power-profile/README.md](ansible/roles/power-profile/README.md) -- `arcane` (off by default): [Arcane](https://getarcane.app), a web UI for the containers on the box. Enable it with `-e soe_arcane_enabled=true` and you get a daemon on `http://localhost:3552` that comes back after a reboot and keeps itself updated. Works against docker-ce on Linux and colima on macOS. Bound to loopback because it holds the Docker socket, so whatever reaches that port owns the machine. Login is `arcane` / `Arcane-Admin1!` (set `soe_arcane_admin_password` to change it -- Arcane requires at least 12 characters with an uppercase letter, a lowercase letter, a number and a symbol). Upstream forces a password change on first login; the role performs that change itself right after deploying, so you never meet the dialog. There is still a login -- auto-login is compiled out of every published image, so zero-auth is not available without building your own +- `arcane` (off by default): [Arcane](https://getarcane.app), a web UI for the containers on the box. Enable it with `-e soe_arcane_enabled=true` and you get a daemon on `http://localhost:3552` that comes back after a reboot and keeps itself updated. Works against docker-ce on Linux and colima on macOS. Bound to loopback because it holds the Docker socket, so whatever reaches that port owns the machine. Login is whatever Arcane seeds -- `arcane` / `arcane-admin` as upstream documents it. The role sets neither, and only clears the forced first-login password prompt, which it does by re-submitting that seeded password so the credentials stay unchanged. That needs the password policy relaxed to `basic` (`soe_arcane_password_policy`), because upstream's default `strong` policy rejects its own seeded password. There is still a login -- auto-login sits behind a `buildables` Go build tag that no published image is compiled with, so zero-auth is not available without building your own image - `local-services` (off by default): a persistent local ClickHouse and Redpanda for ad-hoc work -- somewhere to poke at a query or hand-feed a topic without waiting for a suite to build. Enable with `-e soe_local_services_enabled=true`. Deployed **stopped**: `restart: no`, so a reboot leaves them down and they cost nothing until `local-services up`, which pulls latest and takes seconds. Both capped at 1GB and bound to loopback. They are spike instances -- integration and e2e suites create and tear down their own containers, because a shared daemon makes a suite non-hermetic and order-dependent **Desktop UI** (`winlike` or `maclike` tag): GNOME extensions, a transparent taskbar (winlike) or a dock (maclike). diff --git a/ansible/roles/soe/defaults/main.yml b/ansible/roles/soe/defaults/main.yml index 1235a53..6e0d9d1 100644 --- a/ansible/roles/soe/defaults/main.yml +++ b/ansible/roles/soe/defaults/main.yml @@ -39,21 +39,15 @@ soe_arcane_auto_update_exclude: # abbreviation. soe_arcane_timezone: UTC -# Admin password, set once while the seeded credential is still known. +# The login is whatever Arcane seeds -- this role sets neither the username nor +# the password, so upstream's documented default is what you sign in with. # -# Arcane seeds `arcane` / `arcane-admin` with a forced password change on first -# login. The role performs that change itself straight after the first deploy, -# which clears the flag, so nobody meets the dialog. -# -# Upstream's own seeded value cannot be reused here: the API enforces at least -# 12 characters with an uppercase letter, a lowercase letter, a number and a -# symbol, and rejects anything weaker with a 400. This is the seeded name in a -# form that policy accepts. -# -# A known default rather than a generated secret: this is a loopback-only UI, -# and a password the developer already knows beats one they have to go and look -# up. Override it in group_vars on any machine where that trade does not hold. -soe_arcane_admin_password: Arcane-Admin1! +# Upstream's default policy is `strong`: 12 characters with an uppercase letter, +# a number and a symbol, which its OWN seeded password fails. Retiring the +# forced first-login prompt means re-submitting that seeded password, so the +# policy has to accept it. Eight characters remains the floor whatever this +# says -- the request schema rejects anything shorter before the policy is read. +soe_arcane_password_policy: basic # ============================================================================ # Local services -- persistent ClickHouse + Redpanda for ad-hoc work, OPT-IN. diff --git a/ansible/roles/soe/tasks/arcane.yml b/ansible/roles/soe/tasks/arcane.yml index adb6b29..6c9cbb2 100644 --- a/ansible/roles/soe/tasks/arcane.yml +++ b/ansible/roles/soe/tasks/arcane.yml @@ -176,34 +176,41 @@ no_log: true when: not ansible_check_mode + # Selected by the global-admin flag rather than by name, so a host whose + # admin has since been renamed is still found. - name: Work out whether Arcane still wants a password change # noqa: var-naming[no-role-prefix] -- soe_ IS the role prefix here ansible.builtin.set_fact: soe_arcane_admin: >- {{ (soe_arcane_users.json.data | default([]) - | selectattr('username', 'equalto', 'arcane') + | selectattr('isGlobalAdmin', 'defined') + | selectattr('isGlobalAdmin') | list | first) | default({}) }} when: not ansible_check_mode - # No currentPassword needed on the admin path, so this works whatever the - # password happens to be now. - - name: Set the Arcane admin password + # Must precede the change below: the policy is read at validation time, and + # the default `strong` rejects Arcane's own seeded password. + - name: Relax the Arcane password policy ansible.builtin.uri: - url: "http://127.0.0.1:{{ soe_arcane_port }}/api/users/{{ soe_arcane_admin.id }}" + url: "http://127.0.0.1:{{ soe_arcane_port }}/api/environments/0/settings" method: PUT headers: X-Api-Key: "{{ soe_arcane_admin_api_key }}" body_format: json body: - password: "{{ soe_arcane_admin_password }}" + authPasswordPolicy: "{{ soe_arcane_password_policy }}" status_code: [200] - register: soe_arcane_pwset - changed_when: soe_arcane_pwset.status == 200 - no_log: true + register: soe_arcane_policy + changed_when: soe_arcane_policy.status == 200 when: - not ansible_check_mode - soe_arcane_admin.requiresPasswordChange | default(false) + # Changing the seeded password to itself: the flag clears, and the login + # stays the one upstream documents. `arcane-admin` is upstream's seed, not a + # value this role chooses -- it is only ever submitted while the + # forced-change flag is still up, which is before anyone could have changed + # it. - name: Retire the forced password change ansible.builtin.uri: url: "http://127.0.0.1:{{ soe_arcane_port }}/api/auth/password" @@ -212,12 +219,11 @@ X-Api-Key: "{{ soe_arcane_admin_api_key }}" body_format: json body: - currentPassword: "{{ soe_arcane_admin_password }}" - newPassword: "{{ soe_arcane_admin_password }}" + currentPassword: arcane-admin + newPassword: arcane-admin status_code: [200] register: soe_arcane_pwchange changed_when: soe_arcane_pwchange.status == 200 - no_log: true when: - not ansible_check_mode - soe_arcane_admin.requiresPasswordChange | default(false)