Compare commits

...

2 Commits

Author SHA256 Message Date
a9a7db7c6b V8.00.000.2025.06.17
All checks were successful
🛡️ Shell Script Linting / 🛡️ Shell Script Linting (push) Successful in 1m52s
Signed-off-by: Marc S. Weidner <msw@coresecret.dev>
2025-10-26 15:16:37 +00:00
a2b1fcb457 V8.00.000.2025.06.17
Signed-off-by: Marc S. Weidner <msw@coresecret.dev>
2025-10-26 15:11:48 +00:00
7 changed files with 112 additions and 55 deletions

View File

@@ -13,14 +13,14 @@
# This file contains configurations for the CISS.debian.installer
# Master V8.00.000.2025.06.17
# YAML specification: 1.2
#
preseed:
description: "Configuration values for automated installation of encrypted systems on this host via primordial-workflow™."
created_at: "2025-10-23"
created_for: "host_domain_tld"
name: "CISS.debian.installer"
version: "V8.00.000.2025.06.17"
#
################################################################################################################################
# APT settings
################################################################################################################################

View File

@@ -11,8 +11,8 @@ include_toc: true
[![Static Badge](https://badges.coresecret.dev/badge/shellformat-passed-white?style=plastic&logo=google&logoColor=white&logoSize=auto&label=shellformat&color=%234285F4)](https://github.com/mvdan/sh) &nbsp;
[![Static Badge](https://badges.coresecret.dev/badge/Shellstyle-Google-white?style=plastic&logo=google&logoColor=white&logoSize=auto&label=Shellstyle&color=%234285F4)](https://google.github.io/styleguide/shellguide.html)
&nbsp;
[![Static Badge](https://badges.coresecret.dev/badge/Gitea-1.24.6-white?style=plastic&logo=gitea&logoColor=white&logoSize=auto&label=gitea&color=%23609926)](https://docs.gitea.com/) &nbsp;
[![Static Badge](https://badges.coresecret.dev/badge/IntelliJ-2025.2.2-white?style=plastic&logo=intellijidea&logoColor=white&logoSize=auto&label=IntelliJ&color=%23000000)](https://www.jetbrains.com/store/?section=personal&billing=yearly) &nbsp;
[![Static Badge](https://badges.coresecret.dev/badge/Gitea-1.24.7-white?style=plastic&logo=gitea&logoColor=white&logoSize=auto&label=gitea&color=%23609926)](https://docs.gitea.com/) &nbsp;
[![Static Badge](https://badges.coresecret.dev/badge/IntelliJ-2025.2.4-white?style=plastic&logo=intellijidea&logoColor=white&logoSize=auto&label=IntelliJ&color=%23000000)](https://www.jetbrains.com/store/?section=personal&billing=yearly) &nbsp;
[![Static Badge](https://badges.coresecret.dev/badge/keepassxc-2.7.10-white?style=plastic&logo=keepassxc&logoColor=white&logoSize=auto&label=KeePassXC&color=%236CAC4D)](https://keepassxc.org/) &nbsp;
[![Static Badge](https://badges.coresecret.dev/badge/netcup-Netcup-white?style=plastic&logo=netcup&logoColor=white&logoSize=auto&label=powered&color=%23056473)](https://www.netcup.com/de) &nbsp;
[![Static Badge](https://badges.coresecret.dev/badge/powered-Centurion-white?style=plastic&logo=europeanunion&logoColor=white&logoSize=auto&label=powered&color=%230F243E)](https://coresecret.eu/) &nbsp;

View File

@@ -213,8 +213,8 @@ yaml_reader
info_echo "1252_yaml_validator.sh"
yaml_validator
#info_echo "1256_secret_parser.sh"
#yaml_secret
info_echo "1256_secret_parser.sh"
yaml_secret
### CDI_3200

View File

@@ -44,28 +44,36 @@ yaml_parser() {
### Generate Arrays for [Grub Parameter], [Locales], [NTPSec Server FQDN], [Software Packages].
while IFS='=' read -r var_key var_value; do
var_value=${var_value#\'}
var_value=${var_value%\'}
# shellcheck disable=SC2034,SC2249
case "${var_key}" in
grub_parameter_[0-9]*) ARY_BOOTPARAM+=("${var_value}") ;;
locale_locale_[0-9]*) ARY_LOCALE+=("${var_value}") ;;
ntp_server_[0-9]*) ARY_NTPSRVR+=("${var_value}") ;;
ssh_allow_ipv4_[0-9]*) ARY_ALLOW_IPV4+=("${var_value}") ;;
ssh_allow_ipv6_[0-9]*) ARY_ALLOW_IPV6+=("${var_value}") ;;
software_[0-9]*) ARY_PACKAGES+=("${var_value}") ;;
esac
done < "${VAR_PRESEED}"
var_key=""
### Search all set variables for user_userN_name patterns.
# shellcheck disable=SC2312
while IFS='=' read -r var_key _; do
### Accept any of these keys: name, fullname, uid, gid, shell, password, sshpubkey, authentication_* and privileges_*
if [[ "${var_key}" =~ ^user_user([0-9]+)_(name|fullname|uid|gid|shell|password|sshpubkey|authentication_[A-Za-z0-9_]+|privileges_[A-Za-z0-9_]+)$ ]]; then
var_index=${BASH_REMATCH[1]}
(( var_index > VAR_USER_MAX )) && VAR_USER_MAX=var_index
fi
done < "${VAR_PRESEED}"
### If nothing matched, default to 0 (only user 0).

View File

@@ -98,33 +98,74 @@ ciss_secret_varname_from_path() {
readonly -f ciss_secret_varname_from_path
#######################################
# Purpose:
# High-performance parsing of only "*.value" keys from 'SECRETS.yaml' into Bash globals.
# 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_<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_AGE
# CISS_SECRETS_MAP
# CISS_SECRETS_SOURCE
# DIR_CNF
# ERR_MISSING_AGE_KEY
# 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() {
### 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=""
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
secrets_encrypted="$(yq -r '.secrets.x_files // false' -- "${secrets_yaml}")" || secrets_encrypted="false"
if [[ "${secrets_encrypted}" == "true" ]]; then
@@ -137,67 +178,76 @@ yaml_secret() {
[[ -r "${SOPS_AGE_KEY_FILE}" ]] || return "${ERR_MISSING_AGE_KEY}"
fi
sops -d --input-type=yaml --output-type=yaml -- "${secrets_if}" >| "${secrets_of}"
__umask=$(umask)
umask 0077
[[ -r "${secrets_of}" ]] || return "${ERR_DECRYPTION_SOPS}"
### 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
# shellcheck disable=SC2016,SC2312
exec {__pipe_fd}< <( yq -r -N -0 'path(..) as $p | select($p[0]=="secrets" and $p[-1]=="value") | ($p[0:-1]|join("."))' -- "${secrets_yaml}" )
ciss_secrets_wiper "${secrets_if}" && mv "${secrets_of}" "${secrets_if}"
fi
### Single consumer: read NUL-delimited pairs and assign variables.
### Loop invariant: next read is PATH, then VALUE. Stop cleanly at EOF.
while :; do
yq -o=shell "${secrets_if}" >| "${__SECRETS}" && ciss_secrets_wiper "${secrets_if}"
### Read path (up to NUL); break on EOF.
IFS= read -r -d '' __path <&"${__pipe_fd}" || break
echo "${__path}"
LC_ALL=C sed -n -E '
/^[[:space:]]*(#|$)/b
s/^[[:space:]]*export[[:space:]]+//
/^[[:space:]]*[A-Za-z_][A-Za-z0-9_]*_value=/p
' -- "${__SECRETS}" >| "${__SECRETS}.value_only"
### Read value (up to NUL); if missing (odd count), treat as empty
IFS= read -r -d '' __value <&"${__pipe_fd}" || __value=""
echo "${__value}"
ciss_secrets_wiper "${__SECRETS}"
### Drop the leading 'secrets.' prefix for naming.
__path_wo_prefix="${__path#secrets.}"
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
### 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 to a global variable, preserving content verbatim (including newlines).
### Assign as global (verbatim, preserves newlines).
unset -v "${__varname}"
declare -g "${__varname}"
printf -v "${__varname}" '%s' "${__value}"
echo "declare -g ${__varname}"
printf -v "${__varname}" '%s' "${__val}"
echo "printf -v ${__varname} ${__val}"
### Track in the map (without .value)
CISS_SECRETS_MAP["${__path_wo_prefix}"]="${__varname}"
done
### Close the producer FD
exec {__pipe_fd}<&-
### 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'.

View File

@@ -60,6 +60,7 @@ declare -girx ERR_VERIFY_LOGROTATE=213 # Error verification by 'logrotate'.
declare -girx ERR_READ_AUTH_FILE=212 # Error reading the Luks Backup auth token file.
declare -girx ERR_ACCOUNT_CREATE=211 # Error creating user accounts.
declare -girx ERR_LUKS_HEADER_ENC=210 # Error encrypting LUKS Header backup.
declare -girx ERR_DECRYPTION_SOPS=132 # An error occurred while decrypting SECRETS.yaml.
declare -girx ERR_MISSING_AGE_BIN=130 # SOPS binary for decryption SECRETS.yaml missing.
declare -girx ERR_MISSING_AGE_KEY=129 # AGE key for decryption SECRETS.yaml values missing.
declare -girx ERR_GUARD_SOURCE=128 # Module tried to load twice.

View File

@@ -52,13 +52,11 @@ declare -grx VAR_SETUP_PART="${DIR_CNF}/partitioning.yaml"
### Initialize SECRETS.yaml variables.
# shellcheck disable=SC2034
declare -gA CISS_SECRETS_MAP=() # YAML path (w/o '.value' and without 'secrets.') -> varname.
declare -gA CISS_SECRETS_MAP=() # YAML path (w/o '.value' and without 'secrets.') -> varname.
# shellcheck disable=SC2034
declare -g CISS_SECRETS_AGE="" # AGE PRIVATE Keyfile to decrypt SOPS encrypted values.
declare -g CISS_SECRETS_AGE="/root/.config/sops/age/keys.txt" # AGE PRIVATE Keyfile to decrypt SOPS encrypted values.
# shellcheck disable=SC2034
declare -gr CISS_SECRETS_SOURCE="${DIR_CNF}/SECRETS.yaml" # Effective YAML source used (plain or decrypted stream)
# shellcheck disable=SC2034
declare -g CISS_SECRETS_XFILES="" # Derived from SOPS presence heuristic.
declare -gr CISS_SECRETS_SOURCE="${DIR_CNF}/SECRETS.yaml" # Effective YAML source used (plain or decrypted stream).
### Base mount paths and variables for debootstrap.
declare -grx TARGET="/target"