#!/usr/bin/env bash # # decrypt-interactiv.sh # # Zweck: # - Entschluesselt entweder: # (A) eine komplett mit ansible-vault verschluesselte Datei (ANSIBLE_VAULT Header) # (B) einzelne YAML "!vault |" Bloecke in einer Datei (z.B. group_vars/host_vars) # (C) einen einzelnen verschluesselten String (Argument, Pipe oder interaktiv) # # Passwort wird einmalig interaktiv abgefragt. # set -euo pipefail # Temporare Datei fuer das einmalig abgefragte Vault-Passwort. VAULT_PASS_FILE="" cleanup() { # Sicherheitsnetz: Temp-Datei immer entfernen, auch bei Fehler/Abbruch. if [[ -n "${VAULT_PASS_FILE}" && -f "${VAULT_PASS_FILE}" ]]; then rm -f "${VAULT_PASS_FILE}" fi } ask_vault_password_once() { # Passwort nur einmal pro Lauf abfragen und danach wiederverwenden. if [[ -n "${VAULT_PASS_FILE}" && -f "${VAULT_PASS_FILE}" ]]; then return 0 fi if [[ ! -r /dev/tty ]]; then echo "Error: No interactive TTY available for password prompt." >&2 return 1 fi local pw1="" # Wichtig: Passwort direkt vom Terminal lesen, nicht von stdin. read -rsp "Vault password: " pw1 < /dev/tty printf '\n' > /dev/tty if [[ -z "${pw1}" ]]; then echo "Error: Empty password is not allowed." >&2 return 1 fi # Passwort in Datei speichern, damit ansible-vault es nicht interaktiv erwartet. VAULT_PASS_FILE="$(mktemp)" chmod 600 "${VAULT_PASS_FILE}" printf '%s' "${pw1}" > "${VAULT_PASS_FILE}" unset pw1 } decrypt_with_prompted_password() { # Alle decrypt-Aufrufe laufen zentral ueber diese Funktion. ask_vault_password_once ansible-vault decrypt --vault-password-file "${VAULT_PASS_FILE}" "$@" 2>/dev/null } ######################################## # Hilfe anzeigen ######################################## show_help() { cat </dev/null 2>&1; then echo "Error: ansible-vault not found in PATH." >&2 return 1 fi ######################################## # Wenn ein Argument uebergeben wurde ######################################## if [[ -n "${1:-}" ]]; then # --- Fall 1: Argument ist eine Datei --- if [[ -f "$1" ]]; then if [[ "$(head -n1 "$1")" == "\$ANSIBLE_VAULT;1.1;AES256" ]]; then decrypt_with_prompted_password "$1" return 0 fi printf 'Reading vault values from file...\n\n' local parsing=0 local result="" local name="" local blue local discard blue="$(tput setaf 4 || true)" discard="$(tput sgr0 || true)" # Best-effort Parsing: Datei wird in Tokens zerlegt und !vault-Bloecke # werden gesammelt, dann rekursiv als Stringinput entschluesselt. while IFS= read -r token; do if [[ "$(echo "$token" | grep -c "\!vault")" -gt 0 ]] && [[ $parsing -eq 0 ]]; then parsing=1 elif [[ $parsing -eq 1 ]] && [[ "$(echo "$token" | grep -c ":")" -eq 0 ]]; then result="$(printf "%s\n%s" "$result" "$token")" else if [[ -n "$result" ]]; then printf "\n\n%s%s%s\n" "$blue" "$name" "$discard" printf "%s" "$result" | vdecr name="" result="" parsing=0 fi fi if [[ "$(echo "$token" | grep -c ":")" -eq 1 ]]; then name="$token" fi done < <(tr -s '[:space:]' '\n' < "$1") return 0 fi # --- Fall 3: Argument ist ein String --- local str="$1" # --- Fall 4: String kommt per Pipe (stdin ist kein TTY) --- elif [[ ! -t 0 ]]; then local str str="$(cat)" # --- Fall 5: Interaktiv --- else printf 'Interactive mode. Paste encrypted string and press Ctrl-D two times to confirm.\n' local str str="$(cat)" printf '\n' fi ######################################## # String entschluesseln ######################################## # YAML-Deko entfernen und den Ciphertext in ein Format bringen, # das ansible-vault decrypt direkt verarbeiten kann. printf -- "%s" "$str" | \ sed 's/ /\n/g' | \ sed '/---\|^.*:\|\!vault\||\|^$/d' | \ decrypt_with_prompted_password printf '\n' } # Aufraeumen erfolgt automatisch beim Verlassen des Skripts. trap cleanup EXIT ######################################## # Main ######################################## if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then show_help exit 0 fi vdecr "${1:-}"