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

Signed-off-by: Marc S. Weidner <msw@coresecret.dev>
This commit is contained in:
2025-09-10 10:37:51 +02:00
parent 92ef69cc2e
commit ba716d35d5
7 changed files with 395 additions and 67 deletions

View File

@@ -29,57 +29,144 @@ guard_sourcing
#######################################
accounts_setup() {
### Declare Arrays, HashMaps, and Variables.
declare -r var_logfile="/root/.ciss/cdi/log/4520_accounts_setup.log"
declare -i i=0
declare tmp_username="" tmp_fullname="" tmp_uid="" tmp_gid="" tmp_shell="" tmp_password="" tmp_sshpubkey="" tmp_sudo="" \
tmp_restricted=""
declare var_username="" var_fullname="" var_uid="" var_gid="" var_shell="" var_password="" var_sshpubkey="" var_sudo="" \
var_restricted="" var_chpasswd="" var_sshdir=""
declare tmp_username="" tmp_fullname="" tmp_uid="" tmp_gid="" tmp_shell="" tmp_password="" tmp_sshpubkey="" \
tmp_access_ssh="" tmp_access_tty="" tmp_auth_pwd="" tmp_2fa_ssh="" tmp_2fa_tty="" tmp_sudo="" tmp_restricted=""
declare var_username="" var_fullname="" var_uid="" var_gid="" var_shell="" var_password="" var_sshpubkey="" \
var_access_ssh="" var_access_tty="" var_auth_pwd="" var_2fa_ssh="" var_2fa_tty="" var_sudo="" var_restricted=""
declare var_chpasswd="" var_sshdir="" var_pam_login="/etc/pam.d/login"
chroot_logger "${TARGET}${var_logfile}"
### Prepare the '2fa'-seed variable.
read_totp_seed
do_log "debug" "file_only" "4520() Command: [read_totp_seed]"
### Preparing the root account
chown root:root "${TARGET}/etc/passwd" "${TARGET}/etc/shadow" "${TARGET}/etc/group" "${TARGET}/etc/gshadow"
chmod 0644 "${TARGET}/etc/passwd" "${TARGET}/etc/group"
chmod 0600 "${TARGET}/etc/shadow" "${TARGET}/etc/gshadow"
if [[ -x "${TARGET}${user_root_shell}" ]]; then
chroot_exec "${TARGET}" chsh -s "${user_root_shell}" root
else
chroot_exec "${TARGET}" chsh -s /bin/bash root
do_log "warn" "file_only" "4500() Shell: '${user_root_shell}' not found for: 'root'. Using '/bin/bash' instead."
fi
var_chpasswd="root:${user_root_password}"
chroot_script "${TARGET}" "echo \"${var_chpasswd}\" | chpasswd -e"
var_chpasswd=""
### 0) The 'root' account is generated via debootstrap by default.
### 1) Prepare the 'root' account.
install -d -m 0700 -o root -g root "${TARGET}/root/.ssh"
install -m 0600 -o root -g root /dev/null "${TARGET}/root/.ssh/authorized_keys"
grep -qxF "${user_root_sshpubkey}" "${TARGET}/root/.ssh/authorized_keys" || \
printf "%s\n" "${user_root_sshpubkey}" >> "${TARGET}/root/.ssh/authorized_keys"
if [[ "${user_root_authentication_access_ssh}" == "false" ]]; then
if grep -q '^\s*PermitRootLogin' "${TARGET}/etc/ssh/sshd_config"; then
sed -i 's/^\s*PermitRootLogin\s\+.*/PermitRootLogin no/' "${TARGET}/etc/ssh/sshd_config"
else
echo 'PermitRootLogin no' >> "${TARGET}/etc/ssh/sshd_config"
fi
fi
install -D -m 0600 -o root -g root "${VAR_SETUP_PATH}/includes/target/etc/skel/.bashrc" "${TARGET}/root/"
install -D -m 0600 -o root -g root "${VAR_SETUP_PATH}/includes/target/etc/skel/.zshrc" "${TARGET}/root/"
if [[ "${user_root_shell}" == "/bin/zsh" ]]; then
if [[ -x "${TARGET}${user_root_shell}" ]]; then
chroot_exec "${TARGET}" chsh -s "${user_root_shell}" root
install -D -m 0600 -o root -g root "${VAR_SETUP_PATH}/includes/target/etc/skel/.zshrc" "${TARGET}/root/"
do_log "info" "file_only" "4520() Shell: '${user_root_shell}' used for: 'root'."
else
chroot_exec "${TARGET}" chsh -s /bin/bash root
do_log "info" "file_only" "4520() Shell: '${user_root_shell}' not found for: 'root'. Using '/bin/bash' instead."
fi
fi
install -D -m 0600 -o root -g root "${VAR_SETUP_PATH}/includes/target/root/.ciss/alias" "${TARGET}/root/.ciss/"
install -D -m 0700 -o root -g root "${VAR_SETUP_PATH}/includes/target/root/.ciss/clean_logout.sh" "${TARGET}/root/.ciss/"
install -D -m 0600 -o root -g root "${VAR_SETUP_PATH}/includes/target/root/.ciss/shortcuts" "${TARGET}/root/.ciss/"
# To be able to copy/paste from vim, one needs to create a '.vimrc' with the following content:
### To be able to copy/paste from vim, one needs to create a '.vimrc' with the following content:
echo 'set clipboard=unnamed' >| "${TARGET}/root/.vimrc"
chmod 0600 "${TARGET}/root/.vimrc"
do_log "info" "file_only" "4520() Skeleton: 'root' successfully generated."
### 2) Check SSH access capabilities.
case "${user_root_authentication_access_ssh}" in
false)
sed -i -E "s|^[[:space:]]*PermitRootLogin[[:space:]]+.*$|$(printf '%-29s%s' 'PermitRootLogin' 'no')|" "${TARGET}/etc/ssh/sshd_config"
do_log "info" "file_only" "4520() User: 'root' SSH access: [PermitRootLogin no]"
;;
true)
sed -i -E "s|^[[:space:]]*PermitRootLogin[[:space:]]+.*$|$(printf '%-29s%s' 'PermitRootLogin' 'prohibit-password')|" "${TARGET}/etc/ssh/sshd_config"
do_log "info" "file_only" "4520() User: 'root' SSH access: [PermitRootLogin prohibit-password]"
;;
esac
### 3) Check tty access capabilities.
case "${user_root_authentication_access_tty}" in
false)
### 1) Ensure the 'pam_access' line is not activated in '/etc/pam.d/login' and '/etc/pam.d/sshd' in parallel.
pam_access_sync_login_sshd
### 2) Ensure 'pam_securetty' in the auth phase; requisite causes immediate fail for disallowed ttys.
if ! grep -qE '^\s*auth\s+requisite\s+pam_securetty\.so' "${var_pam_login}"; then
### Insert pam_securetty before pam_unix to fail early.
awk '
BEGIN{ins=0}
{
if(!ins && $0 ~ /^\s*auth\s+.*pam_unix\.so/){
print "auth requisite pam_securetty.so"
ins=1
}
print
}
END{ if(!ins) print "auth requisite pam_securetty.so" }
' "${var_pam_login}" >| "${var_pam_login}.new" && mv -f "${var_pam_login}.new" "${var_pam_login}"
fi
### 3) Disallow all local access for root in '/etc/security/access.conf'.
printf "-: root:ALL \n" >> "${TARGET}/etc/security/access.conf"
### 4) Empty "/etc/securetty".
cat << 'EOF' >| "${TARGET}/etc/securetty"
EOF
do_log "info" "file_only" "4520() User: 'root' tty access: [false]"
;;
true)
### 1) Allow local access for 'root' only on 'tty1' in '/etc/security/access.conf'.
printf "+: root:tty1 \n" >> "${TARGET}/etc/security/access.conf"
### 2) Allow local access for 'root' only on 'tty1' in '/etc/securetty'.
cat << 'EOF' >| "${TARGET}/etc/securetty"
tty1
EOF
do_log "info" "file_only" "4520() User: 'root' tty access: [true]"
;;
esac
### Check the password policy for the 'root' account.
case "${user_root_authentication_password}" in
false)
chroot_script "${TARGET}" "passwd -l root"
do_log "info" "file_only" "4520() User: 'root' password access: [false]"
;;
true)
var_chpasswd="root:${user_root_password}"
chroot_script "${TARGET}" "echo \"${var_chpasswd}\" | chpasswd -e"
var_chpasswd=""
do_log "info" "file_only" "4520() User: 'root' password access: [true]"
;;
esac
### Update the 'root' SSH pubkey, if provided via 'preseed.yaml'.
if [[ -n "${user_root_sshpubkey:-}" ]]; then
printf "%s\n" "${user_root_sshpubkey}" >| "${TARGET}/root/.ssh/authorized_keys"
do_log "info" "file_only" "4520() User: 'root' SSH public key: inserted."
fi
### Update the 'root' 'totp'-policy and write the '.google_authenticator'-file.
[[ "${user_root_authentication_2fa_ssh}" == "true" || "${user_root_authentication_2fa_tty}" == "true" ]] && \
write_google_authenticator_file "root"
[[ "${user_root_authentication_2fa_ssh}" == "true" ]] && pam_access_totp_enable "root" "sshd"
[[ "${user_root_authentication_2fa_tty}" == "true" ]] && pam_access_totp_enable "root" "login"
do_log "info" "file_only" "User: 'root' updated."
### Install all user accounts.
### Iterate through all remaining 'user' accounts and install them.
for ((i = 0; i <= VAR_USER_MAX; i++)); do
### Prepare all user-variables.
tmp_username="user_user${i}_name"
tmp_fullname="user_user${i}_fullname"
tmp_uid="user_user${i}_uid"
@@ -87,6 +174,11 @@ accounts_setup() {
tmp_shell="user_user${i}_shell"
tmp_password="user_user${i}_password"
tmp_sshpubkey="user_user${i}_sshpubkey"
tmp_access_ssh="user_user${i}authentication_access_ssh"
tmp_access_tty="user_user${i}authentication_access_ssh"
tmp_auth_pwd="user_user${i}authentication_password"
tmp_2fa_ssh="user_user${i}authentication_2fa_ssh"
tmp_2fa_tty="user_user${i}authentication_2fa_tty"
tmp_sudo="user_user${i}_privileges_sudo"
tmp_restricted="user_user${i}_privileges_restricted"
@@ -97,12 +189,20 @@ accounts_setup() {
var_shell="${!tmp_shell}"
var_password="${!tmp_password}"
var_sshpubkey="${!tmp_sshpubkey}"
var_access_ssh"${!tmp_access_ssh}"
var_access_tty"${!tmp_access_tty}"
var_auth_pwd"${!tmp_auth_pwd}"
var_2fa_ssh"${!tmp_2fa_ssh}"
var_2fa_tty"${!tmp_2fa_tty}"
var_sudo="${!tmp_sudo}"
var_restricted="${!tmp_restricted}"
### 0) A) Check if the 'group' of the 'user' already exists.
chroot_exec "${TARGET}" getent group "${var_username}" >/dev/null || \
chroot_exec "${TARGET}" groupadd --gid "${var_gid}" "${var_username}"
### 0) B) Generates the user account.
### If the 'user' is not restricted in scope, then generate the account accordingly, with a predefined expiry date.
if [[ "${var_restricted}" == "false" ]]; then
chroot_exec "${TARGET}" useradd \
@@ -122,7 +222,6 @@ accounts_setup() {
--comment "${var_fullname}" \
--expiredate 2102-12-31 \
--gid "${var_gid}" \
--home-dir /home/"${var_username}" \
--inactive 0 \
--no-create-home \
--shell "${var_shell}" \
@@ -131,6 +230,43 @@ accounts_setup() {
fi
### 1) Prepare the 'user' account.
install -d -m 0700 -o "${var_uid}" -g "${var_gid}" "${TARGET}/home/${var_username}/.ssh"
install -m 0600 -o "${var_uid}" -g "${var_gid}" /dev/null "${TARGET}/home/${var_username}/.ssh/authorized_keys"
install -D -m 0600 -o "${var_uid}" -g "${var_gid}" "${VAR_SETUP_PATH}/includes/target/etc/skel/.bashrc" "${TARGET}/home/${var_username}/"
if [[ "${var_shell}" == "/bin/zsh" ]]; then
if [[ -x "${TARGET}${var_shell}" ]]; then
chroot_exec "${TARGET}" chsh -s "${var_shell}" "${var_username}"
do_log "info" "file_only" "4520() Shell: '${var_shell}' used for: '${var_username}'."
else
chroot_exec "${TARGET}" chsh -s /bin/bash "${var_username}"
do_log "info" "file_only" "4520() Shell: '${var_shell}' not found for: '${var_username}'. Using '/bin/bash' instead."
fi
fi
do_log "info" "file_only" "4520() Skeleton: '${var_username}' successfully generated."
### 2) Check SSH access capabilities.
# Nothing to do here as per user SSH capabilities are already handled in '4330_installation_ssh.sh'
var_chpasswd="${var_username}:${var_password}"
chroot_script "${TARGET}" "echo \"${var_chpasswd}\" | chpasswd -e"
var_chpasswd=""
@@ -152,6 +288,8 @@ accounts_setup() {
done
unset VAR_TEMP_PLAIN_MFA_SEED
guard_dir && return 0
@@ -181,7 +319,6 @@ write_google_authenticator_file() {
var_secret="$(generate_totp_secret "${var_user}")"
umask 0077
{
printf '%s\n' "${var_secret}"
printf '"RATE_LIMIT 3 30"\n'
@@ -191,14 +328,19 @@ write_google_authenticator_file() {
### Emergency Codes:
for i in {0..7}; do printf '%08d\n' "$(( RANDOM % 100000000 ))"; done
} >| "${var_base}/.google_authenticator"
guard_trace off
chown "${var_user}:${var_user}" "${var_base}/.google_authenticator"
chmod 0600 "${var_base}/.google_authenticator"
{
printf '%s\n' "${var_user}"
printf '%s\n' "${var_secret}"
} >| "${DIR_TMP}/TOTP_${var_user}.secret"
chmod 0400 "${DIR_TMP}/TOTP_${var_user}.secret"
umask 0022
unset var_secret
guard_trace off
return 0
}
@@ -233,9 +375,12 @@ generate_totp_secret() {
-kdfopt salt:"${var_salt}" -kdfopt info:"${var_info}" -binary HKDF | base32 | tr -d '=' | tr '[:lower:]' '[:upper:]'
)"
printf '%s\n' "${var_secret}"
unset var_secret
guard_trace off
printf '%s\n' "${var_secret}"
return 0
}
@@ -270,4 +415,125 @@ read_totp_seed(){
return 0
}
#######################################
# Ensure the 'pam_access' line is not activated in '/etc/pam.d/login' and '/etc/pam.d/sshd' in parallel.
# Arguments:
# None
# Returns:
# 0: on success
#######################################
pam_access_sync_login_sshd() {
declare var_file_login="/etc/pam.d/login"
declare var_file_sshd="/etc/pam.d/sshd"
### Guard: files must exist, no-op otherwise.
if [[ ! -f "${var_file_login}" ]]; then
return 0
fi
if [[ ! -f "${var_file_sshd}" ]]; then
: ### Still continue, only '/etc/pam.d/login' will be processed
fi
### 1) If the 'pam_access' line is commented in '/etc/pam.d/login', uncomment exactly one occurrence.
### Match lines like: [spaces]# [spaces]account required pam_access.so ...
if grep -Eq '^[[:space:]]*#[[:space:]]*account[[:space:]]+required[[:space:]]+pam_access\.so([[:space:]]|$)' "${var_file_login}"; then
awk '
BEGIN { done=0 }
{
if (!done && $0 ~ /^[[:space:]]*#[[:space:]]*account[[:space:]]+required[[:space:]]+pam_access\.so([[:space:]]|$)/) {
sub(/^[[:space:]]*#[[:space:]]*/, "", $0); # drop leading # and following spaces
done=1;
}
print;
}
' "${var_file_login}" >| "${var_file_login}.new"
mv -f "${var_file_login}.new" "${var_file_login}"
fi
### 2) If '/etc/pam.d/login' now has an active pam_access line, ensure '/etc/pam.d/sshd' pam_access line(s) are commented out.
if grep -Eq '^[[:space:]]*account[[:space:]]+required[[:space:]]+pam_access\.so([[:space:]]|$)' "${var_file_login}"; then
if [[ -f "${var_file_sshd}" ]]; then
awk '
### Comment only active matches; leave already-commented lines as-is.
/^[[:space:]]*account[[:space:]]+required[[:space:]]+pam_access\.so([[:space:]]|$)/ { print "# " $0; next }
{ print }
' "${var_file_sshd}" > "${var_file_sshd}.new"
mv -f "${var_file_sshd}.new" "${var_file_sshd}"
fi
fi
return 0
}
#######################################
# Enable per-user TOTP in a given PAM service (login, sshd, su, sudo).
# Arguments:
# 1: <username>
# 2: <pam_module>
# Returns:
# 0: on success
#######################################
pam_access_totp_enable() {
declare var_user="$1"
declare var_module="$2"
declare var_pam_file="/etc/pam.d/${var_module}"
declare var_users_file="${TARGET}/etc/ciss/2fa.users"
### Basic sanitation; module must be a safe 'pam.d' filename.
[[ -n "${var_user:-}" && -n "${var_module:-}" ]] || return 0
[[ "${var_module}" =~ ^[A-Za-z0-9._+-]+$ ]] || return 0
[[ -f "${var_pam_file}" ]] || return 0
### 0) Ensure the allowlist file contains the user (deduplicated).
if ! grep -Fxq "${var_user}" "${var_users_file}"; then
printf '%s\n' "${var_user}" >> "${var_users_file}"
fi
### 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 and uses nullok.
### We place it right after pam_unix.so or @include common-auth; fallback: append.
if ! grep -q '^# CISS TOTP START$' "${var_pam_file}"; then
awk -v START='# CISS TOTP START' -v END='# CISS TOTP END' '
BEGIN{ins=0}
{
print
if (!ins && ($0 ~ /^[[:space:]]*auth[[:space:]]+.*pam_unix\.so/ || $0 ~ /^[[:space:]]*@include[[:space:]]+common-auth/)) {
print START
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 nullok"
print END
ins=1
}
}
END{
if (!ins) {
print START
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 nullok"
print END
}
}
' "${var_pam_file}" > "${var_pam_file}.new" && mv -f "${var_pam_file}.new" "${var_pam_file}"
fi
### 2) Comment out any other active GA lines to avoid double prompts.
### We keep the CISS block intact (recognized by the START/END markers).
awk '
BEGIN{in_ciss=0}
/^# CISS TOTP START$/ { in_ciss=1; print; next }
/^# CISS TOTP END$/ { in_ciss=0; print; next }
{
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}"
return 0
}
# vim: number et ts=2 sw=2 sts=2 ai tw=128 ft=sh