diff --git a/ansible.cfg b/ansible.cfg index 512282c..51d58a2 100644 --- a/ansible.cfg +++ b/ansible.cfg @@ -40,7 +40,7 @@ inventory = ./hosts remote_user = chris roles_path = ./roles -vault_password_file = open_the_vault.sh +vault_password_file = oopen-server_the_vault.sh #retry_files_enabled = False #allow_world_readable_tmpfiles = True #interpreter_python: auto diff --git a/decrypt-interactiv.sh b/decrypt-interactiv.sh index cc762fd..f545631 100755 --- a/decrypt-interactiv.sh +++ b/decrypt-interactiv.sh @@ -1,26 +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. +# -# Prüfen, ob ein Dateiname als Argument übergeben wurde -if [ -z "$1" ]; then - echo "Usage: $0 " - exit 1 +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 -DATEI="$1" - -# Prüfen, ob ansible-vault existiert und ausführbar ist -if ! command -v ansible-vault >/dev/null 2>&1; then - echo "Fehler: 'ansible-vault' ist nicht installiert oder nicht im PATH." - exit 2 -fi - -# Prüfen, ob die angegebene Datei existiert -if [ ! -f "$DATEI" ]; then - echo "Fehler: Datei '$DATEI' existiert nicht." - exit 3 -fi - -# Befehl ausführen -ansible-vault decrypt --ask-vault-pass "$DATEI" - -exit 0 +vdecr "${1:-}" diff --git a/decrypt-vault-strings-from-file.sh b/decrypt-vault-strings-from-file.sh index cfd1856..4ea9f67 100755 --- a/decrypt-vault-strings-from-file.sh +++ b/decrypt-vault-strings-from-file.sh @@ -75,6 +75,7 @@ vdecr() { 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 @@ -133,6 +134,8 @@ vdecr() { # Tokenisierung nach Whitespace (ähnlich dem Original) done < <(tr -s '[:space:]' '\n' < "$1") + echo "Processed vault values from file: $1" >&2 + return 0 fi diff --git a/decrypt-vault-strings-from-file.sh.00 b/decrypt-vault-strings-from-file.sh.00 deleted file mode 100755 index 2b01227..0000000 --- a/decrypt-vault-strings-from-file.sh.00 +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env bash - -vdecr() { - unset IFS - - if [[ -n "$1" ]]; then - if [[ -f "$1" ]]; then - - if [[ $(head -n1 "$1") == "\$ANSIBLE_VAULT;1.1;AES256" ]]; then - cat "$1" | ansible-vault decrypt 2> /dev/null - return 0 - fi - printf 'Reading vault values from file...\n\n' - local parsing=0 - local result="" - local name="" - local blue=$(tput setaf 4) - local discard=$(tput sgr0) - for line in $(cat $1); do - if [[ $(echo "$line" | grep -c "\!vault") -gt 0 ]] && [[ $parsing -eq 0 ]]; then - parsing=1 - elif [[ $parsing -eq 1 ]] && [[ $( echo $line | grep -c ":") -eq 0 ]]; then - result=$(printf "${result}\n${line}") - else - if [[ $result != "" ]]; then - printf "\n\n${blue}$name${discard}\n" - printf "$result" | vdecr - name="" - result="" - parsing=0 - fi - fi - if [[ $( echo "$line" | grep -c ":") -eq 1 ]]; then - name="$line" - fi - done - return 0 - fi - local str="$1" - elif [[ ! -t 0 ]]; then - local str=$(cat) - else - printf 'Interactive mode. Paste encrypted string and press Ctrl-D two times to confirm.\n' - local str=$(cat) - printf '\n' - fi - - printf -- "$str" | sed 's/ /\n/g' | \ - sed '/---\|^.*:\|\!vault\||\|^$/d' | \ - #ansible-vault decrypt --vault-password-file ~/.vault-pass 2> /dev/null - ansible-vault decrypt 2> /dev/null - printf '\n' -} - -vdecr $1 diff --git a/encrypt-interactiv.sh b/encrypt-interactiv.sh index 1b81d3e..9acc8f5 100755 --- a/encrypt-interactiv.sh +++ b/encrypt-interactiv.sh @@ -1,26 +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. +# -# Prüfen, ob ein Dateiname als Argument übergeben wurde -if [ -z "$1" ]; then - echo "Usage: $0 " - exit 1 -fi +set -euo pipefail -DATEI="$1" +# 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}" -# Prüfen, ob ansible-vault existiert und ausführbar ist -if ! command -v ansible-vault >/dev/null 2>&1; then - echo "Fehler: 'ansible-vault' ist nicht installiert oder nicht im PATH." - exit 2 -fi +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 +} -# Prüfen, ob die angegebene Datei existiert -if [ ! -f "$DATEI" ]; then - echo "Fehler: Datei '$DATEI' existiert nicht." - exit 3 -fi +ask_vault_password_once() { + # Passwort nur einmal pro Skriptlauf abfragen. + if [[ -n "${VAULT_PASS_FILE}" && -f "${VAULT_PASS_FILE}" ]]; then + return 0 + fi -# Befehl ausführen -ansible-vault encrypt --ask-vault-pass "$DATEI" + if [[ ! -r /dev/tty ]]; then + echo "Error: No interactive TTY available for password prompt." >&2 + return 1 + fi -exit 0 + 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-string.sh b/encrypt-vault-string.sh deleted file mode 100755 index 7a45c32..0000000 --- a/encrypt-vault-string.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -read -r -s -p "String zum Verschlüsseln: " PLAINTEXT -echo - -printf '%s' "$PLAINTEXT" | ansible-vault encrypt_string --stdin-name 'secret' diff --git a/encrypt-vault-strings-from-file.sh b/encrypt-vault-strings-from-file.sh index eb78e84..e9c9c43 100755 --- a/encrypt-vault-strings-from-file.sh +++ b/encrypt-vault-strings-from-file.sh @@ -31,6 +31,9 @@ Modes: $(basename "$0") vars.yml $(basename "$0") -o output.yml vars.yml + Force whole-file encryption for a YAML file: + $(basename "$0") --full-file vars.yml + 3) String encrypt: $(basename "$0") 'mySecret' echo 'mySecret' | $(basename "$0") @@ -38,6 +41,7 @@ Modes: Options: -o FILE Write YAML output to FILE instead of stdout + --full-file Encrypt the given file in-place, even if it is .yml/.yaml -h, --help Show this help and exit Notes: @@ -62,13 +66,37 @@ vencr() { fi ######################################## - # Optionales Output-File (-o) + # Optionen: Ausgabe-Datei und Vollverschluesselung fuer YAML-Dateien. ######################################## local out_file="" - if [[ "${1:-}" == "-o" && -n "${2:-}" ]]; then - out_file="$2" - shift 2 - fi + local force_full_file=0 + while [[ -n "${1:-}" && "${1:-}" == -* ]]; do + case "$1" in + -o) + if [[ -z "${2:-}" ]]; then + echo "Error: -o requires a file name." >&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 --- @@ -82,8 +110,9 @@ vencr() { return 0 fi - # Fall B: YAML mit key: value Zeilen - if grep -Eq '^[[:space:]]*[A-Za-z0-9_.-]+:[[:space:]]*[^#].*$' "$f"; then + # 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)" diff --git a/open_the_vault.sh b/oopen-server_the_vault.sh similarity index 100% rename from open_the_vault.sh rename to oopen-server_the_vault.sh