204 lines
5.1 KiB
Bash
Executable File
204 lines
5.1 KiB
Bash
Executable File
#!/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 <<EOF
|
|
Usage:
|
|
$(basename "$0") [OPTION] [INPUT]
|
|
|
|
Decrypt modes:
|
|
|
|
1) Full vault-encrypted file (ANSIBLE_VAULT header):
|
|
$(basename "$0") secrets.vault
|
|
|
|
2) YAML file containing one or multiple "!vault |" blocks:
|
|
$(basename "$0") group_vars/all.yml
|
|
|
|
3) Encrypted vault string:
|
|
$(basename "$0") 'secret: !vault | \$ANSIBLE_VAULT;1.1;AES256 ...'
|
|
echo 'secret: !vault | ...' | $(basename "$0")
|
|
|
|
4) Interactive mode (paste, then Ctrl-D):
|
|
$(basename "$0")
|
|
|
|
Options:
|
|
-h, --help Show this help and exit
|
|
|
|
Notes:
|
|
- This script prints decrypted values to stdout.
|
|
- Vault password is prompted once per script run.
|
|
EOF
|
|
}
|
|
|
|
########################################
|
|
# Hauptfunktion
|
|
########################################
|
|
vdecr() {
|
|
unset IFS
|
|
|
|
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
|
show_help
|
|
return 0
|
|
fi
|
|
|
|
if ! command -v ansible-vault >/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:-}"
|