From 8865ab9d18c4eed8cdbb50dbd424950f68673400 Mon Sep 17 00:00:00 2001 From: juvdiaz Date: Thu, 2 Jul 2026 12:42:46 -0600 Subject: [PATCH] Adding haproxy pass as secret managed in pipeline --- docs/jeannie.1.md | 10 +++- jeannie | 113 ++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 111 insertions(+), 12 deletions(-) diff --git a/docs/jeannie.1.md b/docs/jeannie.1.md index a012b29..6f17efc 100644 --- a/docs/jeannie.1.md +++ b/docs/jeannie.1.md @@ -595,8 +595,14 @@ Raspberry Pi when Docker is missing. Default is to fail. missing. Default is to fail. `TF_VAR_haproxy_stats_password` -: Required HAProxy stats password for the edge stack. It must be set explicitly -and must not use the old default. +: HAProxy stats password for the edge stack. If unset, Jeannie loads it from +`LAB_EDGE_HAPROXY_STATS_CREDENTIALS_FILE`, recovers it from the live edge host's +rendered HAProxy config when available, or generates and stores a new value. +It must not use the old default. + +`LAB_EDGE_HAPROXY_STATS_CREDENTIALS_FILE` +: Local Debian-host credentials file for the edge HAProxy stats password. +Defaults to `~/.config/homelab/edge-haproxy.env`. `LAB_CONFIRM_NUKE=homelab` : Required confirmation for direct `./jeannie nuke`. diff --git a/jeannie b/jeannie index fbdd88f..1b4ea9a 100755 --- a/jeannie +++ b/jeannie @@ -481,6 +481,9 @@ run_tofu_stack() { if [[ "${stack}" == "bootstrap/cluster" ]]; then ensure_cluster_worker_var_file fi + if [[ "${stack}" == "bootstrap/edge" ]]; then + ensure_edge_haproxy_stats_password + fi if truthy "${auto_approve}"; then apply_args+=("-auto-approve") @@ -4916,19 +4919,109 @@ apps() { edge_apply() { require_debian_server "edge" - if [[ -z "${TF_VAR_haproxy_stats_password:-}" ]] || - ((${#TF_VAR_haproxy_stats_password} < 12)) || - [[ "${TF_VAR_haproxy_stats_password}" == "adminpassword" ]]; then - cat >&2 <<'EOF' -TF_VAR_haproxy_stats_password must be set to a non-default value with at least 12 characters before applying the edge stack. -Example: - export TF_VAR_haproxy_stats_password='' - ./jeannie edge -EOF + run_tofu_stack "bootstrap/edge" +} + +edge_haproxy_stats_password_valid() { + local password="${1:-}" + + [[ -n "${password}" ]] && ((${#password} >= 12)) && [[ "${password}" != "adminpassword" ]] +} + +edge_haproxy_stats_credentials_file() { + printf '%s\n' "${LAB_EDGE_HAPROXY_STATS_CREDENTIALS_FILE:-${HOME}/.config/homelab/edge-haproxy.env}" +} + +generate_edge_haproxy_stats_password() { + if command -v openssl >/dev/null 2>&1; then + openssl rand -hex 24 + elif command -v python3 >/dev/null 2>&1; then + python3 - <<'PY' +import secrets + +print(secrets.token_hex(24)) +PY + else + echo "openssl or python3 is required to generate an edge HAProxy stats password." >&2 exit 1 fi +} - run_tofu_stack "bootstrap/edge" +write_edge_haproxy_stats_credentials() { + local credentials_file="$1" + local password="$2" + local credentials_dir + + credentials_dir="$(dirname "${credentials_file}")" + mkdir -p "${credentials_dir}" + chmod 0700 "${credentials_dir}" + { + printf 'TF_VAR_haproxy_stats_password=' + printf '%q' "${password}" + printf '\n' + } > "${credentials_file}" + chmod 0600 "${credentials_file}" +} + +recover_edge_haproxy_stats_password() { + local edge_host="${TF_VAR_edge_host:-${LAB_EDGE_HOST:-}}" + local edge_user="${TF_VAR_edge_user:-${LAB_EDGE_USER:-ubuntu}}" + local edge_key="${TF_VAR_edge_ssh_key_path:-${LAB_EDGE_SSH_KEY_PATH:-/home/jv/.ssh/id_ed25519}}" + local edge_install_dir="${TF_VAR_edge_install_dir:-${LAB_EDGE_INSTALL_DIR:-/opt/homelab-edge}}" + local remote_auth + local remote_password + + if [[ -z "${edge_host}" || "${edge_install_dir}" == *"'"* ]]; then + return 1 + fi + + remote_auth="$(ssh -i "${edge_key}" -o BatchMode=yes -o ConnectTimeout=10 -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new "${edge_user}@${edge_host}" \ + "sudo sed -n 's/^[[:space:]]*stats auth //p' '${edge_install_dir}/config_files/haproxy.cfg' | head -n 1" 2>/dev/null || true)" + remote_auth="${remote_auth//$'\r'/}" + if [[ "${remote_auth}" != *:* ]]; then + return 1 + fi + + remote_password="${remote_auth#*:}" + if ! edge_haproxy_stats_password_valid "${remote_password}"; then + return 1 + fi + + printf '%s\n' "${remote_password}" +} + +ensure_edge_haproxy_stats_password() { + local credentials_file + local recovered_password + + if edge_haproxy_stats_password_valid "${TF_VAR_haproxy_stats_password:-}"; then + export TF_VAR_haproxy_stats_password + return 0 + fi + + credentials_file="$(edge_haproxy_stats_credentials_file)" + if [[ -r "${credentials_file}" ]]; then + # shellcheck disable=SC1090 + source "${credentials_file}" + if edge_haproxy_stats_password_valid "${TF_VAR_haproxy_stats_password:-}"; then + export TF_VAR_haproxy_stats_password + return 0 + fi + echo "Ignoring invalid edge HAProxy stats credentials in ${credentials_file}." >&2 + fi + + if recovered_password="$(recover_edge_haproxy_stats_password)"; then + TF_VAR_haproxy_stats_password="${recovered_password}" + export TF_VAR_haproxy_stats_password + write_edge_haproxy_stats_credentials "${credentials_file}" "${TF_VAR_haproxy_stats_password}" + echo "Recovered edge HAProxy stats password from the live edge host and stored it at ${credentials_file}." + return 0 + fi + + TF_VAR_haproxy_stats_password="$(generate_edge_haproxy_stats_password)" + export TF_VAR_haproxy_stats_password + write_edge_haproxy_stats_credentials "${credentials_file}" "${TF_VAR_haproxy_stats_password}" + echo "Generated edge HAProxy stats password at ${credentials_file}." } ensure_cluster_worker_var_file() {