V8.00.000.2025.06.17
All checks were successful
🛡️ Shell Script Linting / 🛡️ Shell Script Linting (push) Successful in 59s

Signed-off-by: Marc S. Weidner <msw@coresecret.dev>
This commit is contained in:
2025-09-14 13:49:38 +02:00
parent d9a1c926de
commit 175cfd0bff
2 changed files with 95 additions and 56 deletions

View File

@@ -154,7 +154,7 @@ chroot_script() {
} }
####################################### #######################################
# Run the installer-desired code via stdin inside the chroot with bash -s. # Run the installer-desired code incl. positional arguments via stdin (HEREDEC) inside the chroot with bash -s.
# Globals: # Globals:
# BASH_SOURCE # BASH_SOURCE
# TERM # TERM
@@ -163,9 +163,9 @@ chroot_script() {
# VAR_DEBUG_TRAP # VAR_DEBUG_TRAP
# VAR_IN_DIALOG_WR # VAR_IN_DIALOG_WR
# Arguments: # Arguments:
# 1: Target of the chroot environment # 1: Target of chroot environment
# 2: Command string to execute inside a shell (quoted) # 2: Command string to execute inside a shell (HEREDOC):
# 3: Log level of command pipeline to be executed. # chroot_stdin "${TARGET}" "__payload__" -- "${ARG1}" "${ARG2}" ... <<'EOF' ... EOF
# Returns: # Returns:
# 0: on success # 0: on success
# ERR_CHRT_COMMAND: on failure # ERR_CHRT_COMMAND: on failure
@@ -193,7 +193,7 @@ chroot_stdin() {
DEBIAN_FRONTEND="noninteractive" \ DEBIAN_FRONTEND="noninteractive" \
APT_LISTCHANGES_FRONTEND="none" \ APT_LISTCHANGES_FRONTEND="none" \
/bin/bash -o errexit -o errtrace -o functrace -o nounset -o pipefail \ /bin/bash -o errexit -o errtrace -o functrace -o nounset -o pipefail \
-O inherit_errexit -O failglob -O lastpipe -s -O inherit_errexit -O failglob -O lastpipe -s -- "$@"
then then

View File

@@ -401,10 +401,18 @@ generate_totp_secret() {
guard_trace on guard_trace on
### Derive 20 bytes via HKDF-SHA256 using OpenSSL 3 kdf, output as raw, then base32 (uppercase, no padding). ### Derive 20 bytes via HKDF-SHA256 using OpenSSL 3 kdf, output as raw, then base32 (uppercase, no padding).
### NOTE: 'key' must be provided via '-kdfopt key:hex:<STRING>'; expects a hexstring (no spaces).
# shellcheck disable=SC2312 # shellcheck disable=SC2312
var_secret="$( var_secret="$(
printf '%s' "${VAR_TEMP_PLAIN_MFA_SEED}" | xxd -r -p | openssl kdf -keylen 20 -kdfopt digest:SHA256 \ openssl kdf -keylen 20 \
-kdfopt salt:"${var_salt}" -kdfopt info:"${var_info}" -binary HKDF | base32 | tr -d '=' | tr '[:lower:]' '[:upper:]' -kdfopt digest:SHA256 \
-kdfopt key:hex:"${VAR_TEMP_PLAIN_MFA_SEED}" \
-kdfopt salt:"${var_salt}" \
-kdfopt info:"${var_info}" \
-binary HKDF \
| base32 \
| tr -d '=' \
| tr '[:lower:]' '[:upper:]'
)" )"
printf '%s\n' "${var_secret}" printf '%s\n' "${var_secret}"
@@ -427,31 +435,39 @@ hardening_su() {
### Declare Arrays, HashMaps, and Variables. ### Declare Arrays, HashMaps, and Variables.
declare -r pam_su="/etc/pam.d/su" declare -r pam_su="/etc/pam.d/su"
[[ -f "${pam_su}" ]] || return 0 [[ -f "${TARGET}${pam_su}" ]] || return 0
### If the pam_wheel line already exists with the group=sudo and use_uid, then do nothing. chroot_stdin "${TARGET}" "__payload__" -- "${pam_su}" <<'EOF'
if grep -Eq '^[[:space:]]*auth[[:space:]]+required[[:space:]]+pam_wheel\.so([[:space:]].*)?\bgroup=sudo\b([[:space:]].*)?\buse_uid\b' "${pam_su}"; then export LC_ALL=C
return 0 pam="$1"
fi
### Insert 'auth required pam_wheel.so use_uid group=sudo' before pam_unix/rootok (fail early). if grep -Eq '^[[:space:]]*auth[[:space:]]+required[[:space:]]+pam_wheel[.]so([[:space:]].*)?group=sudo([[:space:]].*)?use_uid' "${pam}"; then
:
else
tmp="$(mktemp "${pam}.XXXXXX")"
### 1) Insert rule before pam_unix.so or pam_rootok.so (fail early). Fallback: append.
awk ' awk '
BEGIN{ins=0} BEGIN { ins=0 }
{ {
### Insert just before the first pam_unix or pam_rootok auth line. if (!ins && $0 ~ /^[[:space:]]*auth[[:space:]]+.*pam_(unix|rootok)[.]so/ ) {
if (!ins && $0 ~ /^[[:space:]]*auth[[:space:]]+.*pam_(unix|rootok)\.so/) {
print "auth required pam_wheel.so use_uid group=sudo" print "auth required pam_wheel.so use_uid group=sudo"
ins=1 ins=1
} }
print print
} }
END{ END {
if (!ins) { if (!ins) {
### Fallback: append if no anchor found
print "auth required pam_wheel.so use_uid group=sudo" print "auth required pam_wheel.so use_uid group=sudo"
} }
} }
' "${pam_su}" > "${pam_su}.new" && mv -f "${pam_su}.new" "${pam_su}" ' "${pam}" >| "${tmp}"
test -s "${tmp}"
mv -f "${tmp}" "${pam}"
rm -f -- "${tmp}" || :
fi
:
EOF
return 0 return 0
} }
@@ -502,7 +518,7 @@ EOF
if [[ ! -f "${var_sudoers_winscp_global}" ]]; then if [[ ! -f "${var_sudoers_winscp_global}" ]]; then
cat << EOF > "${var_sudoers_winscp_global}" cat << EOF >| "${var_sudoers_winscp_global}"
### Added by CISS.debian.installer. WinSCP SFTP-as-root (least privilege). ### Added by CISS.debian.installer. WinSCP SFTP-as-root (least privilege).
### Allow exactly the sftp-server binary, optionally with -e (stderr logging). ### Allow exactly the sftp-server binary, optionally with -e (stderr logging).
Cmnd_Alias CISS_SFTPROOT = ${var_sftp_bin}, ${var_sftp_bin} -e Cmnd_Alias CISS_SFTPROOT = ${var_sftp_bin}, ${var_sftp_bin} -e
@@ -668,11 +684,12 @@ pam_access_totp_enable() {
declare var_module="$2" declare var_module="$2"
declare var_pam_file="/etc/pam.d/${var_module}" declare var_pam_file="/etc/pam.d/${var_module}"
declare var_users_file="${TARGET}/etc/ciss/2fa.users" declare var_users_file="${TARGET}/etc/ciss/2fa.users"
declare var_allowlist="/etc/ciss/2fa.users"
### Basic sanitation; module must be a safe 'pam.d' filename. ### Basic sanitation; module must be a safe 'pam.d' filename.
[[ -n "${var_user:-}" && -n "${var_module:-}" ]] || return 0 [[ -n "${var_user:-}" && -n "${var_module:-}" ]] || return 0
[[ "${var_module}" =~ ^[A-Za-z0-9._+-]+$ ]] || return 0 [[ "${var_module}" =~ ^[A-Za-z0-9._+-]+$ ]] || return 0
[[ -f "${var_pam_file}" ]] || return 0 [[ -f "${TARGET}${var_pam_file}" ]] || return 0
### 0) Ensure the allowlist file contains the user (deduplicated). ### 0) Ensure the allowlist file contains the user (deduplicated).
if ! grep -Fxq "${var_user}" "${var_users_file}"; then if ! grep -Fxq "${var_user}" "${var_users_file}"; then
@@ -682,44 +699,66 @@ pam_access_totp_enable() {
### 1) Ensure a single CISS TOTP framework block is present in the PAM file. ### 1) Ensure a single CISS TOTP framework block is present in the PAM file.
### The block gates GA by pam_listfile over '/etc/ciss/2fa.users'. ### The block gates GA by pam_listfile over '/etc/ciss/2fa.users'.
### We place it right after pam_unix.so or @include common-auth; fallback: append. ### We place it right after pam_unix.so or @include common-auth; fallback: append.
if ! grep -q '^# CISS TOTP START$' "${var_pam_file}"; then chroot_stdin "${TARGET}" "__payload__" -- "${var_pam_file}" "${var_allowlist}" <<'EOF'
awk -v START='# CISS TOTP START' -v END='# CISS TOTP END' ' export LC_ALL=C
BEGIN{ins=0} pam="$1"
{ allowlist="$2"
print tmp="$(mktemp "${pam}.XXXXXX")"
if (!ins && ($0 ~ /^[[:space:]]*auth[[:space:]]+.*pam_unix\.so/ || $0 ~ /^[[:space:]]*@include[[:space:]]+common-auth/)) {
print START awk -v START='# CISS TOTP START' -v END='# CISS TOTP END' -v allowlist="${allowlist}" '
print "auth [success=1 default=ignore] pam_listfile.so item=user sense=deny file=/etc/ciss/2fa.users onerr=ignore" BEGIN { ins=0 }
print "auth required pam_google_authenticator.so" {
print END print
ins=1 if (!ins && ($0 ~ /^[[:space:]]*auth[[:space:]]+.*pam_unix[.]so/ \
} || $0 ~ /^[[:space:]]*@include[[:space:]]+common-auth/)) {
} print START
END{ # Only users in allowlist must pass GA:
if (!ins) { # pam_listfile sense=deny succeeds for non-listed → skip next line (GA)
print START print "auth [success=1 default=ignore] pam_listfile.so item=user sense=deny file=" allowlist " onerr=ignore"
print "auth [success=1 default=ignore] pam_listfile.so item=user sense=allow file=/etc/ciss/2fa.users onerr=ignore" print "auth required pam_google_authenticator.so"
print "auth required pam_google_authenticator.so" print END
print END ins=1
} }
} }
' "${var_pam_file}" > "${var_pam_file}.new" && mv -f "${var_pam_file}.new" "${var_pam_file}" END {
fi if (!ins) {
print START
print "auth [success=1 default=ignore] pam_listfile.so item=user sense=deny file=" allowlist " onerr=ignore"
print "auth required pam_google_authenticator.so"
print END
}
}
' "${pam}" >| "${tmp}"
test -s "${tmp}"
mv -f "${tmp}" "${pam}"
rm -f -- "${tmp}" || :
:
EOF
### 2) Comment out any other active GA lines to avoid double prompts. ### 2) Comment out any other active GA lines to avoid double prompts.
### We keep the CISS block intact (recognized by the START/END markers). chroot_stdin "${TARGET}" "__payload__" -- "${var_pam_file}" <<'EOF'
awk ' export LC_ALL=C
BEGIN{in_ciss=0} pam="$1"
/^# CISS TOTP START$/ { in_ciss=1; print; next } tmp="$(mktemp "${pam}.XXXXXX")"
/^# CISS TOTP END$/ { in_ciss=0; print; next } awk '
{ BEGIN { in_ciss=0 }
if (!in_ciss && $0 ~ /^[[:space:]]*auth[[:space:]]+.*pam_google_authenticator\.so/ && $0 !~ /^[[:space:]]*#/) { /^# CISS TOTP START$/ { in_ciss=1; print; next }
print "# " $0 /^# CISS TOTP END$/ { in_ciss=0; print; next }
} else { {
print if (!in_ciss && $0 ~ /^[[:space:]]*auth[[:space:]]+.*pam_google_authenticator[.]so/ && $0 !~ /^[[:space:]]*#/) {
} print "# " $0
} else {
print
} }
' "${var_pam_file}" > "${var_pam_file}.new" && mv -f "${var_pam_file}.new" "${var_pam_file}" }
' "${pam}" >| "${tmp}"
test -s "${tmp}"
mv -f "${tmp}" "${pam}"
rm -f -- "${tmp}" || :
:
EOF
return 0 return 0
} }