#!/usr/bin/env bash # # encrypt-interactiv.sh # # Zweck: # - Verschluesselt entweder: # (A) ein komplettes File mit ansible-vault encrypt # (B) YAML key: value Zeilen als key: !vault | Block # (C) einen einzelnen String (Argument, Pipe oder interaktiv) # # Passwort wird einmalig interaktiv abgefragt. # set -euo pipefail # Temp-Datei fuer das einmalig abgefragte Passwort. VAULT_PASS_FILE="" # Vault-ID fuer Encrypt-Operationen (bei Bedarf per ENV ueberschreibbar). VAULT_ID_LABEL="${VAULT_ID_LABEL:-default}" cleanup() { # 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 Skriptlauf abfragen. 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="" local pw2="" # Wichtig: Passwort immer direkt vom Terminal lesen, # nicht von stdin (stdin kann String/Pipe-Daten enthalten). read -rsp "Vault password: " pw1 < /dev/tty printf '\n' > /dev/tty read -rsp "Confirm password: " pw2 < /dev/tty printf '\n' > /dev/tty if [[ -z "${pw1}" ]]; then echo "Error: Empty password is not allowed." >&2 return 1 fi if [[ "${pw1}" != "${pw2}" ]]; then echo "Error: Password confirmation does not match." >&2 return 1 fi VAULT_PASS_FILE="$(mktemp)" chmod 600 "${VAULT_PASS_FILE}" printf '%s' "${pw1}" > "${VAULT_PASS_FILE}" unset pw1 pw2 } encrypt_with_prompted_password() { ask_vault_password_once ansible-vault encrypt \ --encrypt-vault-id "${VAULT_ID_LABEL}" \ --vault-password-file "${VAULT_PASS_FILE}" \ "$@" } encrypt_string_with_prompted_password() { ask_vault_password_once ansible-vault encrypt_string \ --encrypt-vault-id "${VAULT_ID_LABEL}" \ --vault-password-file "${VAULT_PASS_FILE}" \ "$@" } ######################################## # Hilfe anzeigen ######################################## show_help() { cat < !vault block): $(basename "$0") vars.yml $(basename "$0") -o output.yml vars.yml 3) String encrypt: $(basename "$0") 'mySecret' echo 'mySecret' | $(basename "$0") $(basename "$0") (interactive mode) Options: -o FILE Write YAML output to FILE instead of stdout -h, --help Show this help and exit Notes: - Simple "key: value" YAML lines will be converted. - Already encrypted (!vault) entries are preserved. - Full vault-encrypted files (ANSIBLE_VAULT header) are detected. - Vault password is prompted once per script run. - Vault ID defaults to "default" and can be changed via VAULT_ID_LABEL. EOF } ######################################## # Hauptfunktion ######################################## vencr() { 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 ######################################## # Optionales Output-File (-o) ######################################## local out_file="" if [[ "${1:-}" == "-o" && -n "${2:-}" ]]; then out_file="$2" shift 2 fi ######################################## # 1) File als Argument ######################################## if [[ -n "${1:-}" && -f "$1" ]]; then local f="$1" if [[ "$(head -n1 "$f")" == "\$ANSIBLE_VAULT;1.1;AES256"* ]]; then echo "File already encrypted (ANSIBLE_VAULT header found): $f" >&2 return 0 fi if grep -Eq '^[[:space:]]*[A-Za-z0-9_.-]+:[[:space:]]*[^#].*$' "$f"; then local tmpout tmpout="$(mktemp)" while IFS= read -r line || [[ -n "$line" ]]; do if [[ -z "$line" || "$line" =~ ^[[:space:]]*# ]]; then printf '%s\n' "$line" >> "$tmpout" continue fi if echo "$line" | grep -q '\!vault'; then printf '%s\n' "$line" >> "$tmpout" continue fi if [[ "$line" =~ ^([[:space:]]*)([A-Za-z0-9_.-]+):[[:space:]]*(.+)$ ]]; then local indent="${BASH_REMATCH[1]}" local key="${BASH_REMATCH[2]}" local value="${BASH_REMATCH[3]}" if [[ "$value" == "|" || "$value" == ">" || "$value" == "" ]]; then printf '%s\n' "$line" >> "$tmpout" continue fi value="${value%\"}"; value="${value#\"}" value="${value%\'}"; value="${value#\'}" # Aus key: value wird ein key: !vault |-Block. while IFS= read -r enc_line; do printf '%s%s\n' "$indent" "$enc_line" >> "$tmpout" done < <( printf '%s' "$value" | encrypt_string_with_prompted_password --stdin-name "$key" ) else printf '%s\n' "$line" >> "$tmpout" fi done < "$f" if [[ -n "$out_file" ]]; then mv "$tmpout" "$out_file" echo "Encrypted YAML written to: $out_file" >&2 else cat "$tmpout" rm -f "$tmpout" fi return 0 fi encrypt_with_prompted_password "$f" echo "Encrypted file in-place: $f" >&2 return 0 fi ######################################## # 2) String-Verschluesselung ######################################## local str="" local name="secret" if [[ -n "${1:-}" ]]; then str="$1" elif [[ ! -t 0 ]]; then str="$(cat)" else echo "Interactive mode." read -r -p "Variable name (default: secret): " name_in if [[ -n "$name_in" ]]; then name="$name_in" fi echo "Paste plaintext and press Ctrl-D to confirm:" str="$(cat)" echo fi printf '%s' "$str" | encrypt_string_with_prompted_password --stdin-name "$name" } trap cleanup EXIT ######################################## # Script starten ######################################## vencr "$@"