V9.14.022.2026.06.11: add path security helpers

This commit is contained in:
2026-06-11 05:07:33 +02:00
parent 9ef535554a
commit 74897d85b1
4 changed files with 819 additions and 0 deletions
+340
View File
@@ -0,0 +1,340 @@
#!/bin/bash
# SPDX-Version: 3.0
# SPDX-CreationInfo: 2026-06-11; WEIDNER, Marc S.; <msw@coresecret.dev>
# SPDX-ExternalRef: GIT https://git.coresecret.dev/msw/CISS.debian.live.builder.git
# SPDX-FileContributor: WEIDNER, Marc S.; Centurion Intelligence Consulting Agency
# SPDX-FileCopyrightText: 2024-2026; WEIDNER, Marc S.; <msw@coresecret.dev>
# SPDX-FileType: SOURCE
# SPDX-License-Identifier: LicenseRef-CNCL-1.1 OR LicenseRef-CCLA-1.1
# SPDX-LicenseComment: This file is part of the CISS.debian.installer.secure framework.
# SPDX-PackageName: CISS.debian.live.builder
# SPDX-Security-Contact: security@coresecret.eu
guard_sourcing || return "${ERR_GUARD_SRCE}"
#######################################
# Prints a generic build-directory validation error without disclosing a path.
# Arguments:
# 1: unsafe input class
# 2: quiet flag
# Returns:
# ERR_BUILD_PATH
#######################################
build_directory_validation_error() {
declare error_class="$1" quiet="${2:-false}"
if [[ "${quiet}" != "true" ]]; then
printf "\e[91m❌ Unsafe build-directory input rejected: %s. \e[0m\n" "${error_class}" >&2
fi
return "${ERR_BUILD_PATH:-217}"
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f build_directory_validation_error
#######################################
# Canonicalises an existing path or a path whose parent exists.
# Arguments:
# 1: candidate path
# 2: output variable name
# Returns:
# 0: on success
# ERR_BUILD_PATH: on failure
#######################################
canonicalize_build_directory() {
declare candidate="$1" output_variable="$2" basename="" parent="" resolved_path=""
[[ -n "${candidate}" && "${candidate}" == /* ]] || return "${ERR_BUILD_PATH:-217}"
[[ "${candidate}" == "/" ]] || candidate="${candidate%/}"
[[ ! -L "${candidate}" ]] || return "${ERR_BUILD_PATH:-217}"
if [[ -e "${candidate}" ]]; then
resolved_path="$(realpath "${candidate}" 2>/dev/null)" || return "${ERR_BUILD_PATH:-217}"
else
basename="${candidate##*/}"
parent="${candidate%/*}"
[[ -n "${parent}" ]] || parent="/"
[[ -n "${basename}" && "${basename}" != "." && "${basename}" != ".." && -d "${parent}" ]] || return "${ERR_BUILD_PATH:-217}"
resolved_path="$(realpath "${parent}" 2>/dev/null)" || return "${ERR_BUILD_PATH:-217}"
resolved_path="${resolved_path%/}/${basename}"
fi
[[ "${candidate}" == "${resolved_path}" ]] || return "${ERR_BUILD_PATH:-217}"
printf -v "${output_variable}" '%s' "${resolved_path}"
return 0
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f canonicalize_build_directory
#######################################
# Validates a build directory against the repository cleanup policy.
# Globals:
# VAR_TMP_SECRET
# VAR_WORKDIR
# Arguments:
# 1: candidate path
# 2: output variable name
# 3: quiet flag
# Returns:
# 0: on success
# ERR_BUILD_PATH: on failure
#######################################
validate_build_directory_path() {
declare candidate="$1" output_variable="${2:-}" quiet="${3:-false}" secret_root="" validated_path="" workdir=""
declare -a rejected_paths=(
"/" "/bin" "/boot" "/dev" "/etc" "/home" "/lib" "/lib64" "/media" "/mnt" "/opt" "/proc" "/root" "/run" "/sbin" "/srv"
"/sys" "/tmp" "/usr" "/usr/local" "/var" "/var/lib" "/var/tmp"
)
declare rejected_path=""
canonicalize_build_directory "${candidate}" validated_path || {
build_directory_validation_error \
"path is empty, non-absolute, non-canonical, missing its parent, or is a symlink" "${quiet}"
return "${ERR_BUILD_PATH:-217}"
}
for rejected_path in "${rejected_paths[@]}"; do
if [[ "${validated_path}" == "${rejected_path}" ]]; then
build_directory_validation_error "broad or system parent directory" "${quiet}"
return "${ERR_BUILD_PATH:-217}"
fi
done
workdir="$(realpath "${VAR_WORKDIR}" 2>/dev/null)" || {
build_directory_validation_error "repository work directory cannot be resolved" "${quiet}"
return "${ERR_BUILD_PATH:-217}"
}
secret_root="$(realpath "${VAR_TMP_SECRET}" 2>/dev/null)" || {
build_directory_validation_error "secret root cannot be resolved" "${quiet}"
return "${ERR_BUILD_PATH:-217}"
}
if [[ "${workdir}" == "${validated_path}" || "${workdir}" == "${validated_path}/"* || "${validated_path}" == "${workdir}/"* \
|| "${validated_path}" == "${secret_root}" || "${validated_path}" == "${secret_root}/"* ]]; then
build_directory_validation_error "path is outside the dedicated build-directory policy" "${quiet}"
return "${ERR_BUILD_PATH:-217}"
fi
if [[ -n "${output_variable}" ]]; then
printf -v "${output_variable}" '%s' "${validated_path}"
fi
return 0
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f validate_build_directory_path
#######################################
# Validates the builder-owned marker for an exact build directory.
# Globals:
# EUID
# Arguments:
# 1: candidate path
# 2: quiet flag
# Returns:
# 0: on success
# ERR_BUILD_PATH: on failure
#######################################
validate_build_directory_marker() {
declare candidate="$1" quiet="${2:-false}" directory_mode="" directory_owner="" marker="" marker_build_dir=""
declare expected_marker_value="" marker_link_count="" marker_value="" mode="" owner=""
validate_build_directory_path "${candidate}" marker_build_dir "${quiet}" || return "${ERR_BUILD_PATH:-217}"
[[ -d "${marker_build_dir}" ]] || {
build_directory_validation_error "build directory does not exist" "${quiet}"
return "${ERR_BUILD_PATH:-217}"
}
directory_owner="$(secure_stat -c '%u' "${marker_build_dir}" 2>/dev/null)" || return "${ERR_BUILD_PATH:-217}"
directory_mode="$(secure_stat -c '%a' "${marker_build_dir}" 2>/dev/null)" || return "${ERR_BUILD_PATH:-217}"
if [[ "${directory_owner}" != "${EUID}" ]] || (( (8#${directory_mode} & 022) != 0 )); then
build_directory_validation_error "build directory ownership or permissions are unsafe" "${quiet}"
return "${ERR_BUILD_PATH:-217}"
fi
marker="${marker_build_dir}/.ciss-live-builder-owned"
if [[ -L "${marker}" || ! -f "${marker}" ]]; then
build_directory_validation_error "builder-owned marker is missing or unsafe" "${quiet}"
return "${ERR_BUILD_PATH:-217}"
fi
owner="$(secure_stat -c '%u' "${marker}" 2>/dev/null)" || return "${ERR_BUILD_PATH:-217}"
mode="$(secure_stat -c '%a' "${marker}" 2>/dev/null)" || return "${ERR_BUILD_PATH:-217}"
marker_link_count="$(secure_stat -c '%h' "${marker}" 2>/dev/null)" || return "${ERR_BUILD_PATH:-217}"
marker_value="$(cat "${marker}" || exit $?; printf '.')" || return "${ERR_BUILD_PATH:-217}"
marker_value="${marker_value%.}"
expected_marker_value="${marker_build_dir}"$'\n'
if [[ "${owner}" != "${EUID}" || "${mode}" != "400" || "${marker_link_count}" != "1" \
|| "${marker_value}" != "${expected_marker_value}" ]]; then
build_directory_validation_error "builder-owned marker does not match the exact directory" "${quiet}"
return "${ERR_BUILD_PATH:-217}"
fi
return 0
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f validate_build_directory_marker
#######################################
# Validates an existing exact subpath below a marker-owned build directory.
# Arguments:
# 1: build directory
# 2: relative subpath
# 3: output variable name
# 4: quiet flag
# Returns:
# 0: on success
# ERR_BUILD_PATH: on failure
#######################################
validate_build_directory_subpath() {
declare build_directory="$1" relative_path="$2" output_variable="$3" quiet="${4:-false}"
declare candidate_subpath="" resolved_subpath=""
validate_build_directory_marker "${build_directory}" "${quiet}" || return "${ERR_BUILD_PATH:-217}"
if [[ -z "${relative_path}" || "${relative_path}" == /* || "${relative_path}" == "." || "${relative_path}" == ".." \
|| "${relative_path}" == ../* || "${relative_path}" == */../* || "${relative_path}" == */.. \
|| "${relative_path}" == ./* || "${relative_path}" == */./* || "${relative_path}" == */. ]]; then
build_directory_validation_error "unsafe relative cleanup subpath" "${quiet}"
return "${ERR_BUILD_PATH:-217}"
fi
candidate_subpath="${build_directory}/${relative_path}"
if [[ -L "${candidate_subpath}" || ! -e "${candidate_subpath}" ]]; then
build_directory_validation_error "cleanup subpath is missing or is a symlink" "${quiet}"
return "${ERR_BUILD_PATH:-217}"
fi
resolved_subpath="$(realpath "${candidate_subpath}" 2>/dev/null)" || return "${ERR_BUILD_PATH:-217}"
if [[ "${resolved_subpath}" != "${candidate_subpath}" || "${resolved_subpath}" != "${build_directory}/"* ]]; then
build_directory_validation_error "cleanup subpath escapes the exact build directory" "${quiet}"
return "${ERR_BUILD_PATH:-217}"
fi
printf -v "${output_variable}" '%s' "${resolved_subpath}"
return 0
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f validate_build_directory_subpath
#######################################
# Initialises a new or empty build directory and its ownership marker.
# Arguments:
# 1: candidate path
# 2: output variable name
# Returns:
# 0: on success
# ERR_BUILD_PATH: on failure
#######################################
initialize_build_directory() {
declare candidate="$1" output_variable="$2" directory_mode="" directory_owner="" existing_entry=""
declare initialized_build_dir="" marker=""
validate_build_directory_path "${candidate}" initialized_build_dir || return "${ERR_BUILD_PATH:-217}"
if [[ ! -e "${initialized_build_dir}" ]]; then
mkdir -m 0700 "${initialized_build_dir}" || return "${ERR_BUILD_PATH:-217}"
fi
validate_build_directory_path "${initialized_build_dir}" initialized_build_dir || return "${ERR_BUILD_PATH:-217}"
[[ -d "${initialized_build_dir}" && ! -L "${initialized_build_dir}" ]] || return "${ERR_BUILD_PATH:-217}"
directory_owner="$(secure_stat -c '%u' "${initialized_build_dir}" 2>/dev/null)" || return "${ERR_BUILD_PATH:-217}"
directory_mode="$(secure_stat -c '%a' "${initialized_build_dir}" 2>/dev/null)" || return "${ERR_BUILD_PATH:-217}"
if [[ "${directory_owner}" != "${EUID}" ]] || (( (8#${directory_mode} & 022) != 0 )); then
build_directory_validation_error "build directory ownership or permissions are unsafe"
return "${ERR_BUILD_PATH:-217}"
fi
marker="${initialized_build_dir}/.ciss-live-builder-owned"
if [[ -e "${marker}" || -L "${marker}" ]]; then
validate_build_directory_marker "${initialized_build_dir}" || return "${ERR_BUILD_PATH:-217}"
else
existing_entry="$(find "${initialized_build_dir}" -mindepth 1 -maxdepth 1 -print -quit)" || return "${ERR_BUILD_PATH:-217}"
if [[ -n "${existing_entry}" ]]; then
build_directory_validation_error "non-empty directory has no builder-owned marker"
return "${ERR_BUILD_PATH:-217}"
fi
(umask 077; printf '%s\n' "${initialized_build_dir}" >| "${marker}") || return "${ERR_BUILD_PATH:-217}"
chmod 0400 "${marker}" || return "${ERR_BUILD_PATH:-217}"
validate_build_directory_marker "${initialized_build_dir}" || return "${ERR_BUILD_PATH:-217}"
fi
printf -v "${output_variable}" '%s' "${initialized_build_dir}"
return 0
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f initialize_build_directory
#######################################
# Removes paths with a one-filesystem boundary where supported.
# Arguments:
# paths to remove
# Returns:
# rm exit status
#######################################
remove_build_paths() {
# shellcheck disable=SC2312
if rm --help 2>&1 | grep -q -- '--one-file-system'; then
rm -rf --one-file-system -- "$@"
else
rm -rf -- "$@"
fi
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f remove_build_paths
#######################################
# Deletes all content except the ownership marker from an exact build directory.
# Arguments:
# 1: candidate path
# Returns:
# 0: on success
# ERR_BUILD_PATH: on failure
#######################################
clean_build_directory_contents() {
declare candidate="$1" build_entry=""
declare -a build_entries=()
declare -i old_dotglob=0 old_failglob=0 old_nullglob=0
validate_build_directory_marker "${candidate}" || return "${ERR_BUILD_PATH:-217}"
shopt -q dotglob && old_dotglob=1
shopt -q failglob && old_failglob=1
shopt -q nullglob && old_nullglob=1
shopt -s dotglob nullglob
shopt -u failglob
build_entries=("${candidate}"/*)
for build_entry in "${build_entries[@]}"; do
[[ "${build_entry}" == "${candidate}/.ciss-live-builder-owned" ]] && continue
remove_build_paths "${build_entry}" || {
if (( old_dotglob )); then shopt -s dotglob; else shopt -u dotglob; fi
if (( old_failglob )); then shopt -s failglob; else shopt -u failglob; fi
if (( old_nullglob )); then shopt -s nullglob; else shopt -u nullglob; fi
return "${ERR_BUILD_PATH:-217}"
}
done
if (( old_dotglob )); then shopt -s dotglob; else shopt -u dotglob; fi
if (( old_failglob )); then shopt -s failglob; else shopt -u failglob; fi
if (( old_nullglob )); then shopt -s nullglob; else shopt -u nullglob; fi
validate_build_directory_marker "${candidate}" || return "${ERR_BUILD_PATH:-217}"
return 0
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f clean_build_directory_contents
# vim: number et ts=2 sw=2 sts=2 ai tw=128 ft=sh
+92
View File
@@ -0,0 +1,92 @@
#!/bin/bash
# SPDX-Version: 3.0
# SPDX-CreationInfo: 2026-06-11; WEIDNER, Marc S.; <msw@coresecret.dev>
# SPDX-ExternalRef: GIT https://git.coresecret.dev/msw/CISS.debian.live.builder.git
# SPDX-FileContributor: WEIDNER, Marc S.; Centurion Intelligence Consulting Agency
# SPDX-FileCopyrightText: 2024-2026; WEIDNER, Marc S.; <msw@coresecret.dev>
# SPDX-FileType: SOURCE
# SPDX-License-Identifier: LicenseRef-CNCL-1.1 OR LicenseRef-CCLA-1.1
# SPDX-LicenseComment: This file is part of the CISS.debian.installer.secure framework.
# SPDX-PackageName: CISS.debian.live.builder
# SPDX-Security-Contact: security@coresecret.eu
guard_sourcing || return "${ERR_GUARD_SRCE}"
#######################################
# Replaces exact registered secret values in one controlled log file.
# Globals:
# _ARY_SECRET_REDACTION_VALUES
# Arguments:
# 1: log file
# Returns:
# 0: on success or missing log
# ERR_SANITIZING: on failure
#######################################
sanitize_debug_log() {
declare log_file="$1" log_text="" replacement="" secret_value="" tmp_file=""
[[ -n "${log_file}" && -f "${log_file}" ]] || return 0
[[ ! -L "${log_file}" ]] || return "${ERR_SANITIZING:-133}"
log_text="$(cat "${log_file}" || exit $?; printf '.')" || return "${ERR_SANITIZING:-133}"
log_text="${log_text%.}"
for secret_value in "${_ARY_SECRET_REDACTION_VALUES[@]}"; do
[[ -n "${secret_value}" ]] || continue
printf -v replacement '%*s' "${#secret_value}" ''
replacement="${replacement// /*}"
log_text="${log_text//"${secret_value}"/"${replacement}"}"
done
tmp_file="$(mktemp "${log_file}.sanitize.XXXXXX")" || return "${ERR_SANITIZING:-133}"
chmod 0600 "${tmp_file}" || {
rm -f "${tmp_file}"
return "${ERR_SANITIZING:-133}"
}
printf '%s' "${log_text}" >| "${tmp_file}" || {
rm -f "${tmp_file}"
return "${ERR_SANITIZING:-133}"
}
mv -f "${tmp_file}" "${log_file}" || {
rm -f "${tmp_file}"
return "${ERR_SANITIZING:-133}"
}
return 0
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f sanitize_debug_log
#######################################
# Runs the final exact-value sanitisation pass for controlled logs.
# Globals:
# LOG_DEBUG
# LOG_ERROR
# LOG_VAR
# Arguments:
# None
# Returns:
# 0: on success
# ERR_SANITIZING: on failure
#######################################
sanitize_debug_logs() {
declare log_file=""
declare -a log_files=("${LOG_DEBUG:-}" "${LOG_VAR:-}" "${LOG_ERROR:-}")
set +x
if [[ -e "/proc/$$/fd/42" || -e "/dev/fd/42" ]]; then
exec 42>&-
fi
for log_file in "${log_files[@]}"; do
sanitize_debug_log "${log_file}" || return "${ERR_SANITIZING:-133}"
done
return 0
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f sanitize_debug_logs
# vim: number et ts=2 sw=2 sts=2 ai tw=128 ft=sh
+384
View File
@@ -0,0 +1,384 @@
#!/bin/bash
# SPDX-Version: 3.0
# SPDX-CreationInfo: 2026-06-11; WEIDNER, Marc S.; <msw@coresecret.dev>
# SPDX-ExternalRef: GIT https://git.coresecret.dev/msw/CISS.debian.live.builder.git
# SPDX-FileContributor: WEIDNER, Marc S.; Centurion Intelligence Consulting Agency
# SPDX-FileCopyrightText: 2024-2026; WEIDNER, Marc S.; <msw@coresecret.dev>
# SPDX-FileType: SOURCE
# SPDX-License-Identifier: LicenseRef-CNCL-1.1 OR LicenseRef-CCLA-1.1
# SPDX-LicenseComment: This file is part of the CISS.debian.installer.secure framework.
# SPDX-PackageName: CISS.debian.live.builder
# SPDX-Security-Contact: security@coresecret.eu
guard_sourcing || return "${ERR_GUARD_SRCE}"
if ! declare -p _ARY_SECRET_REDACTION_VALUES >/dev/null 2>&1; then
declare -ga _ARY_SECRET_REDACTION_VALUES=()
fi
#######################################
# Runs GNU stat on Debian and gstat on macOS development hosts.
# Arguments:
# stat arguments
# Returns:
# stat exit status
#######################################
secure_stat() {
if command -v gstat >/dev/null 2>&1; then
gstat "$@"
else
stat "$@"
fi
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f secure_stat
#######################################
# Prints a generic secret validation error without disclosing a path or value.
# Arguments:
# 1: unsafe input class
# 2: quiet flag
# Returns:
# ERR_SECRET_PATH
#######################################
secret_validation_error() {
declare error_class="$1" quiet="${2:-false}"
if [[ "${quiet}" != "true" ]]; then
printf "\e[91m❌ Unsafe secret input rejected: %s. \e[0m\n" "${error_class}" >&2
fi
return "${ERR_SECRET_PATH:-216}"
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f secret_validation_error
#######################################
# Registers an exact known secret value for final log redaction.
# Globals:
# _ARY_SECRET_REDACTION_VALUES
# Arguments:
# 1: secret value
# Returns:
# 0: on success
#######################################
register_secret_value() {
declare secret_value="$1" registered_value="" was_traced="false"
[[ $- == *x* ]] && was_traced="true"
set +x
if [[ -n "${secret_value}" ]]; then
for registered_value in "${_ARY_SECRET_REDACTION_VALUES[@]}"; do
if [[ "${registered_value}" == "${secret_value}" ]]; then
[[ "${was_traced}" == "true" ]] && set -x
return 0
fi
done
_ARY_SECRET_REDACTION_VALUES+=("${secret_value}")
fi
[[ "${was_traced}" == "true" ]] && set -x
return 0
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f register_secret_value
#######################################
# Registers exact text values from a controlled secret file.
# Globals:
# _ARY_SECRET_REDACTION_VALUES
# Arguments:
# 1: secret file
# Returns:
# 0: on success
#######################################
register_secret_file_for_redaction() {
declare secret_file="$1" secret_line="" secret_text="" was_traced="false"
[[ $- == *x* ]] && was_traced="true"
set +x
secret_text="$(cat "${secret_file}" || exit $?; printf '.')" || {
[[ "${was_traced}" == "true" ]] && set -x
return "${ERR_SANITIZING:-133}"
}
secret_text="${secret_text%.}"
register_secret_value "${secret_text}"
while IFS= read -r secret_line || [[ -n "${secret_line}" ]]; do
register_secret_value "${secret_line}"
done < "${secret_file}"
[[ "${was_traced}" == "true" ]] && set -x
return 0
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f register_secret_file_for_redaction
#######################################
# Validates a filename-only secret argument.
# Arguments:
# 1: filename
# 2: input class
# Returns:
# 0: on success
# ERR_SECRET_PATH: on failure
#######################################
validate_secret_filename() {
declare filename="$1" input_class="${2:-filename-only secret argument}"
declare filename_regex='^[A-Za-z0-9._@%+=:,~-]+$'
if [[ -z "${filename}" || "${filename}" == "." || "${filename}" == ".." || "${filename}" == */* \
|| ! "${filename}" =~ ${filename_regex} ]]; then
secret_validation_error "${input_class}"
return "${ERR_SECRET_PATH:-216}"
fi
return 0
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f validate_secret_filename
#######################################
# Validates a restrictively permissioned secret directory.
# Arguments:
# 1: directory path
# 2: input class
# 3: require tmpfs
# 4: quiet flag
# Returns:
# 0: on success
# ERR_SECRET_PATH: on failure
#######################################
validate_secret_directory() {
declare directory="$1" input_class="${2:-secret directory}" require_tmpfs="${3:-false}" quiet="${4:-false}"
declare fs_type="" mode="" owner=""
if [[ -z "${directory}" || -L "${directory}" || ! -d "${directory}" ]]; then
secret_validation_error "${input_class}" "${quiet}"
return "${ERR_SECRET_PATH:-216}"
fi
owner="$(secure_stat -c '%u' "${directory}" 2>/dev/null)" || {
secret_validation_error "${input_class} ownership" "${quiet}"
return "${ERR_SECRET_PATH:-216}"
}
mode="$(secure_stat -c '%a' "${directory}" 2>/dev/null)" || {
secret_validation_error "${input_class} permissions" "${quiet}"
return "${ERR_SECRET_PATH:-216}"
}
if [[ "${owner}" != "${EUID}" || "${mode}" != "700" ]]; then
secret_validation_error "${input_class} ownership or permissions" "${quiet}"
return "${ERR_SECRET_PATH:-216}"
fi
if [[ "${require_tmpfs}" == "true" ]]; then
fs_type="$(secure_stat -f -c '%T' "${directory}" 2>/dev/null)" || {
secret_validation_error "${input_class} filesystem" "${quiet}"
return "${ERR_SECRET_PATH:-216}"
}
if [[ "${fs_type}" != "tmpfs" && "${fs_type}" != "ramfs" ]]; then
secret_validation_error "${input_class} is not tmpfs-backed" "${quiet}"
return "${ERR_SECRET_PATH:-216}"
fi
fi
return 0
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f validate_secret_directory
#######################################
# Validates and registers a secret file.
# Arguments:
# 1: file path
# 2: input class
# 3: register for redaction
# 4: quiet flag
# Returns:
# 0: on success
# ERR_SECRET_PATH: on failure
#######################################
validate_secret_file() {
declare secret_file="$1" input_class="${2:-secret file}" register_value="${3:-true}" quiet="${4:-false}"
declare link_count="" mode="" owner=""
if [[ -z "${secret_file}" || -L "${secret_file}" || ! -f "${secret_file}" ]]; then
secret_validation_error "${input_class}" "${quiet}"
return "${ERR_SECRET_PATH:-216}"
fi
owner="$(secure_stat -c '%u' "${secret_file}" 2>/dev/null)" || {
secret_validation_error "${input_class} ownership" "${quiet}"
return "${ERR_SECRET_PATH:-216}"
}
mode="$(secure_stat -c '%a' "${secret_file}" 2>/dev/null)" || {
secret_validation_error "${input_class} permissions" "${quiet}"
return "${ERR_SECRET_PATH:-216}"
}
link_count="$(secure_stat -c '%h' "${secret_file}" 2>/dev/null)" || {
secret_validation_error "${input_class} link count" "${quiet}"
return "${ERR_SECRET_PATH:-216}"
}
if [[ "${owner}" != "${EUID}" || "${link_count}" != "1" || ( "${mode}" != "400" && "${mode}" != "600" ) ]]; then
secret_validation_error "${input_class} ownership, permissions, or link count" "${quiet}"
return "${ERR_SECRET_PATH:-216}"
fi
if [[ "${register_value}" == "true" ]]; then
register_secret_file_for_redaction "${secret_file}"
fi
return 0
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f validate_secret_file
#######################################
# Validates an explicitly supported absolute secret file path.
# Arguments:
# 1: file path
# 2: input class
# Returns:
# 0: on success
# ERR_SECRET_PATH: on failure
#######################################
validate_secret_absolute_file() {
declare secret_file="$1" input_class="${2:-absolute secret file}" resolved_file=""
if [[ -z "${secret_file}" || "${secret_file}" != /* ]]; then
secret_validation_error "${input_class} must be an absolute path"
return "${ERR_SECRET_PATH:-216}"
fi
resolved_file="$(realpath "${secret_file}" 2>/dev/null)" || {
secret_validation_error "${input_class} cannot be resolved"
return "${ERR_SECRET_PATH:-216}"
}
if [[ "${resolved_file}" != "${secret_file}" ]]; then
secret_validation_error "${input_class} must be canonical and must not traverse symlinked parents"
return "${ERR_SECRET_PATH:-216}"
fi
validate_secret_file "${secret_file}" "${input_class}" || return "${ERR_SECRET_PATH:-216}"
return 0
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f validate_secret_absolute_file
#######################################
# Validates an explicitly supported absolute secret directory path.
# Arguments:
# 1: directory path
# 2: input class
# Returns:
# 0: on success
# ERR_SECRET_PATH: on failure
#######################################
validate_secret_absolute_directory() {
declare directory="$1" input_class="${2:-absolute secret directory}" resolved_directory=""
if [[ -z "${directory}" || "${directory}" != /* ]]; then
secret_validation_error "${input_class} must be an absolute path"
return "${ERR_SECRET_PATH:-216}"
fi
resolved_directory="$(realpath "${directory}" 2>/dev/null)" || {
secret_validation_error "${input_class} cannot be resolved"
return "${ERR_SECRET_PATH:-216}"
}
if [[ "${resolved_directory}" != "${directory}" ]]; then
secret_validation_error "${input_class} must be canonical and must not traverse symlinked parents"
return "${ERR_SECRET_PATH:-216}"
fi
validate_secret_directory "${directory}" "${input_class}" || return "${ERR_SECRET_PATH:-216}"
return 0
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f validate_secret_absolute_directory
#######################################
# Validates a filename-only secret file below the fixed secret root.
# Globals:
# VAR_TMP_SECRET
# Arguments:
# 1: filename
# 2: input class
# 3: register for redaction
# Returns:
# 0: on success
# ERR_SECRET_PATH: on failure
#######################################
validate_secret_file_in_root() {
declare filename="$1" input_class="${2:-secret file}" register_value="${3:-true}"
validate_secret_directory "${VAR_TMP_SECRET}" "secret root" "true" || return "${ERR_SECRET_PATH:-216}"
validate_secret_filename "${filename}" "${input_class} filename" || return "${ERR_SECRET_PATH:-216}"
validate_secret_file "${VAR_TMP_SECRET}/${filename}" "${input_class}" "${register_value}" || return "${ERR_SECRET_PATH:-216}"
return 0
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f validate_secret_file_in_root
#######################################
# Validates the fixed tmpfs secret staging area and all entries below it.
# Globals:
# VAR_TMP_SECRET
# Arguments:
# 1: quiet flag
# Returns:
# 0: on success
# ERR_SECRET_PATH: on failure
#######################################
validate_secret_staging_area() {
declare quiet="${1:-false}" secret_entries_file="" secret_entry=""
declare -a secret_entries=()
validate_secret_directory "${VAR_TMP_SECRET}" "secret root" "true" "${quiet}" || return "${ERR_SECRET_PATH:-216}"
secret_entries_file="$(mktemp)" || return "${ERR_SECRET_PATH:-216}"
if ! find "${VAR_TMP_SECRET}" -xdev -mindepth 1 -print0 >| "${secret_entries_file}"; then
rm -f "${secret_entries_file}"
secret_validation_error "secret-root enumeration failed" "${quiet}"
return "${ERR_SECRET_PATH:-216}"
fi
mapfile -d '' -t secret_entries < "${secret_entries_file}"
rm -f "${secret_entries_file}"
for secret_entry in "${secret_entries[@]}"; do
if [[ -L "${secret_entry}" ]]; then
secret_validation_error "symlink below secret root" "${quiet}"
return "${ERR_SECRET_PATH:-216}"
elif [[ -d "${secret_entry}" ]]; then
validate_secret_directory "${secret_entry}" "directory below secret root" "false" "${quiet}" \
|| return "${ERR_SECRET_PATH:-216}"
elif [[ -f "${secret_entry}" ]]; then
validate_secret_file "${secret_entry}" "file below secret root" "true" "${quiet}" || return "${ERR_SECRET_PATH:-216}"
else
secret_validation_error "non-regular entry below secret root" "${quiet}"
return "${ERR_SECRET_PATH:-216}"
fi
done
return 0
}
### Prevents accidental 'unset -f'.
# shellcheck disable=SC2034
readonly -f validate_secret_staging_area
# vim: number et ts=2 sw=2 sts=2 ai tw=128 ft=sh