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)