--- id: ALERT-20260909-001 type: alert status: resolved severity: high title: Plaintext admin password hash committed outside vault created: 2026-09-09 updated: 2026-09-09 resolved: 2026-09-09 tags: [alert, security, secrets] related: [NFR-002, OQ-006, TECH-SECURITY] source_paths: - roles/bootstrap/defaults/main.yml - inventory/group_vars/external.yml - README.md confidence: confirmed --- # ALERT-20260909-001 - Plaintext admin password hash committed outside vault ## Summary The SHA-512 hash of the admin password (`admin_password`) is committed in plaintext in `roles/bootstrap/defaults/main.yml` and `inventory/group_vars/external.yml`, while the root README states that `admin_password` lives encrypted in `vault.yml`. The documented secret policy (AGENTS.md, README "Beitragen") forbids plaintext secrets in `defaults/` or `group_vars/`. The same hash is reused across both locations. ## Why It Matters - A committed password hash allows offline guessing of the shared admin password; reusing one hash across hosts amplifies the impact. - It contradicts the project's own documented vault policy and misleads contributors (README claims the value is vaulted). ## Evidence - `roles/bootstrap/defaults/main.yml` lines 12-13 (`admin_password` hash) - `inventory/group_vars/external.yml` lines 10-11 (same hash) - `README.md` "Secrets & Vault" section claims `admin_password` is in `vault.yml` - AGENTS.md "Secrets": no plaintext secrets in `defaults/`/`group_vars/` ## Suggested Resolution Move `admin_password` to `vault.yml` in both locations; keep no plaintext fallback in role defaults. If a default is truly needed for bootstrap, document it explicitly as an accepted risk and set a unique hash per site. Verify whether `vault.yml` already overrides it (OQ-006). ## Resolution Resolved 2026-09-09. OQ-006 was verified first: decrypting `vault.yml` confirmed it did **not** define `admin_password` (no override existed), so the committed hash was a policy violation, not an intended fallback. Fix applied: - `admin_password` (SHA-512 hash) moved into encrypted `vault.yml`. - Plaintext hash removed from `roles/bootstrap/defaults/main.yml` and `inventory/group_vars/external.yml` (comment now points to `vault.yml`). - `bootstrap.yml` loads `vault.yml` via `vars_files` (same pattern as `playbooks/monitoring.yml`), so the bootstrap role still resolves the variable for every inventory. - Documentation updated: root `README.md` "Secrets & Vault" and `roles/bootstrap/README.md` (variable now documented as vault-sourced). - NFR-002 acceptance notes and evidence updated accordingly. Remaining recommendation (hardening, not blocking): the same hash is still shared across sites; rotating to a unique hash per site would remove the single shared credential. ## Resolution Evidence - `ansible-vault view vault.yml` (decrypted during resolution) now lists `admin_password` among `PROXMOX_USER_NAME`, `PROXMOX_USER_PASS`, `monitoring_zabbix_api_password`. - Repository-wide search for the former hash string (salt fragment) returns no matches - the hash exists only inside the encrypted vault file. - `ansible-playbook --syntax-check bootstrap.yml` passes (with `--vault-password-file .vault_pass`). - `rg -n "admin_password"` shows the variable now only in `vault.yml` (encrypted) and consumed at `roles/bootstrap/tasks/create-admin-user.yml:9`.