From 4e2ca88bd46fca38d7120f85027f6f7aa2aa2495 Mon Sep 17 00:00:00 2001 From: Keith Jackson Date: Sun, 16 Aug 2026 15:04:44 -0500 Subject: [PATCH] Wire ansible-vault for host become password (unattended host-prereqs) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Where the sudo password goes, codified: - inventory: new [hypervisor] group (localhost, connection=local). - host-prereqs.yml targets 'hypervisor'; become password from vaulted group_vars/hypervisor/vault.yml (ansible_become_password) — user creates it encrypted (safe to commit); README documents the exact commands. - ansible.cfg vault_password_file = ~/.config/home-k3s/ansible-vault-pass (passphrase kept OUTSIDE the repo, never committed). - ci/yamllint.yaml ignores **/vault.yml (vault blob isn't parseable YAML). Nodes keep NOPASSWD sudo (group_vars/all) — vault scoped to the host only. Co-Authored-By: Claude Opus 4.8 (1M context) --- ci/yamllint.yaml | 4 ++ infra/ansible/ansible.cfg | 4 ++ .../inventory/group_vars/hypervisor/README.md | 37 +++++++++++++++++++ infra/ansible/inventory/hosts.ini | 5 +++ infra/ansible/playbooks/host-prereqs.yml | 5 +-- 5 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 infra/ansible/inventory/group_vars/hypervisor/README.md diff --git a/ci/yamllint.yaml b/ci/yamllint.yaml index 5b9d58f..f97d132 100644 --- a/ci/yamllint.yaml +++ b/ci/yamllint.yaml @@ -7,6 +7,10 @@ yaml-files: - "*.yaml" - "*.yml" +# ansible-vault-encrypted files are a `$ANSIBLE_VAULT;...` blob, not parseable YAML — skip them. +ignore: | + **/vault.yml + rules: # Kubernetes / Kustomize / Helm manifests routinely exceed 80 cols; allow room # but keep a sane ceiling so lines stay reviewable. diff --git a/infra/ansible/ansible.cfg b/infra/ansible/ansible.cfg index d23f4ce..60fc2a3 100644 --- a/infra/ansible/ansible.cfg +++ b/infra/ansible/ansible.cfg @@ -7,6 +7,10 @@ retry_files_enabled = False stdout_callback = default result_format = yaml interpreter_python = auto_silent +# Vault passphrase file — kept OUTSIDE the repo (never committed). Decrypts the vaulted +# group_vars (e.g. the host become password) so plays run unattended. Create it once: +# install -m600 /dev/stdin ~/.config/home-k3s/ansible-vault-pass <<<'YOUR_VAULT_PASSPHRASE' +vault_password_file = ~/.config/home-k3s/ansible-vault-pass [ssh_connection] pipelining = True diff --git a/infra/ansible/inventory/group_vars/hypervisor/README.md b/infra/ansible/inventory/group_vars/hypervisor/README.md new file mode 100644 index 0000000..201763e --- /dev/null +++ b/infra/ansible/inventory/group_vars/hypervisor/README.md @@ -0,0 +1,37 @@ +# group_vars for the `hypervisor` host (the workstation) + +The host-side plays (`playbooks/host-prereqs.yml`) run against the `hypervisor` group and need +**become (sudo) on the host**. Supply the password as an **ansible-vault-encrypted** var here — it is +encrypted, so it is safe to commit; the passphrase that decrypts it stays out of the repo. + +## Two files (one committed-encrypted, one never committed) + +1. **The vault passphrase** — a file **outside the repo**, referenced by `ansible.cfg` + (`vault_password_file = ~/.config/home-k3s/ansible-vault-pass`). Create it once: + ```bash + mkdir -p ~/.config/home-k3s + install -m600 /dev/stdin ~/.config/home-k3s/ansible-vault-pass <<<'YOUR_VAULT_PASSPHRASE' + ``` + +2. **The encrypted become password** — `vault.yml` in **this directory** (committed, encrypted): + ```bash + cd infra/ansible + ansible-vault create inventory/group_vars/hypervisor/vault.yml + # file content (single line): + # ansible_become_password: "YOUR_HOST_SUDO_PASSWORD" + ``` + (`ansible-vault create` uses the passphrase file above automatically — no prompt.) + +Then a rebuild is unattended: +```bash +cd infra/ansible && ansible-playbook playbooks/host-prereqs.yml +``` + +## Notes +- `vault.yml` is skipped by the yamllint gate (`ci/yamllint.yaml` ignores `**/vault.yml`) because a + vault blob is not parseable YAML. +- The k3s **nodes** use passwordless sudo (`group_vars/all.yml`), so they need no become password — + this vault is scoped to the `hypervisor` host only. +- If your host already has passwordless sudo, you can skip the vault and just run the playbook. +- This is the Ansible-bootstrap-layer secret (host credential), distinct from the platform's + in-cluster **SOPS + age** secrets (ADR-0007) — different layer, different tool. diff --git a/infra/ansible/inventory/hosts.ini b/infra/ansible/inventory/hosts.ini index b1c94e6..915c37d 100644 --- a/infra/ansible/inventory/hosts.ini +++ b/infra/ansible/inventory/hosts.ini @@ -15,3 +15,8 @@ k3s-dev-3 ansible_host=10.73.0.13 [k3s_servers:children] k3s_primary k3s_secondary + +# The hypervisor/workstation host itself (target of host-prereqs.yml). Its become +# (sudo) password comes from the vaulted group_vars/hypervisor/vault.yml. +[hypervisor] +localhost ansible_connection=local diff --git a/infra/ansible/playbooks/host-prereqs.yml b/infra/ansible/playbooks/host-prereqs.yml index 7e54485..fb8caa3 100644 --- a/infra/ansible/playbooks/host-prereqs.yml +++ b/infra/ansible/playbooks/host-prereqs.yml @@ -9,9 +9,8 @@ # Idempotent: safe to re-run; only changes what isn't already in the desired state. Uses # firewall-cmd directly (no extra Ansible collection dependency). - name: Configure host prerequisites for the local dev cluster - hosts: localhost - connection: local - become: true + hosts: hypervisor # localhost (connection=local set in inventory) + become: true # ansible_become_password from vaulted group_vars/hypervisor/vault.yml gather_facts: false vars: # The bootstrap Gitea (bootstrap/gitea/) runs on the host; the in-cluster Flux must reach it