487d2b3ba8
🛡️ Retrieve DNSSEC status of coresecret.dev. / 🛡️ Retrieve DNSSEC status of coresecret.dev. (push) Has been cancelled
🛡️ Shell Script Linting / 🛡️ Shell Script Linting (push) Has been cancelled
💙 Generating a PUBLIC Live ISO. / 💙 Generating a PUBLIC Live ISO. (push) Has been cancelled
🔐 Generating a Private Live ISO TRIXIE. / 🔐 Generating a Private Live ISO TRIXIE. (push) Has been cancelled
Signed-off-by: Marc S. Weidner <msw@coresecret.dev>
407 lines
12 KiB
Bash
407 lines
12 KiB
Bash
#!/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
|
|
# shellcheck disable=SC2154
|
|
|
|
guard_sourcing || return "${ERR_GUARD_SRCE}"
|
|
|
|
#######################################
|
|
# Print a validation error without echoing secret values.
|
|
# Globals:
|
|
# None
|
|
# Arguments:
|
|
# 1: Validation label.
|
|
# 2: Error detail.
|
|
# Returns:
|
|
# 0: on success
|
|
#######################################
|
|
secret_validation_error() {
|
|
declare label="$1" detail="$2"
|
|
|
|
printf "\e[91mERROR: %s: %s\e[0m\n" "${label}" "${detail}" >&2
|
|
|
|
return 0
|
|
}
|
|
### Prevents accidental 'unset -f'.
|
|
# shellcheck disable=SC2034
|
|
readonly -f secret_validation_error
|
|
|
|
#######################################
|
|
# Validate a filename-only secret argument.
|
|
# Globals:
|
|
# ERR_ARG_MSMTCH
|
|
# Arguments:
|
|
# 1: Validation label.
|
|
# 2: Filename.
|
|
# Returns:
|
|
# 0: on success
|
|
# ERR_ARG_MSMTCH: on failure
|
|
#######################################
|
|
validate_secret_filename() {
|
|
declare label="$1" filename="$2"
|
|
declare filename_regex='^[A-Za-z0-9._@%+=:,~-]+$'
|
|
|
|
if [[ -z "${filename}" ]]; then
|
|
secret_validation_error "${label}" "filename MUST NOT be empty."
|
|
return "${ERR_ARG_MSMTCH}"
|
|
fi
|
|
|
|
if [[ "${filename}" == "." || "${filename}" == ".." ]]; then
|
|
secret_validation_error "${label}" "filename MUST NOT be '.' or '..'."
|
|
return "${ERR_ARG_MSMTCH}"
|
|
fi
|
|
|
|
if [[ "${filename}" == -* ]]; then
|
|
secret_validation_error "${label}" "filename MUST NOT start with '-'."
|
|
return "${ERR_ARG_MSMTCH}"
|
|
fi
|
|
|
|
if [[ "${filename}" == */* || "${filename}" == *\\* ]]; then
|
|
secret_validation_error "${label}" "filename MUST NOT contain path separators."
|
|
return "${ERR_ARG_MSMTCH}"
|
|
fi
|
|
|
|
if [[ ! "${filename}" =~ ${filename_regex} ]]; then
|
|
secret_validation_error "${label}" "filename contains unsupported characters."
|
|
return "${ERR_ARG_MSMTCH}"
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
### Prevents accidental 'unset -f'.
|
|
# shellcheck disable=SC2034
|
|
readonly -f validate_secret_filename
|
|
|
|
#######################################
|
|
# Validate the fixed tmpfs secret root.
|
|
# Globals:
|
|
# ERR_INVLD_CHAR
|
|
# Arguments:
|
|
# 1: Secret root path.
|
|
# Returns:
|
|
# 0: on success
|
|
# ERR_INVLD_CHAR: on failure
|
|
#######################################
|
|
validate_secret_root() {
|
|
declare secret_root="$1" root_owner="" root_mode="" root_mode_octal="" root_fs=""
|
|
|
|
if [[ -z "${secret_root}" ]]; then
|
|
secret_validation_error "secret root" "path MUST NOT be empty."
|
|
return "${ERR_INVLD_CHAR}"
|
|
fi
|
|
|
|
if [[ -L "${secret_root}" ]]; then
|
|
secret_validation_error "secret root" "path MUST NOT be a symlink."
|
|
return "${ERR_INVLD_CHAR}"
|
|
fi
|
|
|
|
if [[ ! -d "${secret_root}" ]]; then
|
|
secret_validation_error "secret root" "path MUST be an existing directory."
|
|
return "${ERR_INVLD_CHAR}"
|
|
fi
|
|
|
|
root_owner="$(stat -c '%u:%g' -- "${secret_root}")"
|
|
if [[ "${root_owner}" != "0:0" ]]; then
|
|
secret_validation_error "secret root" "directory MUST be owned by root:root."
|
|
return "${ERR_INVLD_CHAR}"
|
|
fi
|
|
|
|
root_mode="$(stat -c '%a' -- "${secret_root}")"
|
|
root_mode_octal=$((8#${root_mode}))
|
|
if (( (root_mode_octal & 077) != 0 || (root_mode_octal & 0700) != 0700 )); then
|
|
secret_validation_error "secret root" "directory mode MUST be 0700 or stricter."
|
|
return "${ERR_INVLD_CHAR}"
|
|
fi
|
|
|
|
root_fs="$(stat -f -c '%T' -- "${secret_root}")"
|
|
if [[ "${root_fs}" != "tmpfs" && "${root_fs}" != "ramfs" ]]; then
|
|
secret_validation_error "secret root" "directory MUST be backed by tmpfs or ramfs."
|
|
return "${ERR_INVLD_CHAR}"
|
|
fi
|
|
|
|
# shellcheck disable=SC2312
|
|
if find "${secret_root}" -xdev -type l -print -quit | grep -q .; then
|
|
secret_validation_error "secret root" "secret tree MUST NOT contain symlinks."
|
|
return "${ERR_INVLD_CHAR}"
|
|
fi
|
|
|
|
# shellcheck disable=SC2312
|
|
if find "${secret_root}" -xdev \( -type b -o -type c -o -type p -o -type s \) -print -quit | grep -q .; then
|
|
secret_validation_error "secret root" "secret tree MUST NOT contain special files."
|
|
return "${ERR_INVLD_CHAR}"
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
### Prevents accidental 'unset -f'.
|
|
# shellcheck disable=SC2034
|
|
readonly -f validate_secret_root
|
|
|
|
#######################################
|
|
# Normalize ownership and mode of regular files in the secret root.
|
|
# Globals:
|
|
# ERR_INVLD_CHAR
|
|
# Arguments:
|
|
# 1: Secret root path.
|
|
# Returns:
|
|
# 0: on success
|
|
# ERR_INVLD_CHAR: on failure
|
|
#######################################
|
|
harden_secret_root_files() {
|
|
declare secret_root="$1"
|
|
|
|
validate_secret_root "${secret_root}" || return "$?"
|
|
|
|
find "${secret_root}" -xdev -type f -exec chown root:root -- {} +
|
|
find "${secret_root}" -xdev -type f -exec chmod 0400 -- {} +
|
|
|
|
return 0
|
|
}
|
|
### Prevents accidental 'unset -f'.
|
|
# shellcheck disable=SC2034
|
|
readonly -f harden_secret_root_files
|
|
|
|
#######################################
|
|
# Validate that an existing absolute path is a regular non-symlink file.
|
|
# Globals:
|
|
# ERR_INVLD_CHAR
|
|
# Arguments:
|
|
# 1: Validation label.
|
|
# 2: File path.
|
|
# Returns:
|
|
# 0: on success
|
|
# ERR_INVLD_CHAR: on failure
|
|
#######################################
|
|
validate_secret_absolute_file_basics() {
|
|
declare label="$1" file_path="$2"
|
|
|
|
if [[ -z "${file_path}" ]]; then
|
|
secret_validation_error "${label}" "file path MUST NOT be empty."
|
|
return "${ERR_INVLD_CHAR}"
|
|
fi
|
|
|
|
if [[ "${file_path}" != /* ]]; then
|
|
secret_validation_error "${label}" "file path MUST be absolute."
|
|
return "${ERR_INVLD_CHAR}"
|
|
fi
|
|
|
|
if [[ -L "${file_path}" ]]; then
|
|
secret_validation_error "${label}" "file MUST NOT be a symlink."
|
|
return "${ERR_INVLD_CHAR}"
|
|
fi
|
|
|
|
if [[ ! -f "${file_path}" ]]; then
|
|
secret_validation_error "${label}" "file MUST be an existing regular file."
|
|
return "${ERR_INVLD_CHAR}"
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
### Prevents accidental 'unset -f'.
|
|
# shellcheck disable=SC2034
|
|
readonly -f validate_secret_absolute_file_basics
|
|
|
|
#######################################
|
|
# Validate a strict secret file.
|
|
# Globals:
|
|
# ERR_INVLD_CHAR
|
|
# Arguments:
|
|
# 1: Validation label.
|
|
# 2: File path.
|
|
# Returns:
|
|
# 0: on success
|
|
# ERR_INVLD_CHAR: on failure
|
|
#######################################
|
|
validate_secret_file_path() {
|
|
declare label="$1" file_path="$2" file_owner="" file_mode="" file_mode_octal=""
|
|
|
|
validate_secret_absolute_file_basics "${label}" "${file_path}" || return "$?"
|
|
|
|
file_owner="$(stat -c '%u:%g' -- "${file_path}")"
|
|
if [[ "${file_owner}" != "0:0" ]]; then
|
|
secret_validation_error "${label}" "file MUST be owned by root:root."
|
|
return "${ERR_INVLD_CHAR}"
|
|
fi
|
|
|
|
file_mode="$(stat -c '%a' -- "${file_path}")"
|
|
file_mode_octal=$((8#${file_mode}))
|
|
if (( (file_mode_octal & 077) != 0 || (file_mode_octal & 0400) != 0400 )); then
|
|
secret_validation_error "${label}" "file mode MUST allow root read and no group/other access."
|
|
return "${ERR_INVLD_CHAR}"
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
### Prevents accidental 'unset -f'.
|
|
# shellcheck disable=SC2034
|
|
readonly -f validate_secret_file_path
|
|
|
|
#######################################
|
|
# Validate a filename-only secret stored below the fixed secret root.
|
|
# Globals:
|
|
# VAR_TMP_SECRET
|
|
# Arguments:
|
|
# 1: Validation label.
|
|
# 2: Filename.
|
|
# Returns:
|
|
# 0: on success
|
|
# Non-zero: on failure
|
|
#######################################
|
|
validate_secret_file_in_root() {
|
|
declare label="$1" filename="$2"
|
|
|
|
validate_secret_filename "${label}" "${filename}" || return "$?"
|
|
validate_secret_file_path "${label}" "${VAR_TMP_SECRET}/${filename}" || return "$?"
|
|
|
|
return 0
|
|
}
|
|
### Prevents accidental 'unset -f'.
|
|
# shellcheck disable=SC2034
|
|
readonly -f validate_secret_file_in_root
|
|
|
|
#######################################
|
|
# Validate a public input file copied into the ISO.
|
|
# Globals:
|
|
# ERR_INVLD_CHAR
|
|
# Arguments:
|
|
# 1: Validation label.
|
|
# 2: File path.
|
|
# Returns:
|
|
# 0: on success
|
|
# ERR_INVLD_CHAR: on failure
|
|
#######################################
|
|
validate_public_input_file() {
|
|
declare label="$1" file_path="$2" file_owner="" file_mode="" file_mode_octal=""
|
|
|
|
validate_secret_absolute_file_basics "${label}" "${file_path}" || return "$?"
|
|
|
|
file_owner="$(stat -c '%u:%g' -- "${file_path}")"
|
|
if [[ "${file_owner}" != "0:0" ]]; then
|
|
secret_validation_error "${label}" "file MUST be owned by root:root."
|
|
return "${ERR_INVLD_CHAR}"
|
|
fi
|
|
|
|
file_mode="$(stat -c '%a' -- "${file_path}")"
|
|
file_mode_octal=$((8#${file_mode}))
|
|
if (( (file_mode_octal & 022) != 0 )); then
|
|
secret_validation_error "${label}" "file MUST NOT be group- or world-writable."
|
|
return "${ERR_INVLD_CHAR}"
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
### Prevents accidental 'unset -f'.
|
|
# shellcheck disable=SC2034
|
|
readonly -f validate_public_input_file
|
|
|
|
#######################################
|
|
# Validate the authorized_keys directory.
|
|
# Globals:
|
|
# ERR_INVLD_CHAR
|
|
# Arguments:
|
|
# 1: Directory path.
|
|
# Returns:
|
|
# 0: on success
|
|
# ERR_INVLD_CHAR: on failure
|
|
#######################################
|
|
validate_ssh_pubkey_directory() {
|
|
declare key_dir="$1" key_file="" dir_owner="" dir_mode="" dir_mode_octal=""
|
|
|
|
if [[ -z "${key_dir}" ]]; then
|
|
secret_validation_error "--ssh-pubkey" "directory path MUST NOT be empty."
|
|
return "${ERR_INVLD_CHAR}"
|
|
fi
|
|
|
|
if [[ "${key_dir}" != /* ]]; then
|
|
secret_validation_error "--ssh-pubkey" "directory path MUST be absolute."
|
|
return "${ERR_INVLD_CHAR}"
|
|
fi
|
|
|
|
if [[ -L "${key_dir}" ]]; then
|
|
secret_validation_error "--ssh-pubkey" "directory MUST NOT be a symlink."
|
|
return "${ERR_INVLD_CHAR}"
|
|
fi
|
|
|
|
if [[ ! -d "${key_dir}" ]]; then
|
|
secret_validation_error "--ssh-pubkey" "directory MUST exist."
|
|
return "${ERR_INVLD_CHAR}"
|
|
fi
|
|
|
|
dir_owner="$(stat -c '%u:%g' -- "${key_dir}")"
|
|
if [[ "${dir_owner}" != "0:0" ]]; then
|
|
secret_validation_error "--ssh-pubkey" "directory MUST be owned by root:root."
|
|
return "${ERR_INVLD_CHAR}"
|
|
fi
|
|
|
|
dir_mode="$(stat -c '%a' -- "${key_dir}")"
|
|
dir_mode_octal=$((8#${dir_mode}))
|
|
if (( (dir_mode_octal & 022) != 0 )); then
|
|
secret_validation_error "--ssh-pubkey" "directory MUST NOT be group- or world-writable."
|
|
return "${ERR_INVLD_CHAR}"
|
|
fi
|
|
|
|
key_file="${key_dir}/authorized_keys"
|
|
validate_public_input_file "--ssh-pubkey authorized_keys" "${key_file}" || return "$?"
|
|
|
|
return 0
|
|
}
|
|
### Prevents accidental 'unset -f'.
|
|
# shellcheck disable=SC2034
|
|
readonly -f validate_ssh_pubkey_directory
|
|
|
|
#######################################
|
|
# Validate all selected secret inputs after argument parsing.
|
|
# Globals:
|
|
# VAR_AGE
|
|
# VAR_AGE_KEY
|
|
# VAR_LUKS
|
|
# VAR_LUKS_KEY
|
|
# VAR_SIGNER
|
|
# VAR_SIGNING_CA
|
|
# VAR_SIGNING_KEY
|
|
# VAR_SIGNING_KEY_PASS
|
|
# VAR_SSHPUBKEY
|
|
# VAR_TMP_SECRET
|
|
# Arguments:
|
|
# None
|
|
# Returns:
|
|
# 0: on success
|
|
# Non-zero: on failure
|
|
#######################################
|
|
validate_selected_secret_inputs() {
|
|
if [[ "${VAR_AGE,,}" == "true" ]]; then
|
|
validate_secret_file_in_root "--key_age" "${VAR_AGE_KEY}" || return "$?"
|
|
fi
|
|
|
|
if [[ "${VAR_LUKS,,}" == "true" ]]; then
|
|
validate_secret_file_in_root "--key_luks" "${VAR_LUKS_KEY}" || return "$?"
|
|
fi
|
|
|
|
if [[ "${VAR_SIGNER,,}" == "true" ]]; then
|
|
validate_secret_file_in_root "--signing_key" "${VAR_SIGNING_KEY}" || return "$?"
|
|
validate_secret_file_in_root "--signing_key_pass" "${VAR_SIGNING_KEY_PASS}" || return "$?"
|
|
fi
|
|
|
|
if [[ -n "${VAR_SIGNING_CA:-}" ]]; then
|
|
validate_secret_file_in_root "--signing_ca" "${VAR_SIGNING_CA}" || return "$?"
|
|
fi
|
|
|
|
if [[ -n "${VAR_SSHPUBKEY:-}" ]]; then
|
|
validate_ssh_pubkey_directory "${VAR_SSHPUBKEY}" || return "$?"
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
### Prevents accidental 'unset -f'.
|
|
# shellcheck disable=SC2034
|
|
readonly -f validate_selected_secret_inputs
|
|
# vim: number et ts=2 sw=2 sts=2 ai tw=128 ft=sh
|