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

218 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}"
#######################################
# 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=""
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
### Create the producer as a process substitution.
if [[ "${secrets_encrypted}" == "true" ]]; then
### Decrypt once, stream into yq; avoid storing full doc in memory; emits '<path>\0<value>\0' for each 'secrets.*.value'
# shellcheck disable=SC2016,SC2312
exec {__pipe_fd}< <(
sops -d --input-type=yaml --output-type=yaml -- "${secrets_yaml}" | yq -r -N -0 'leaf_paths as $p
| select($p[0]=="secrets" and $p[-1]=="value")
| ($p[0:-1] | join(".")), ((getpath($p)//"") | tostring)
' -
)
else
### One-pass producer: emits '<path>\0<value>\0' for each 'secrets.*.value'
# -r : raw scalars
# -N : no "---" doc separators
# -0 : NUL between *each* result
# shellcheck disable=SC2016,SC2312
exec {__pipe_fd}< <(
yq -r -N -0 'paths as $p
| select( ($p[0]=="secrets") and ($p[-1]=="value")
and ((getpath($p)|type) != "object")
and ((getpath($p)|type) != "array") )
| ($p[0:-1] | join(".")), ((getpath($p)//"") | tostring)
' -- "${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