diff --git a/decrypt-interactiv.sh b/decrypt-interactiv.sh new file mode 100755 index 0000000..f545631 --- /dev/null +++ b/decrypt-interactiv.sh @@ -0,0 +1,203 @@ +#!/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:-}" diff --git a/decrypt-vault-strings-from-file.sh b/decrypt-vault-strings-from-file.sh new file mode 100755 index 0000000..4ea9f67 --- /dev/null +++ b/decrypt-vault-strings-from-file.sh @@ -0,0 +1,184 @@ +#!/usr/bin/env bash +# +# decrypt-vault-strings-from-file.sh +# +# Zweck: +# - Entschlüsselt entweder: +# (A) eine komplett mit ansible-vault verschlüsselte Datei (ANSIBLE_VAULT Header) +# (B) einzelne YAML "!vault |" Blöcke in einer Datei (z.B. group_vars/host_vars) +# (C) einen einzelnen verschlüsselten String (Argument, Pipe oder interaktiv) +# +# Erwartet eine Vault-Passwortdatei unter: ~/.vault-pass +# + +set -euo pipefail + +######################################## +# 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 übergeben wurde + ######################################## + if [[ -n "${1:-}" ]]; then + + # --- Fall 1: Argument ist eine Datei --- + if [[ -f "$1" ]]; then + + # Wenn Datei eine "komplett verschlüsselte" Vault-Datei ist + # (Header ist die erste Zeile) + if [[ "$(head -n1 "$1")" == "\$ANSIBLE_VAULT;1.1;AES256" ]]; then + # Vollständige Datei entschlüsseln und ausgeben + ansible-vault decrypt "$1" 2>/dev/null + echo "Decrypted file: $1" >&2 + return 0 + fi + + # --- Fall 2: Datei enthält einzelne "!vault" Blöcke (z.B. YAML) --- + printf 'Reading vault values from file...\n\n' + + # parsing = 0 -> wir sind nicht im Vault-Block + # parsing = 1 -> wir sammeln gerade Vault-Block-Zeilen + local parsing=0 + local result="" + local name="" + + # Farbausgabe (blau) für den jeweiligen Key/Name + local blue + local discard + blue="$(tput setaf 4 || true)" + discard="$(tput sgr0 || true)" + + # Originalscript hat "for line in \$(cat file)" verwendet, + # was nach Whitespace tokenisiert. Um das Verhalten kontrollierter + # beizubehalten, tokenisieren wir hier ebenfalls nach Whitespace: + # + # - Das hilft, wenn Vault-Blöcke eingerückt sind oder YAML Zeichen enthält, + # weil später ohnehin per sed weiter "normalisiert" wird. + # + # WICHTIG: Das ist nicht "echtes" YAML-Parsing, sondern best-effort. + while IFS= read -r token; do + + # Start eines vault blocks erkennen (Token enthält "!vault") + if [[ "$(echo "$token" | grep -c "\!vault")" -gt 0 ]] && [[ $parsing -eq 0 ]]; then + parsing=1 + + # Im Vault-Block: Token ohne ":" werden gesammelt (Ciphertext-Zeilen) + elif [[ $parsing -eq 1 ]] && [[ "$(echo "$token" | grep -c ":")" -eq 0 ]]; then + result="$(printf "%s\n%s" "$result" "$token")" + + # Sonst: Blockende / neuer Bereich + else + # Wenn wir einen gesammelten Block haben -> ausgeben und decrypten + if [[ -n "$result" ]]; then + printf "\n\n%s%s%s\n" "$blue" "$name" "$discard" + # Rekursiver Aufruf: der gesammelte Block wird wie "Stringinput" + # behandelt und im unteren Abschnitt entschlüsselt + printf "%s" "$result" | vdecr + name="" + result="" + parsing=0 + fi + fi + + # Token mit ":" als "Name" merken (typischerweise YAML key:) + if [[ "$(echo "$token" | grep -c ":")" -eq 1 ]]; then + name="$token" + fi + + # Tokenisierung nach Whitespace (ähnlich dem Original) + done < <(tr -s '[:space:]' '\n' < "$1") + + echo "Processed vault values from file: $1" >&2 + + 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 entschlüsseln + # + # Das Script erwartet, dass der Input evtl. als YAML-Fragment kommt, + # und "normalisiert" ihn so, dass ansible-vault decrypt damit klarkommt: + # + # - ersetzt Spaces durch Newlines + # - entfernt YAML-Deko wie "---", "key:", "!vault", "|", leere Zeilen + # - piped den Rest in "ansible-vault decrypt" + ######################################## + printf -- "%s" "$str" | \ + sed 's/ /\n/g' | \ + sed '/---\|^.*:\|\!vault\||\|^$/d' | \ + ansible-vault decrypt 2>/dev/null + + printf '\n' +} + +######################################## +# Main +######################################## +if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then + show_help + exit 0 +fi + +vdecr "${1:-}" diff --git a/encrypt-interactiv.sh b/encrypt-interactiv.sh new file mode 100755 index 0000000..9acc8f5 --- /dev/null +++ b/encrypt-interactiv.sh @@ -0,0 +1,244 @@ +#!/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 "$@" diff --git a/encrypt-vault-strings-from-file.sh b/encrypt-vault-strings-from-file.sh new file mode 100755 index 0000000..e9c9c43 --- /dev/null +++ b/encrypt-vault-strings-from-file.sh @@ -0,0 +1,221 @@ +#!/usr/bin/env bash +# +# encrypt-vault-strings-to-file.sh +# +# Gegenstück zu deinem Decrypt-Script. +# +# Funktionen: +# 1. Komplettes File mit ansible-vault encrypt verschlüsseln +# 2. YAML-Datei mit key: value Zeilen in key: !vault | Blöcke umwandeln +# 3. Einzelnen String verschlüsseln (Argument, Pipe oder interaktiv) +# +# Vault-Passwortdatei: ~/.vault-pass +# + +set -euo pipefail + +######################################## +# Hilfe anzeigen +######################################## +show_help() { +cat <&2 + return 1 + fi + out_file="$2" + shift 2 + ;; + --full-file) + force_full_file=1 + shift + ;; + -h|--help) + show_help + exit 0 + ;; + --) + shift + break + ;; + *) + break + ;; + esac + done + + ######################################## + # --- 1) File als Argument --- + ######################################## + if [[ -n "${1:-}" && -f "$1" ]]; then + local f="$1" + + # Fall A: Datei ist bereits vollständig vault-verschlüsselt + if [[ "$(head -n1 "$f")" == "\$ANSIBLE_VAULT;1.1;AES256"* ]]; then + echo "File already encrypted (ANSIBLE_VAULT header found): $f" >&2 + return 0 + fi + + # Fall B: YAML mit key: value Zeilen nur fuer typische YAML-Dateien + # (damit z.B. README.md als normale Datei komplett verschluesselt wird). + if [[ $force_full_file -eq 0 ]] && [[ "$f" =~ \.(yml|yaml)$ ]] && grep -Eq '^[[:space:]]*[A-Za-z0-9_.-]+:[[:space:]]*[^#].*$' "$f"; then + local tmpout + tmpout="$(mktemp)" + + # Datei zeilenweise lesen + while IFS= read -r line || [[ -n "$line" ]]; do + + # Leere Zeilen oder Kommentare unverändert übernehmen + if [[ -z "$line" || "$line" =~ ^[[:space:]]*# ]]; then + printf '%s\n' "$line" >> "$tmpout" + continue + fi + + # Bereits verschlüsselte !vault Einträge nicht verändern + if echo "$line" | grep -q '\!vault'; then + printf '%s\n' "$line" >> "$tmpout" + continue + fi + + # Einfache key: value Zeilen erkennen + if [[ "$line" =~ ^([[:space:]]*)([A-Za-z0-9_.-]+):[[:space:]]*(.+)$ ]]; then + local indent="${BASH_REMATCH[1]}" + local key="${BASH_REMATCH[2]}" + local value="${BASH_REMATCH[3]}" + + # YAML-Blockindikatoren nicht verändern + if [[ "$value" == "|" || "$value" == ">" || "$value" == "" ]]; then + printf '%s\n' "$line" >> "$tmpout" + continue + fi + + # Leichte Bereinigung von Quotes + value="${value%\"}"; value="${value#\"}" + value="${value%\'}"; value="${value#\'}" + + # Verschlüsselung via ansible-vault encrypt_string + while IFS= read -r enc_line; do + printf '%s%s\n' "$indent" "$enc_line" >> "$tmpout" + done < <( + printf '%s' "$value" | + ansible-vault encrypt_string \ + --stdin-name "$key" + ) + + else + # Nicht passende Zeilen unverändert übernehmen + printf '%s\n' "$line" >> "$tmpout" + fi + + done < "$f" + + # Output schreiben + 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 + + # Fall C: Normale Datei → komplett verschlüsseln (in-place) + ansible-vault encrypt "$f" + + echo "Encrypted file in-place: $f" >&2 + return 0 + fi + + ######################################## + # --- 2) String-Verschlüsselung --- + ######################################## + local str="" + local name="secret" + + # String als Argument + if [[ -n "${1:-}" ]]; then + str="$1" + + # String via Pipe + elif [[ ! -t 0 ]]; then + str="$(cat)" + + # Interaktiver Modus + 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 + + # Ausgabe als YAML-kompatibler !vault Block + echo "Hallo" + printf '%s' "$str" | ansible-vault encrypt_string --stdin-name "$name" + +} + +######################################## +# Script starten +######################################## +vencr "$@"