homelab/build/config/ansible/playbooks/k3s_ssh_key.yml
Russell Seymour 1250c9cef6
Some checks are pending
deploy / deploy (push) Waiting to run
Initial checkin of code for managing homelab
2026-08-24 20:52:36 +01:00

82 lines
3.9 KiB
YAML

---
# Materializes the SSH key the k3s plays authenticate with, from Vault
# (homelab/ci/ssh-k3s) onto the controller. Imported as the first play of both
# playbooks/k3s.yml and playbooks/k3s_maintenance.yml — a separate file rather
# than copied into each, since both target k3s_cluster and both would
# otherwise depend on a file only the other one creates.
#
# This is the same shape CI already uses for the Unraid key:
# .forgejo/workflows/deploy.yml fetches homelab/ci/ssh, writes it to disk and
# points ANSIBLE_PRIVATE_KEY_FILE at it. Vault is the one place secrets live,
# so a manual k3s run shouldn't need the key hand-loaded into ssh-agent first
# — which was the only way it worked before.
#
# hosts: k3s_cluster, not localhost, and deliberately so: the implicit
# localhost is not a member of `all`, so it doesn't inherit group_vars/all.yml
# — where vault_addr, vault_kv_mount and vault_auth_method live. Targeting the
# group picks those up along with k3s_ssh_key_vault_path/_local_path from
# group_vars/k3s_cluster.yml. Nothing here connects to a Pi (gather_facts is
# off and every task is delegated or connectionless), which is the whole
# point: at this stage the key isn't on disk yet.
#
# become: false on the delegated tasks because the k3s_cluster group sets
# ansible_become: true for the Pis (inventory/hosts.yml) — without it these
# would try to sudo on the controller.
- name: Fetch the k3s SSH deploy key from Vault
hosts: k3s_cluster
gather_facts: false
tasks:
# The escape hatch, and the reason it's a flag rather than just pointing
# ansible_ssh_private_key_file somewhere else: if Vault is unreachable,
# overriding the key path alone doesn't help — this play would still fail
# before the first real one runs. Skipping it is the only thing that lets
# a run proceed on a local key. Both overrides together:
#
# ansible-playbook playbooks/k3s.yml \
# -e k3s_ssh_key_fetch=false \
# -e ansible_ssh_private_key_file=~/.ssh/k3s_ansible
- name: Fetch and write the key
when: k3s_ssh_key_fetch | default(true) | bool
block:
# run_once because the key is per-cluster, not per-host: one Vault
# read for the whole group. Facts set by a run_once task apply to
# every host in the play, but nothing outside this play needs it.
- name: Look up the k3s SSH deploy key from Vault
ansible.builtin.set_fact:
k3s_ssh_key_secret: >-
{{ lookup('community.hashi_vault.vault_kv2_get',
k3s_ssh_key_vault_path,
engine_mount_point=vault_kv_mount,
url=vault_addr,
auth_method=vault_auth_method,
role_id=vault_role_id | default(omit),
secret_id=vault_secret_id | default(omit)).secret }}
run_once: true
no_log: true
- name: Ensure the local key directory exists
ansible.builtin.file:
path: "{{ k3s_ssh_key_local_path | dirname }}"
state: directory
mode: "0700"
delegate_to: localhost
become: false
run_once: true
# trim + an explicit trailing newline: OpenSSH rejects a key file
# whose final line isn't terminated, and `vault kv put
# PRIVATE_KEY=@file` is as likely to have stored one with trailing
# whitespace as not. 0600 for the same reason — ssh refuses a key file
# that's group- or world-readable. Both fail at connection time rather
# than here, which is a much less obvious error to read.
- name: Write the private key to the controller
ansible.builtin.copy:
dest: "{{ k3s_ssh_key_local_path }}"
content: "{{ k3s_ssh_key_secret.PRIVATE_KEY | trim }}\n"
mode: "0600"
delegate_to: localhost
become: false
run_once: true
no_log: true