Files
CISS.debian.installer/func/cdi_1250_yaml/1256_secret_parser.sh
Marc S. Weidner 17bf5ca5fc
All checks were successful
🛡️ Shell Script Linting / 🛡️ Shell Script Linting (push) Successful in 1m53s
V8.00.000.2025.06.17
Signed-off-by: Marc S. Weidner <msw@coresecret.dev>
2025-10-24 20:58:50 +01:00

217 lines
6.3 KiB
Bash

#!/bin/bash
# SPDX-Version: 3.0
# SPDX-CreationInfo: 2025-06-17; WEIDNER, Marc S.; <msw@coresecret.dev>
# 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.; <msw@coresecret.dev>
# 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}" || 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
#######################################
# Purpose:
# High-performance 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_<UPPER_SNAKE_CASE_PATH> (PATH excludes "secrets." and trailing ".value")
# All with "declare -g" (no export).
# Mapping: CISS_SECRETS_MAP["foo.bar"]=CISS_SECRET_FOO_BAR
# Security:
# No logging of values. No plaintext temp files. Streaming pipeline; no full-doc materialization.
# Globals:
# CISS_SECRETS_MAP
# CISS_SECRETS_SOURCE
# DIR_CNF
# ERR_MISSING_AGE_KEY
# Arguments:
# None
# Returns:
# 0: on success
#######################################
yaml_secret() {
### Declare Arrays, HashMaps, and Variables.
declare -r SOPS_AGE_KEY_FILE"/root/.config/sops/age/keys.txt"
declare secrets_encrypted="" secrets_yaml="${CISS_SECRETS_SOURCE}" \
__path="" __path_wo_prefix="" __pipe_fd="" __umask="" __value="" __varname="" __yq_expr=""
secrets_encrypted="$(yq -r '.secrets.x_files // false' -- "${secrets_yaml}")" || secrets_encrypted="false"
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}"
fi
__umask=$(umask)
umask 0077
### Build a single streaming producer: (sops -d |) yq -rj '...'
### yq emits: <path_wo_value>\0<plain_value>\0 -> for each secret.
### No newlines between results (-j), only NUL (\u0000) separators -> robust with arbitrary value content.
# shellcheck disable=SC2016
__yq_expr='
paths(scalars) as $p
| select($p[0] == "secrets" and $p[-1] == "value")
| ($p[0:-1] | join(".")) # E.g. "secrets.db.password".
+ "\u0000"
+ ((getpath($p) // "") | tostring) # Plain scalar value; coerce non-strings.
+ "\u0000"
'
### Create the producer as a process substitution.
if [[ "${secrets_encrypted}" == "true" ]]; then
### Decrypt once, stream into yq; avoid storing full doc in memory.
# shellcheck disable=SC1083,SC2312
exec {__pipe_fd} < <(
sops -d --input-type=yaml --output-type=yaml -- "${secrets_yaml}" | yq -rj "${__yq_expr}" -
)
else
# shellcheck disable=SC1083,SC2312
exec {__pipe_fd} < <( yq -rj "${__yq_expr}" -- "${secrets_yaml}")
fi
### Single consumer: read NUL-delimited pairs and assign variables.
### Loop invariant: next read is PATH, then VALUE. Stop cleanly at EOF.
while :; do
### Read path (up to NUL); break on EOF.
IFS= read -r -d '' __path <&"${__pipe_fd}" || break
echo "${__path}"
### Read value (up to NUL); if missing (odd count), treat as empty
IFS= read -r -d '' __value <&"${__pipe_fd}" || __value=""
echo "${__value}"
### Drop the leading 'secrets.' prefix for naming.
__path_wo_prefix="${__path#secrets.}"
echo "${__path_wo_prefix}"
__varname="$(ciss_secret_varname_from_path "${__path_wo_prefix}")"
echo "${__varname}"
### Assign to a global variable, preserving content verbatim (including newlines).
unset -v "${__varname}"
declare -g "${__varname}"
printf -v "${__varname}" '%s' "${__value}"
### Track in the map (without .value)
CISS_SECRETS_MAP["${__path_wo_prefix}"]="${__varname}"
done
### Close the producer FD
exec {__pipe_fd}<&-
umask "${__umask}"
echo "Inside 1256()"
sleep 60
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