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

@@ -672,6 +672,12 @@ software:
# ssh
#
##############################################################################################################################
# Installed by 4500_accounts_preparation.sh
##############################################################################################################################
# bash-completion
# fzf
#
##############################################################################################################################
# Installed by 4510_accounts_hardening.sh
##############################################################################################################################
# libpam-google-authenticator
@@ -684,12 +690,10 @@ software:
# core software
##############################################################################################################################
- apt-utils
- bash-completion
- bat
- debconf
- debconf-utils
- dialog
- fzf
- git
- knot-dnssecutils
- knot-dnsutils
@@ -807,34 +811,32 @@ user:
# root Superuser account (normally disabled for direct login)
##############################################################################################################################
root:
ensure: present # Must always be 'present'.
protected: true # Prevent unintentional edits or deletions.
ensure: present # Must always be 'present'. (Not in use in this version of the installer.)
protected: true # Prevent unintentional edits or deletions. (Not in use in this version of the installer.)
shell: /bin/zsh # Login shell (e.g., '/bin/bash', '/bin/zsh'); use '/usr/sbin/nologin' for non-interactive users.
password: "47110815"
sshpubkey: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINAYZDAqVZUk3LwJsqeVHKvLn8UKkFx642VBbiSS8uSY 2025_ciss.debian.live.ISO_PUBLIC_ONLY"
authentication:
access:
ssh: false # Allow SSH access.
tty: true # Allow TTY (local console) login.
password:
ssh: false # Allow SSH password login.
tty: true # Allow TTY (local console) password login.
tty: false # Allow TTY (local console) login.
password: false # Allow password login. SSH password login is always disabled.
2fa:
ssh: false # Require 2FA for SSH access.
ssh: true # Require 2FA for SSH access.
tty: true # Require 2FA for TTY (local console) login.
privileges:
description: "Root user with full system access and administrative privileges."
sudo: false # Whether the user can escalate to root using sudo.
system: true # Whether this is a low-UID system user (e.g., for automation).
restricted: false # If true, the user is limited in scope (e.g., no login, no file access, --no-create-home)
shell: true # MUST be "true" if the shell is not '/usr/sbin/nologin' or '/bin/false'.
sudo: false # Whether the user can escalate to root using sudo.
system: true # Whether this is a low-UID system user (e.g., for automation).
##############################################################################################################################
# Primary administrative user with full sudo access
##############################################################################################################################
user0:
ensure: present # "present" = create user; "absent" = remove user
protected: true # Prevent unintentional edits or deletions.
ensure: present # Must always be 'present'. (Not in use in this version of the installer.)
protected: true # Prevent unintentional edits or deletions. (Not in use in this version of the installer.)
name: "msw" # The name of the user account.
fullname: "msw" # The full name of the user account holder.
uid: 1000 # Ensures that the same user has the same UID on all systems.
@@ -846,9 +848,7 @@ user:
access:
ssh: true # Allow SSH access.
tty: true # Allow TTY (local console) login.
password:
ssh: false # Allow SSH password login.
tty: true # Allow TTY (local console) password login.
password: false # Allow password login. SSH password login is always disabled.
2fa:
ssh: true # Require 2FA for SSH access.
tty: true # Require 2FA for TTY (local console) login.
@@ -876,9 +876,7 @@ user:
access:
ssh: true # Allow SSH access.
tty: false # Allow TTY (local console) login.
password:
ssh: false # Allow SSH password login.
tty: false # Allow TTY (local console) password login.
password: false # Allow password login. SSH password login is always disabled.
2fa:
ssh: false # Require 2FA for SSH access.
tty: false # Require 2FA for TTY (local console) login.

View File

@@ -128,8 +128,8 @@ EOF
/tmp)
#write_crypttab "${var_ephemeral_enclabel}" "LABEL=${var_host_fs_label}" "/dev/random" "offset=2048,cipher=aes-xts-plain64,size=512,sector-size=4096,discard,tmp=ext4"
write_crypttab "${var_ephemeral_enclabel}" "PARTUUID=${var_host_partuuid}" "/dev/random" "offset=2048,cipher=aes-xts-plain64,size=512,sector-size=4096,discard,plain"
#chroot_script "${TARGET}" "systemctl mask tmp.mount"
#do_log "info" "file_only" "4210() Masked: [tmp.mount]"
chroot_script "${TARGET}" "systemctl mask tmp.mount"
do_log "info" "file_only" "4210() Masked: [tmp.mount]"
;;
*)

View File

@@ -16,14 +16,14 @@ guard_sourcing
# Setup ssh server.
# Globals:
# TARGET
# VAR_DROPBEAR
# VAR_FINAL_FQDN
# VAR_FINAL_IPV4
# VAR_FINAL_IPV6
# VAR_SETUP_PATH
# VAR_USER_MAX
# VAR_DROPBEAR
# VAR_SSH_PORT
# VAR_SSH_CA
# VAR_SSH_PORT
# VAR_USER_MAX
# Arguments:
# None
# Returns:

View File

@@ -0,0 +1,34 @@
#!/bin/bash
# SPDX-Version: 3.0
# SPDX-CreationInfo: 2025-06-17; WEIDNER, Marc S.; <msw@coresecret.dev>
# SPDX-ExternalRef: GIT https://git.coresecret.dev/msw/CISS.debian.installer.git
# SPDX-FileContributor: WEIDNER, Marc S.; Centurion Intelligence Consulting Agency
# SPDX-FileCopyrightText: 2024-2025; WEIDNER, Marc S.; <msw@coresecret.dev>
# SPDX-FileType: SOURCE
# SPDX-License-Identifier: EUPL-1.2 OR LicenseRef-CCLA-1.0
# SPDX-LicenseComment: This file is part of the CISS.debian.installer.secure framework.
# SPDX-PackageName: CISS.debian.installer
# SPDX-Security-Contact: security@coresecret.eu
guard_sourcing
#######################################
# Hardening 'usb-guard'.
# Globals:
# TARGET
# VAR_SSH_PORT
# VAR_UFW_OUT
# Arguments:
# None
# Returns:
# 0: on success
#######################################
hardening_usb() {
### Declare Arrays, HashMaps, and Variables.
declare -r var_logfile="/root/.ciss/cdi/log/4480_hardening_usb.log"
chroot_logger "${TARGET}${var_logfile}"
guard_dir && return 0
}
# vim: number et ts=2 sw=2 sts=2 ai tw=128 ft=sh

View File

@@ -23,6 +23,17 @@ guard_sourcing
# 0: on success
#######################################
accounts_preparation() {
### Declare Arrays, HashMaps, and Variables.
declare -r var_logfile="/root/.ciss/cdi/log/4130_installation_toolset.log"
chroot_logger "${TARGET}${var_logfile}"
chroot_script "${TARGET}" "
export INITRD=No
apt-get install -y --no-install-recommends --no-install-suggests bash-completion fzf 2>&1 | tee -a ${var_logfile}
echo ExitCode: \$? >> ${var_logfile}
"
mkdir -p "${TARGET}/etc/skel/.ciss"
install -m 0600 -o root -g root "${VAR_SETUP_PATH}/includes/target/etc/skel/.bashrc" "${TARGET}/etc/skel/.bashrc"

View File

@@ -38,14 +38,27 @@ accounts_hardening() {
echo ExitCode: \$? >> ${var_logfile}
"
### Keep 'tty1' active, disable the rest (VTs).
### Preparing 2fa hardening.
install -d -m 0755 -o root -g root "${TARGET}/etc/ciss"
touch "${TARGET}/etc/ciss/2fa.users"
chmod 0640 "${TARGET}/etc/ciss/2fa.users"
### Keep 'tty1' active, disable the rest.
chroot_script "${TARGET}" "
systemctl unmask getty@tty1.service
systemctl enable getty@tty1.service
for t in tty2 tty3 tty4 tty5 tty6; do
systemctl mask getty@${t}.service
done
"
systemctl mask serial-getty@.service
"
### Hardening file permissions.
chown root:root "${TARGET}/etc/passwd" "${TARGET}/etc/group"
chown root:shadow "${TARGET}/etc/shadow" "${TARGET}/etc/gshadow"
chmod 0644 "${TARGET}/etc/passwd" "${TARGET}/etc/group"
chmod 0640 "${TARGET}/etc/shadow" "${TARGET}/etc/gshadow"
chmod 0600 "${TARGET}/etc/securetty" "${TARGET}/etc/security/access.conf"
### Hardening '/etc/login.defs'.
mv "${TARGET}/etc/login.defs" "${TARGET}/root/.ciss/cdi/backup/etc/login.defs.bak"
@@ -60,6 +73,12 @@ accounts_hardening() {
insert_comments "${TARGET}/etc/security/pwquality.conf"
cat "${VAR_SETUP_PATH}/includes/target/etc/security/pwquality.cnf" >> "${TARGET}/etc/security/pwquality.conf"
### Hardening '/etc/security/access.conf'.
mv "${TARGET}/etc/security/access.conf" "${TARGET}/root/.ciss/cdi/backup/etc/security/access.conf.bak"
insert_header "${TARGET}/etc/security/access.conf"
insert_comments "${TARGET}/etc/security/access.conf"
cat "${VAR_SETUP_PATH}/includes/target/etc/security/access.cnf" >> "${TARGET}/etc/security/access.conf"
### Hardening password expiration; defaults to 16,384 days.
install -m 0700 -o root -g root "${VAR_SETUP_PATH}/includes/chroot/hooks/4510_password_expiration.hooks.sh" \
"${TARGET}/root/.ciss/cdi/hooks/4510_password_expiration.hooks.sh"

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