#!/bin/bash # SPDX-Version: 3.0 # SPDX-CreationInfo: 2025-06-17; WEIDNER, Marc S.; # SPDX-ExternalRef: GIT https://git.coresecret.dev/msw/CISS.debian.installer.git # SPDX-FileContributor: WEIDNER, Marc S.; Centurion Intelligence Consulting Agency # SPDX-FileCopyrightText: 2024-2025; WEIDNER, Marc S.; # SPDX-FileType: SOURCE # SPDX-License-Identifier: EUPL-1.2 OR LicenseRef-CCLA-1.0 # SPDX-LicenseComment: This file is part of the CISS.debian.installer.secure framework. # SPDX-PackageName: CISS.debian.installer # SPDX-Security-Contact: security@coresecret.eu guard_sourcing || return "${ERR_GUARD_SOURCE}" ####################################### # Debug helper: list variable names (no values). # Globals: # CISS_SECRETS_MAP # Arguments: # None # Returns: # 0: on success ####################################### ciss_secrets_list_names() { ### Declare Arrays, HashMaps, and Variables. declare var_k="" for var_k in "${!CISS_SECRETS_MAP[@]}"; do printf '%s.value -> %s\n' "${var_k}" "${CISS_SECRETS_MAP[${var_k}]}" done return 0 } ### Prevents accidental 'unset -f'. # shellcheck disable=SC2034 readonly -f ciss_secrets_list_names ####################################### # Unset all previously created secret variables. # Globals: # CISS_SECRETS_MAP # Arguments: # None # Returns: # 0: on success ####################################### ciss_secrets_unset() { ### Declare Arrays, HashMaps, and Variables. declare var_k="" var_v="" guard_trace on for var_k in "${!CISS_SECRETS_MAP[@]}"; do var_v="${CISS_SECRETS_MAP[${var_k}]}" if [[ -v "${var_v}" ]]; then unset -v "${var_v}" 2>/dev/null || true fi done CISS_SECRETS_MAP=() guard_trace off return 0 } ### Prevents accidental 'unset -f'. # shellcheck disable=SC2034 readonly -f ciss_secrets_unset ####################################### # Build the canonical var name from a dotted path (without 'secrets.' and without '.value'). # Globals: # None # Arguments: # 1: Variable path # Returns: # 0: on success ####################################### ciss_secret_varname_from_path() { ### Declare Arrays, HashMaps, and Variables. declare var_path="${1:-}" var_path="${var_path//[^A-Za-z0-9_]/_}" var_path="${var_path^^}" printf 'CISS_SECRET_%s' "${var_path}" return 0 } ### Prevents accidental 'unset -f'. # shellcheck disable=SC2034 readonly -f ciss_secret_varname_from_path ####################################### # Wipes the specified file securely. # Globals: # None # Arguments: # 1: File to wipe # Returns: # 0: on success ####################################### ciss_secrets_wiper() { ### Declare Arrays, HashMaps, and Variables. declare var_file="${1:-}" if [[ -f "${var_file}" ]]; then : >| "${var_file}" shred -vfzu -n 5 "${var_file}" > /dev/null 2>&1 || rm -f -- "${var_file}" fi return 0 } ### Prevents accidental 'unset -f'. # shellcheck disable=SC2034 readonly -f ciss_secrets_wiper ####################################### # Parsing of only "*.value" keys from 'SECRETS.yaml' into Bash globals. # If the file contains SOPS markers, decrypt once (streaming) with sops/age, then yq parses in a single pass. # No base64, plain values preserved (including newlines). No repeated per-key decrypts or yq calls. # Conventions: # Variables: CISS_SECRET_ (PATH excludes "secrets." and trailing ".value") # All with "declare -g" (no export). # Mapping: CISS_SECRETS_MAP["foo.bar"]=CISS_SECRET_FOO_BAR # Globals: # CISS_SECRETS_AGE # CISS_SECRETS_MAP # CISS_SECRETS_SOURCE # DIR_CNF # Arguments: # None # Returns: # 0: on success # ERR_DECRYPTION_SOPS: on failure # ERR_MISSING_AGE_BIN: on failure # ERR_MISSING_AGE_KEY: on failure ####################################### yaml_secret() { # TODO: Remove debug echos ### Declare Arrays, HashMaps, and Variables. declare -r SOPS_AGE_KEY_FILE="${CISS_SECRETS_AGE}" declare -a __names=() declare secrets_encrypted="" secrets_if="${CISS_SECRETS_SOURCE}" secrets_of="${DIR_CNF}/SECRETS_DECRYPTED.yaml" \ __SECRETS="${DIR_CNF}/SECRETS_BASH.var" \ __base="" __name="" __umask="" __path_wo_prefix="" __val="" __varname="" __umask=$(umask) umask 0077 # TODO: guard_trace on #guard_trace on secrets_encrypted="$(yq -r '.secrets.x_files // false' -- "${secrets_if}")" || secrets_encrypted="false" do_log "debug" "file_only" "1256() 'secrets_encrypted' according to secrets.x_files: '${secrets_encrypted}'." if grep -qE '(^|\s)sops:\s*$' -- "${secrets_if}" 2>/dev/null || grep -q 'ENC\[' -- "${secrets_if}" 2>/dev/null; then secrets_encrypted="true" do_log "debug" "file_only" "1256() 'secrets_encrypted' according to heuristic mode: '${secrets_encrypted}'." fi if [[ "${secrets_encrypted}" == "true" ]]; then if ! command -v sops >/dev/null 2>&1; then do_log "fatal" "file_only" "1260() SOPS not found but SECRETS.yaml appears to be SOPS-managed." return "${ERR_MISSING_AGE_BIN}" fi [[ -r "${SOPS_AGE_KEY_FILE}" ]] || return "${ERR_MISSING_AGE_KEY}" sops -d --input-type=yaml --output-type=yaml -- "${secrets_if}" >| "${secrets_of}" [[ -r "${secrets_of}" ]] || return "${ERR_DECRYPTION_SOPS}" ciss_secrets_wiper "${secrets_if}" && mv "${secrets_of}" "${secrets_if}" fi yq -o=shell "${secrets_if}" >| "${__SECRETS}" && ciss_secrets_wiper "${secrets_if}" ### Keep only '*_value=' lines, normalize empty RHS, quote unquoted simple RHS. LC_ALL=C sed -n -E ' /^[[:space:]]*(#|$)/b s/^[[:space:]]*(export|declare[[:space:]]+-x)[[:space:]]+//; /^[[:space:]]*[A-Za-z_][A-Za-z0-9_]*_value=/!b s/^([[:space:]]*[A-Za-z_][A-Za-z0-9_]*_value)=[[:space:]]*$/\1='\'''\''/; t print /^[[:space:]]*[A-Za-z_][A-Za-z0-9_]*_value=[[:space:]]*('"'"'|\"|\$'"'"')/b print s/^([[:space:]]*[A-Za-z_][A-Za-z0-9_]*_value)=([^[[:space:]]'"'"'$][^[:space:]]*)[[:space:]]*$/\1='"'"'\2'"'"'/; t print s/^([[:space:]]*[A-Za-z_][A-Za-z0-9_]*_value)=[[:space:]]*(.+)[[:space:]]*$/\1='"'"'\2'"'"'/; t print :print p ' -- "${__SECRETS}" >| "${__SECRETS}.value_only" mv -f -- "${__SECRETS}.value_only" "${__SECRETS}" # shellcheck disable=SC1091 source=./${__SECRETS} source "${__SECRETS}" ### Iterate only variables ending in '_value'. # shellcheck disable=SC2312 mapfile -t __names < <(compgen -A variable 'secrets_*_value') for __name in "${__names[@]}"; do echo "${__name}" ### Value of the generated variable: __val="${!__name}" echo "${__val}" ### Strip suffix and leading namespace: # secrets_db_password_value -> base="secrets_db_password" __base="${__name%_value}" echo "${__base}" # secrets_db_password -> path_wo_prefix="db_password" __path_wo_prefix="${__base#secrets_}" echo "${__path_wo_prefix}" ### Canonical CISS name: __varname="$(ciss_secret_varname_from_path "${__path_wo_prefix}")" echo "${__varname}" ### Assign as global (verbatim, preserves newlines). unset -v "${__varname}" declare -g "${__varname}" echo "declare -g ${__varname}" printf -v "${__varname}" '%s' "${__val}" echo "printf -v ${__varname} ${__val}" CISS_SECRETS_MAP["${__path_wo_prefix}"]="${__varname}" done ### Hygiene: remove the intermediate variables to reduce secret surface, e.g., unset 'secrets_*_value' after transfer. for __name in "${__names[@]}"; do unset -v "${__name}" done umask "${__umask}" echo "Inside 1256()" sleep 60 # TODO: guard_trace off #guard_trace off guard_dir; return 0 } ### Prevents accidental 'unset -f'. # shellcheck disable=SC2034 readonly -f yaml_secret # vim: number et ts=2 sw=2 sts=2 ai tw=128 ft=sh