All checks were successful
🛡️ Shell Script Linting / 🛡️ Shell Script Linting (push) Successful in 1m59s
Signed-off-by: Marc S. Weidner <msw@coresecret.dev>
1678 lines
58 KiB
Bash
1678 lines
58 KiB
Bash
#!/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 || return "${ERR_GUARD_SOURCE}"
|
|
|
|
#######################################
|
|
# Updating root account and generation user accounts.
|
|
# Globals:
|
|
# CISS_SECRET_USER_ROOT_PASSWORD
|
|
# CISS_SECRET_USER_ROOT_SSHPUBKEY
|
|
# LOG_ERR
|
|
# RECOVERY
|
|
# TARGET
|
|
# VAR_RUN_RECOVERY
|
|
# VAR_SETUP_PATH
|
|
# VAR_TEMP_PLAIN_MFA_SEED
|
|
# VAR_USER_MAX
|
|
# VAR_USER_ROOT_SPECIFIC
|
|
# user_root_authentication_2fa_ssh
|
|
# user_root_authentication_2fa_tty
|
|
# user_root_authentication_access_ssh
|
|
# user_root_authentication_access_tty
|
|
# user_root_authentication_password
|
|
# Arguments:
|
|
# None
|
|
# Returns:
|
|
# 0: on success
|
|
# ERR_ACCOUNT_CREATE: on failure
|
|
#######################################
|
|
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_access_tty="" tmp_auth_pwd="" tmp_2fa_ssh="" tmp_2fa_tty="" tmp_sudo="" tmp_restricted="" tmp_system="" \
|
|
tmp_specific=""
|
|
declare var_username="" var_fullname="" var_uid="" var_gid="" var_shell="" var_password="" var_sshpubkey="" \
|
|
var_access_tty="" var_auth_pwd="" var_2fa_ssh="" var_2fa_tty="" var_sudo="" var_restricted="" var_system="" \
|
|
var_specific="" var_ssh_totp_update="false"
|
|
declare var_target="${TARGET}"
|
|
|
|
### Check for TARGET / RECOVERY.
|
|
[[ "${VAR_RUN_RECOVERY}" == "true" ]] && var_target="${RECOVERY}"
|
|
|
|
chroot_logger "${var_target}${var_logfile}"
|
|
|
|
### Install CISS TOTP gate script.
|
|
install -m 0755 -o root -g root "${VAR_SETUP_PATH}/includes/target/usr/local/libexec/ciss_pam_2fa_gate.sh" \
|
|
"${var_target}/usr/local/libexec/"
|
|
|
|
### Update pam modules for 2fa.
|
|
mkdir -p "${var_target}/root/.ciss/cdi/backup/etc/pam.d"
|
|
write_pam_common_auth "${var_target}"
|
|
write_pam_login "${var_target}"
|
|
write_pam_sshd "${var_target}"
|
|
write_pam_su "${var_target}"
|
|
write_pam_su-l "${var_target}"
|
|
write_pam_sudo "${var_target}"
|
|
write_pam_sudo-i "${var_target}"
|
|
|
|
### Prepare the '2fa'-seed variable.
|
|
read_totp_seed
|
|
do_log "debug" "file_only" "4520() Command: [read_totp_seed]"
|
|
|
|
### Updating root ------------------------------------------------------------------------------------------------------------
|
|
|
|
### 0) The 'root' account is generated via debootstrap by default.
|
|
|
|
### 1) Prepare the 'root' account.
|
|
case "${VAR_USER_ROOT_SPECIFIC}" in
|
|
|
|
"ciss" ) accounts_setup_ciss_root ;;
|
|
|
|
"physnet") accounts_setup_physnet_root ;;
|
|
|
|
"none" ) do_log "info" "file_only" "4520() Account preparation: 'root' [none] selected." ;;
|
|
|
|
* ) do_log "warn" "file_only" "4520() Account preparation: 'root' nothing selected. Keeping defaults." ;;
|
|
|
|
esac
|
|
|
|
### 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')|" "${var_target}/etc/ssh/sshd_config"
|
|
do_log "info" "file_only" "4520() User: 'root' SSH access: [PermitRootLogin no]"
|
|
;;
|
|
|
|
true)
|
|
if [[ "${user_root_authentication_2fa_ssh}" == "true" || "${user_root_authentication_2fa_tty}" == "true" ]]; then
|
|
|
|
### SSH Public Key per default, only.
|
|
sed -i -E "s|^[[:space:]]*PermitRootLogin[[:space:]]+.*$|$(printf '%-29s%s' 'PermitRootLogin' 'yes')|" "${var_target}/etc/ssh/sshd_config"
|
|
do_log "info" "file_only" "4520() User: 'root' SSH access: [PermitRootLogin yes]"
|
|
|
|
else
|
|
|
|
### SSH Public Key per default, only.
|
|
sed -i -E "s|^[[:space:]]*PermitRootLogin[[:space:]]+.*$|$(printf '%-29s%s' 'PermitRootLogin' 'prohibit-password')|" "${var_target}/etc/ssh/sshd_config"
|
|
do_log "info" "file_only" "4520() User: 'root' SSH access: [PermitRootLogin prohibit-password]"
|
|
|
|
fi
|
|
;;
|
|
|
|
*)
|
|
do_log "fatal" "file_only" "4520() Not set: user_root_authentication_access_ssh [${user_root_authentication_access_ssh}]"
|
|
return "${ERR_ACCOUNT_CREATE}"
|
|
;;
|
|
|
|
esac
|
|
|
|
### 3) Check tty access capabilities.
|
|
case "${user_root_authentication_access_tty,,}" in
|
|
|
|
false)
|
|
### Disallow all local access for root in '/etc/security/access.conf'.
|
|
printf -- '-: root:ALL\n' >> "${var_target}/etc/security/access.conf"
|
|
do_log "info" "file_only" "4520() User: 'root' [disallow all local access in '/etc/security/access.conf']"
|
|
|
|
### Empty "/etc/securetty".
|
|
cat << 'EOF' >| "${var_target}/etc/securetty"
|
|
EOF
|
|
do_log "info" "file_only" "4520() User: 'root' [empty '/etc/securetty']"
|
|
;;
|
|
|
|
true)
|
|
### Allow local access for 'root' on 'tty1' only in '/etc/security/access.conf'.
|
|
printf -- "+: root:tty1 \n" >> "${var_target}/etc/security/access.conf"
|
|
do_log "info" "file_only" "4520() User: 'root' [allow local access on tty1 in '/etc/security/access.conf']"
|
|
|
|
### Allow local access for 'root' on 'tty1' only in '/etc/securetty'.
|
|
cat << 'EOF' >| "${var_target}/etc/securetty"
|
|
tty1
|
|
EOF
|
|
do_log "info" "file_only" "4520() User: 'root' [tty1 in '/etc/securetty']."
|
|
;;
|
|
|
|
*)
|
|
do_log "fatal" "file_only" "4520() Not set: user_root_authentication_access_tty [${user_root_authentication_access_tty}]"
|
|
return "${ERR_ACCOUNT_CREATE}"
|
|
;;
|
|
|
|
esac
|
|
|
|
### 4) Check the password policy for the 'root' account.
|
|
chroot_script "${var_target}" "printf '%s:%s\n' 'root' '${CISS_SECRET_USER_ROOT_PASSWORD}' | /usr/sbin/chpasswd -e"
|
|
do_log "info" "file_only" "4520() User: 'root' password: inserted."
|
|
unset CISS_SECRET_USER_ROOT_PASSWORD
|
|
|
|
case "${user_root_authentication_password,,}" in
|
|
|
|
false)
|
|
chroot_script "${var_target}" "passwd -l root"
|
|
do_log "info" "file_only" "4520() User: 'root' password access: [false]"
|
|
;;
|
|
|
|
true)
|
|
chroot_script "${var_target}" "passwd -u root"
|
|
do_log "info" "file_only" "4520() User: 'root' password access: [true]"
|
|
;;
|
|
|
|
*)
|
|
do_log "fatal" "file_only" "4520() Not set: user_root_authentication_password [${user_root_authentication_password}]"
|
|
return "${ERR_ACCOUNT_CREATE}"
|
|
;;
|
|
|
|
esac
|
|
|
|
### 5) Update the 'root' SSH pubkey, if provided via 'preseed.yaml'.
|
|
if [[ -n "${CISS_SECRET_USER_ROOT_SSHPUBKEY:-}" ]]; then
|
|
|
|
printf "%s\n" "${CISS_SECRET_USER_ROOT_SSHPUBKEY}" >| "${var_target}/root/.ssh/authorized_keys"
|
|
unset CISS_SECRET_USER_ROOT_SSHPUBKEY
|
|
do_log "info" "file_only" "4520() User: 'root' SSH public key: inserted."
|
|
|
|
fi
|
|
|
|
### 6) Update the 'root' 'totp'-policy-user file and write the '.google_authenticator'-file.
|
|
if [[ "${user_root_authentication_2fa_ssh}" == "true" || "${user_root_authentication_2fa_tty}" == "true" ]]; then
|
|
|
|
write_google_authenticator_file "root" "0" "0" "${var_target}"
|
|
|
|
[[ "${user_root_authentication_2fa_ssh}" == "true" ]] && write_ciss_2fa_user "root" "sshd" "on" "${var_target}"
|
|
|
|
[[ "${user_root_authentication_2fa_tty}" == "true" ]] && write_ciss_2fa_user "root" "login" "on" "${var_target}"
|
|
|
|
write_ciss_2fa_user "root" "su" "off" "${var_target}"
|
|
write_ciss_2fa_user "root" "sudo" "off" "${var_target}"
|
|
|
|
fi
|
|
|
|
if [[ "${user_root_authentication_2fa_ssh}" == "true" ]]; then
|
|
|
|
var_ssh_totp_update="true"
|
|
cat << EOF >> "${var_target}/etc/ssh/sshd_config"
|
|
Match User root
|
|
AuthenticationMethods publickey,keyboard-interactive:pam
|
|
|
|
EOF
|
|
fi
|
|
|
|
### 7) Install eza themes.
|
|
eza_installer "root" "${var_target}"
|
|
|
|
### 8) Double check permissions.
|
|
### Directories: 0700
|
|
find "${var_target}/root" -type d -exec chmod 0700 {} +
|
|
### Executable files: 0700 (any x-bit set)
|
|
find "${var_target}/root" -type f -perm /111 -exec chmod 0700 {} +
|
|
### Non-executable files: 0600
|
|
find "${var_target}/root" -type f ! -perm /111 -exec chmod 0600 {} +
|
|
### Ownership: UID:GID (do not dereference symlinks; stay on this filesystem)
|
|
find "${var_target}/root" -xdev -exec chown -h root:root {} +
|
|
|
|
### 9) Final status logging.
|
|
do_log "info" "file_only" "4520() User: 'root' updated."
|
|
|
|
### Generating 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"
|
|
tmp_gid="user_user${i}_gid"
|
|
tmp_shell="user_user${i}_shell"
|
|
tmp_password="CISS_SECRET_USER_USER${i}_PASSWORD"
|
|
tmp_sshpubkey="CISS_SECRET_USER_USER${i}_SSHPUBKEY"
|
|
tmp_access_tty="user_user${i}_authentication_access_tty"
|
|
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_system="user_user${i}_privileges_system"
|
|
tmp_restricted="user_user${i}_privileges_restricted"
|
|
tmp_specific="user_user${i}_specific"
|
|
|
|
var_username="${!tmp_username}"
|
|
var_fullname="${!tmp_fullname}"
|
|
var_uid="${!tmp_uid}"
|
|
var_gid="${!tmp_gid}"
|
|
var_shell="${!tmp_shell}"
|
|
var_password="${!tmp_password}"
|
|
var_sshpubkey="${!tmp_sshpubkey}"
|
|
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_system="${!tmp_system}"
|
|
var_restricted="${!tmp_restricted}"
|
|
var_specific="${!tmp_specific}"
|
|
|
|
### 0) A) Check if the 'group' of the 'user' already exists.
|
|
if ! chroot_exec "${var_target}" getent group "${var_username}" >/dev/null; then
|
|
chroot_exec "${var_target}" groupadd --gid "${var_gid}" "${var_username}"
|
|
fi
|
|
sed -i '/getent[[:space:]]\+group/d' -- "${LOG_ERR}"
|
|
|
|
### 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 the 'user' is a system user, then generate with flag '--system'.
|
|
case "${var_restricted}":"${var_system}" in
|
|
|
|
false:false)
|
|
chroot_exec "${var_target}" useradd \
|
|
--comment "${var_fullname}" \
|
|
--create-home \
|
|
--expiredate 2102-09-17 \
|
|
--gid "${var_gid}" \
|
|
--home-dir /home/"${var_username}" \
|
|
--inactive 0 \
|
|
--shell "${var_shell}" \
|
|
--uid "${var_uid}" \
|
|
"${var_username}"
|
|
eza_installer "${var_username}" "${var_target}"
|
|
;;
|
|
|
|
true:false)
|
|
chroot_exec "${var_target}" useradd \
|
|
--comment "${var_fullname}" \
|
|
--expiredate 2102-09-17 \
|
|
--gid "${var_gid}" \
|
|
--inactive 0 \
|
|
--no-create-home \
|
|
--shell "${var_shell}" \
|
|
--uid "${var_uid}" \
|
|
"${var_username}"
|
|
;;
|
|
|
|
false:true)
|
|
chroot_exec "${var_target}" useradd \
|
|
--comment "${var_fullname}" \
|
|
--create-home \
|
|
--expiredate 2102-09-17 \
|
|
--gid "${var_gid}" \
|
|
--home-dir /home/"${var_username}" \
|
|
--inactive 0 \
|
|
--shell "${var_shell}" \
|
|
--system \
|
|
--uid "${var_uid}" \
|
|
"${var_username}"
|
|
;;
|
|
|
|
true:true)
|
|
chroot_exec "${var_target}" useradd \
|
|
--comment "${var_fullname}" \
|
|
--expiredate 2102-09-17 \
|
|
--gid "${var_gid}" \
|
|
--inactive 0 \
|
|
--no-create-home \
|
|
--shell "${var_shell}" \
|
|
--system \
|
|
--uid "${var_uid}" \
|
|
"${var_username}"
|
|
;;
|
|
|
|
*)
|
|
do_log "fatal" "file_only" "4520() Not set: var_restricted:var_system [${var_restricted}:${var_system}]"
|
|
return "${ERR_ACCOUNT_CREATE}"
|
|
;;
|
|
|
|
esac
|
|
|
|
### 1) Prepare the 'user' account.
|
|
install -d -m 0700 -- "${var_target}/home/${var_username}/.ssh"
|
|
install -d -m 0700 -- "${var_target}/home/${var_username}/.cache"
|
|
install -d -m 0700 -- "${var_target}/home/${var_username}/.config"
|
|
install -d -m 0700 -- "${var_target}/home/${var_username}/.local/share"
|
|
install -d -m 0700 -- "${var_target}/home/${var_username}/.local/state"
|
|
install -d -m 0700 -- "${var_target}/home/${var_username}/.local/state/bash"
|
|
install -d -m 0700 -- "${var_target}/home/${var_username}/.local/state/less"
|
|
|
|
case "${var_specific}" in
|
|
|
|
"ciss" ) accounts_setup_ciss_user "${var_uid}" "${var_gid}" "${var_username}" "${var_shell}" ;;
|
|
|
|
"physnet") accounts_setup_physnet_user "${var_uid}" "${var_gid}" "${var_username}" "${var_shell}" ;;
|
|
|
|
"none" ) do_log "info" "file_only" "4520() Account preparation: '${var_username}' [none] selected." ;;
|
|
|
|
* ) do_log "warn" "file_only" "4520() Account preparation: '${var_username}' nothing selected. Keeping defaults." ;;
|
|
|
|
esac
|
|
|
|
### 2) Check SSH access capabilities.
|
|
### Nothing to do here as per-user SSH capabilities are already handled in '4330_installation_ssh.sh'.
|
|
|
|
### 3) Check tty access capabilities.
|
|
case "${var_access_tty,,}" in
|
|
|
|
false)
|
|
### Disallow all local access for user in '/etc/security/access.conf'.
|
|
printf '%s\n' "-: ${var_username}:ALL" >> "${var_target}/etc/security/access.conf"
|
|
do_log "info" "file_only" "4520() User: '${var_username}' [disallow all local access in '/etc/security/access.conf']"
|
|
;;
|
|
|
|
true)
|
|
### Allow local access for 'user' only on 'tty1' in '/etc/security/access.conf'.
|
|
printf '%s\n' "+: ${var_username}:tty1" >> "${var_target}/etc/security/access.conf"
|
|
do_log "info" "file_only" "4520() User: '${var_username}' [allow local access on tty1 in '/etc/security/access.conf']"
|
|
;;
|
|
|
|
*)
|
|
do_log "fatal" "file_only" "4520() Not set: var_access_tty [${var_access_tty}]"
|
|
return "${ERR_ACCOUNT_CREATE}"
|
|
;;
|
|
|
|
esac
|
|
|
|
### 4) Check the password policy for the 'user' account.
|
|
chroot_script "${var_target}" "printf '%s:%s\n' \"${var_username}\" '${var_password}' | /usr/sbin/chpasswd -e"
|
|
|
|
case "${var_auth_pwd}" in
|
|
|
|
false)
|
|
chroot_script "${var_target}" "passwd -l ${var_username}"
|
|
do_log "info" "file_only" "4520() User: '${var_username}' password access: [false]"
|
|
;;
|
|
|
|
true)
|
|
chroot_script "${var_target}" "passwd -u ${var_username}"
|
|
do_log "info" "file_only" "4520() User: '${var_username}' password access: [true]"
|
|
;;
|
|
|
|
*)
|
|
do_log "fatal" "file_only" "4520() Not set: var_auth_pwd [${var_auth_pwd}]"
|
|
return "${ERR_ACCOUNT_CREATE}"
|
|
;;
|
|
|
|
esac
|
|
|
|
### 5) Update the 'user' SSH pubkey, if provided via 'preseed.yaml'.
|
|
if [[ -n "${var_sshpubkey:-}" ]]; then
|
|
|
|
printf "%s\n" "${var_sshpubkey}" >| "${var_target}/home/${var_username}/.ssh/authorized_keys"
|
|
do_log "info" "file_only" "4520() User: '${var_username}' SSH public key: inserted."
|
|
|
|
fi
|
|
|
|
### 6) Update the 'user' 'totp'-policy and write the '.google_authenticator'-file.
|
|
if [[ "${var_2fa_ssh}" == "true" || "${var_2fa_tty}" == "true" ]]; then
|
|
|
|
write_google_authenticator_file "${var_username}" "${var_uid}" "${var_gid}" "${var_target}"
|
|
|
|
[[ "${var_2fa_ssh}" == "true" ]] && write_ciss_2fa_user "${var_username}" "sshd" "on" "${var_target}"
|
|
|
|
[[ "${var_2fa_tty}" == "true" ]] && write_ciss_2fa_user "${var_username}" "login" "on" "${var_target}"
|
|
|
|
write_ciss_2fa_user "${var_username}" "su" "on" "${var_target}"
|
|
|
|
write_ciss_2fa_user "${var_username}" "sudo" "on" "${var_target}"
|
|
|
|
fi
|
|
|
|
if [[ "${var_2fa_ssh}" == "true" ]]; then
|
|
|
|
var_ssh_totp_update="true"
|
|
cat << EOF >> "${var_target}/etc/ssh/sshd_config"
|
|
Match User ${var_username}
|
|
AuthenticationMethods publickey,keyboard-interactive:pam
|
|
|
|
EOF
|
|
fi
|
|
|
|
### 7) Check sudo membership for user.
|
|
if [[ "${var_sudo}" == "true" ]]; then
|
|
|
|
chroot_exec "${var_target}" usermod -aG sudo "${var_username}"
|
|
### Hardening sudo users (idempotent) and ensure WinSCP SFTP-as-root.
|
|
hardening_sudo "${var_username}" "${var_specific:-none}" "${var_target}"
|
|
|
|
fi
|
|
|
|
### 8) Double check permissions.
|
|
### Directories: 0700
|
|
find "${var_target}/home/${var_username}" -type d -exec chmod 0700 {} +
|
|
### Executable files: 0700 (any x-bit set)
|
|
find "${var_target}/home/${var_username}" -type f -perm /111 -exec chmod 0700 {} +
|
|
### Non-executable files: 0600
|
|
find "${var_target}/home/${var_username}" -type f ! -perm /111 -exec chmod 0600 {} +
|
|
### Ownership: UID:GID (do not dereference symlinks; stay on this filesystem)
|
|
find "${var_target}/home/${var_username}" -xdev -exec chown -h "${var_uid}:${var_gid}" {} +
|
|
|
|
### 9) Final status logging.
|
|
unset var_password var_sshpubkey
|
|
do_log "info" "file_only" "4520() Created user: [${var_username}] UID: [${var_uid}] GID: [${var_gid}]"
|
|
|
|
done
|
|
|
|
if [[ "${var_ssh_totp_update}" == "true" ]]; then
|
|
|
|
sed -i -E "s|^[[:space:]]*KbdInteractiveAuthentication[[:space:]]+.*$|$(printf '%-29s%s' 'KbdInteractiveAuthentication' 'yes')|" "${var_target}/etc/ssh/sshd_config"
|
|
|
|
fi
|
|
|
|
if ! grep -Fqx -- '-: ALL:ALL' "${var_target}/etc/security/access.conf"; then
|
|
|
|
printf '%s\n' '-: ALL:ALL' >> "${var_target}/etc/security/access.conf"
|
|
|
|
fi
|
|
|
|
printf "# vim: number et ts=2 sw=2 sts=2 ai tw=128 ft=conf \n" >> "${var_target}/etc/security/access.conf"
|
|
printf "# vim: number et ts=2 sw=2 sts=2 ai tw=128 ft=conf \n" >> "${var_target}/etc/ssh/sshd_config"
|
|
|
|
unset VAR_TEMP_PLAIN_MFA_SEED
|
|
|
|
guard_dir; return 0
|
|
}
|
|
### Prevents accidental 'unset -f'.
|
|
# shellcheck disable=SC2034
|
|
readonly -f accounts_setup
|
|
|
|
#######################################
|
|
# Install eza CISS theme for the respective user.
|
|
# Globals:
|
|
# None
|
|
# Arguments:
|
|
# 1: Username
|
|
# 2: Target
|
|
# Returns:
|
|
# 0: on success
|
|
#######################################
|
|
eza_installer() {
|
|
### Declare Arrays, HashMaps, and Variables.
|
|
declare -r var_user="${1}" var_target="${2}"
|
|
|
|
case "${1}" in
|
|
root) declare var_base="/root" ;;
|
|
*) declare var_base="/home/${var_user}" ;;
|
|
esac
|
|
|
|
chroot_script "${var_target}" "
|
|
cd ${var_base}
|
|
git clone https://github.com/eza-community/eza-themes.git
|
|
mkdir -p ${var_base}/.config/eza
|
|
ln -sf ${var_base}/.ciss/theme_eza_ciss.yml ${var_base}/.config/eza/theme.yml
|
|
"
|
|
|
|
return 0
|
|
}
|
|
### Prevents accidental 'unset -f'.
|
|
# shellcheck disable=SC2034
|
|
readonly -f eza_installer
|
|
|
|
#######################################
|
|
# Generates a deterministic TOTP secret based on:
|
|
# Username, FQDN, MFA salt, MFA master seed
|
|
# Globals:
|
|
# CISS_SECRET_SEEDS_MFA_INFO
|
|
# CISS_SECRET_SEEDS_MFA_SALT
|
|
# VAR_FINAL_FQDN
|
|
# VAR_TEMP_PLAIN_MFA_SEED
|
|
# Arguments:
|
|
# 1: Username
|
|
# Returns:
|
|
# 0: on success
|
|
#######################################
|
|
generate_totp_secret() {
|
|
### Declare Arrays, HashMaps, and Variables.
|
|
declare var_user="${1}"
|
|
declare var_host_id="${VAR_FINAL_FQDN}"
|
|
declare var_salt="${CISS_SECRET_SEEDS_MFA_SALT}:${var_host_id}:${var_user}"
|
|
declare var_info="${CISS_SECRET_SEEDS_MFA_INFO}"
|
|
declare var_secret=""
|
|
|
|
### SECRETS handling ---------------------------------------------------------------------------------------------------------
|
|
guard_trace on
|
|
|
|
### 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
|
|
var_secret="$(
|
|
openssl kdf -keylen 20 \
|
|
-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}"
|
|
|
|
guard_trace off
|
|
### SECRETS handling ---------------------------------------------------------------------------------------------------------
|
|
|
|
return 0
|
|
}
|
|
### Prevents accidental 'unset -f'.
|
|
# shellcheck disable=SC2034
|
|
readonly -f generate_totp_secret
|
|
|
|
#######################################
|
|
# Hardening sudo users (idempotent) and ensure WinSCP SFTP-as-root.
|
|
# Globals:
|
|
# None
|
|
# Arguments:
|
|
# 1: username
|
|
# 2: user_specifics
|
|
# 3: target
|
|
# Returns:
|
|
# 0: on success
|
|
# ERR_VERIFY_LOGROTATE: on failure
|
|
# ERR_VERIFY_VISUDO: on failure
|
|
#######################################
|
|
hardening_sudo() {
|
|
### Declare Arrays, HashMaps, and Variables.
|
|
declare -r var_user="${1}" var_specific="${2}" var_target="${3}"
|
|
declare -r var_logfile="/root/.ciss/cdi/log/4520_accounts_setup.log"
|
|
declare -r var_sudo_iolog_dir="${var_target}/var/log/sudo-io"
|
|
declare -r var_sudoers_main="${var_target}/etc/sudoers"
|
|
declare -r var_sudoers_dir="${var_target}/etc/sudoers.d"
|
|
declare -r var_lr_conf="${var_target}/etc/logrotate.d/sudo"
|
|
declare -r var_sudoers_winscp_global="${var_target}/etc/sudoers.d/90-ciss-winscp-sftp"
|
|
declare -r var_sudoers_winscp_user="${var_target}/etc/sudoers.d/91-ciss-winscp-${var_user}"
|
|
declare -r var_sftp_bin="/usr/lib/openssh/sftp-server"
|
|
|
|
### Create sudo I/O log directory (idempotent).
|
|
if [[ ! -d "${var_sudo_iolog_dir}" ]]; then
|
|
mkdir -p "${var_sudo_iolog_dir}"
|
|
chmod 0700 "${var_sudo_iolog_dir}"
|
|
else
|
|
### Enforce restrictive perms on an existing tree.
|
|
chmod 0700 "${var_sudo_iolog_dir}"
|
|
fi
|
|
|
|
### Ensure sudoers Defaults are present only once. We key on 'iolog_dir' to avoid duplicate blocks.
|
|
if ! grep -qF 'iolog_dir="/var/log/sudo-io"' "${var_sudoers_main}" 2>/dev/null; then
|
|
cat << 'EOF' >> "${var_sudoers_main}"
|
|
|
|
##### Added by CISS.debian.installer
|
|
Defaults timestamp_timeout=480
|
|
Defaults log_host, log_year, log_input, log_exit_status, log_subcmds, logfile="/var/log/sudo.log", iolog_dir="/var/log/sudo-io"
|
|
EOF
|
|
fi
|
|
|
|
case "${var_specific,,}" in
|
|
|
|
"ciss")
|
|
### Install global WinSCP SFTP-as-root command alias (idempotent).
|
|
if [[ -x "${var_target}${var_sftp_bin}" ]]; then
|
|
|
|
if [[ ! -f "${var_sudoers_winscp_global}" ]]; then
|
|
|
|
insert_header "${var_sudoers_winscp_global}"
|
|
insert_comments "${var_sudoers_winscp_global}"
|
|
cat << EOF >| "${var_sudoers_winscp_global}"
|
|
### Added by CISS.debian.installer. WinSCP SFTP-as-root (least privilege).
|
|
### Allow exactly the sftp-server binary, optionally with -e (stderr logging).
|
|
Cmnd_Alias CISS_SFTPROOT = ${var_sftp_bin}, ${var_sftp_bin} -e
|
|
|
|
# Command-scoped hardening for the alias:
|
|
# - noexec : disallow further exec()
|
|
# - !setenv : forbid env manipulation
|
|
# - timestamp_timeout=0: require re-auth each time (no caching)
|
|
Defaults!CISS_SFTPROOT noexec, !setenv, timestamp_timeout=0
|
|
|
|
# vim: number et ts=2 sw=2 sts=2 ai tw=128 ft=conf
|
|
EOF
|
|
chmod 0440 "${var_sudoers_winscp_global}"
|
|
|
|
fi
|
|
|
|
else
|
|
|
|
do_log "warn" "file_only" "4520() sftp-server not found at [${var_sftp_bin}] in TARGET; skipping global alias for now."
|
|
|
|
fi
|
|
|
|
### Grant this user access to the alias (idempotent). Only add if not already present; keep the file permissive correctness.
|
|
if [[ -f "${var_sudoers_winscp_user}" ]]; then
|
|
|
|
if ! grep -qE "^${var_user}\s+ALL=\(root\)\s+NOPASSWD:\s+CISS_SFTPROOT\b" "${var_sudoers_winscp_user}" 2>/dev/null; then
|
|
|
|
echo "${var_user} ALL=(root) NOPASSWD: CISS_SFTPROOT" >> "${var_sudoers_winscp_user}"
|
|
|
|
fi
|
|
|
|
else
|
|
|
|
insert_header "${var_sudoers_winscp_user}"
|
|
insert_comments "${var_sudoers_winscp_user}"
|
|
echo "${var_user} ALL=(root) PASSWD: CISS_SFTPROOT" >> "${var_sudoers_winscp_user}"
|
|
printf "# vim: number et ts=2 sw=2 sts=2 ai tw=128 ft=conf \n" >> "${var_sudoers_winscp_user}"
|
|
|
|
fi
|
|
chmod 0440 "${var_sudoers_winscp_user}"
|
|
;;
|
|
|
|
"physnet")
|
|
:
|
|
;;
|
|
|
|
"none"|*)
|
|
:
|
|
;;
|
|
|
|
esac
|
|
|
|
### Tighten perms on sudoers.d (idempotent).
|
|
find "${var_sudoers_dir}" -type f -exec chmod 0440 {} \;
|
|
|
|
### Verify sudoers syntax in chroot.
|
|
if ! chroot_script "${var_target}" "EDITOR=/usr/bin/nano /usr/sbin/visudo -q -c >> ${var_logfile}"; then
|
|
|
|
do_log "warn" "file_only" "4520() Command: [chroot_script ${var_target} EDITOR=/usr/bin/nano /usr/sbin/visudo -q -c] failed."
|
|
return "${ERR_VERIFY_VISUDO}"
|
|
|
|
else
|
|
|
|
do_log "info" "file_only" "4520() Command: [chroot_script ${var_target} EDITOR=/usr/bin/nano /usr/sbin/visudo -q -c] successful."
|
|
|
|
fi
|
|
|
|
### Ensure logrotate for '/var/log/sudo.log' exists once.
|
|
if ! grep -qF "/var/log/sudo.log" "${var_lr_conf}" 2>/dev/null; then
|
|
|
|
insert_header "${var_lr_conf}"
|
|
insert_comments "${var_lr_conf}"
|
|
cat << EOF >> "${var_lr_conf}"
|
|
/var/log/sudo.log {
|
|
daily
|
|
rotate 384
|
|
maxage 384
|
|
notifempty
|
|
dateext
|
|
dateyesterday
|
|
compress
|
|
compresscmd /usr/bin/zstd
|
|
compressext .zst
|
|
compressoptions -20
|
|
uncompresscmd /usr/bin/unzstd
|
|
delaycompress
|
|
shred
|
|
missingok
|
|
create 600 root root
|
|
sharedscripts
|
|
postrotate
|
|
/usr/bin/systemctl reload sudo.service > /dev/null 2>&1 || true
|
|
endscript
|
|
}
|
|
EOF
|
|
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
### Prevents accidental 'unset -f'.
|
|
# shellcheck disable=SC2034
|
|
readonly -f hardening_sudo
|
|
|
|
#######################################
|
|
# Reads a 256-bit seed from '${CISS_SECRET_SEEDS_MFA_SECRET}' '(64 hex chars) into VAR_TEMP_PLAIN_MFA_SEED.
|
|
# Globals:
|
|
# CISS_SECRET_SEEDS_MFA_SECRET
|
|
# VAR_TEMP_PLAIN_MFA_SEED
|
|
# Arguments:
|
|
# None
|
|
# Returns:
|
|
# 0: on success
|
|
# ERR_READ_SEED_FILE: on failure
|
|
#######################################
|
|
read_totp_seed(){
|
|
### Declare Arrays, HashMaps, and Variables.
|
|
declare -g VAR_TEMP_PLAIN_MFA_SEED=""
|
|
|
|
### SECRETS handling ---------------------------------------------------------------------------------------------------------
|
|
guard_trace on
|
|
|
|
VAR_TEMP_PLAIN_MFA_SEED="${CISS_SECRET_SEEDS_MFA_SECRET}"
|
|
unset CISS_SECRET_SEEDS_MFA_SECRET
|
|
|
|
### Validate: exactly 64 hex.
|
|
[[ "${VAR_TEMP_PLAIN_MFA_SEED}" =~ ^[0-9a-fA-F]{64}$ ]] || return "${ERR_READ_SEED_FILE}"
|
|
|
|
guard_trace off
|
|
### SECRETS handling ---------------------------------------------------------------------------------------------------------
|
|
|
|
return 0
|
|
}
|
|
### Prevents accidental 'unset -f'.
|
|
# shellcheck disable=SC2034
|
|
readonly -f read_totp_seed
|
|
|
|
#######################################
|
|
# Writes idempotently '/etc/ciss/2fa.map'.
|
|
# Globals:
|
|
# None
|
|
# Arguments:
|
|
# 1: user
|
|
# 2: module {login, sshd, su, sudo}
|
|
# 3: status {on, off, 1, 0, yes, no, true, false}
|
|
# 4: target
|
|
# Returns:
|
|
# 0: on success
|
|
#######################################
|
|
write_ciss_2fa_user() {
|
|
### Declare Arrays, HashMaps, and Variables.
|
|
declare -r var_u="${1}" var_m="${2,,}" var_s="${3}" var_target="${4}"
|
|
declare -r var_ciss_2fa_map="${var_target}/etc/ciss/2fa.map"
|
|
declare -r var_map_file="${var_ciss_2fa_map}"
|
|
declare -r var_tmp_file="${var_map_file}.tmp.$$"
|
|
declare -i col_idx="" found="0" status=""
|
|
declare line=""
|
|
|
|
# shellcheck disable=SC2249
|
|
case "${var_s,,}" in
|
|
on|1|yes|true) status="1" ;;
|
|
off|0|no|false) status="0" ;;
|
|
esac
|
|
|
|
# shellcheck disable=SC2249
|
|
case "${var_m}" in
|
|
login) col_idx=2 ;;
|
|
sshd) col_idx=3 ;;
|
|
su) col_idx=4 ;;
|
|
sudo) col_idx=5 ;;
|
|
esac
|
|
|
|
### Read the old map, rewrite with targeted change.
|
|
if [[ -r "${var_map_file}" ]]; then
|
|
|
|
### Read line by line; preserve comments/blank lines; drop duplicate user lines beyond the first.
|
|
while IFS= read -r line || [[ -n "${line}" ]]; do
|
|
|
|
### Preserve comments and blanks verbatim.
|
|
if [[ -z "${line}" || "${line:0:1}" == "#" ]]; then
|
|
|
|
printf '%s\n' "${line}" >> "${var_tmp_file}"
|
|
continue
|
|
|
|
fi
|
|
|
|
### Parse colon-separated fields (the rest is ignored).
|
|
declare u="" f_login="" f_sshd="" f_su="" f_sudo="" rest=""
|
|
IFS=':' read -r u f_login f_sshd f_su f_sudo rest <<< "${line}"
|
|
|
|
### Keep non-target users verbatim.
|
|
if [[ "${u}" != "${var_u}" ]]; then
|
|
|
|
printf '%s\n' "${line}" >> "${var_tmp_file}"
|
|
continue
|
|
|
|
fi
|
|
|
|
### If we already updated this user once, skip any further duplicates.
|
|
if [[ "${found}" -eq 1 ]]; then
|
|
### Drop duplicate occurrence to keep the file canonical.
|
|
continue
|
|
fi
|
|
|
|
### Fill missing fields with defaults=0.
|
|
[[ -n "${f_login}" ]] || f_login=0
|
|
[[ -n "${f_sshd}" ]] || f_sshd=0
|
|
[[ -n "${f_su}" ]] || f_su=0
|
|
[[ -n "${f_sudo}" ]] || f_sudo=0
|
|
|
|
### Toggle the requested column only.
|
|
# shellcheck disable=SC2249
|
|
case "${col_idx}" in
|
|
|
|
2) f_login="${status}" ;;
|
|
3) f_sshd="${status}" ;;
|
|
4) f_su="${status}" ;;
|
|
5) f_sudo="${status}" ;;
|
|
|
|
esac
|
|
|
|
printf '%s:%s:%s:%s:%s\n' "${u}" "${f_login}" "${f_sshd}" "${f_su}" "${f_sudo}" >> "${var_tmp_file}"
|
|
found=1
|
|
|
|
done < "${var_map_file}"
|
|
|
|
fi
|
|
|
|
### If user not found: append a new default line (all 1), with the column set.
|
|
if [[ "${found}" -eq 0 ]]; then
|
|
|
|
declare -i d_login="0" d_sshd="0" d_su="0" d_sudo="0"
|
|
|
|
# shellcheck disable=SC2249
|
|
case "${col_idx}" in
|
|
|
|
2) d_login="${status}" ;;
|
|
3) d_sshd="${status}" ;;
|
|
4) d_su="${status}" ;;
|
|
5) d_sudo="${status}" ;;
|
|
|
|
esac
|
|
|
|
printf '%s:%s:%s:%s:%s\n' "${var_u}" "${d_login}" "${d_sshd}" "${d_su}" "${d_sudo}" >> "${var_tmp_file}"
|
|
|
|
fi
|
|
|
|
if [[ -e "${var_tmp_file}" ]]; then
|
|
|
|
mv -f -- "${var_tmp_file}" "${var_map_file}" || rm -f -- "${var_tmp_file}"
|
|
chmod 0644 "${var_map_file}"
|
|
|
|
fi
|
|
|
|
return 0
|
|
}
|
|
### Prevents accidental 'unset -f'.
|
|
# shellcheck disable=SC2034
|
|
readonly -f write_ciss_2fa_user
|
|
|
|
#######################################
|
|
# Writes '.google_authenticator'-file for the respective user.
|
|
# Globals:
|
|
# DIR_TMP
|
|
# Arguments:
|
|
# 1: USERNAME
|
|
# 2: UID
|
|
# 3: GID
|
|
# 4: TARGET
|
|
# Returns:
|
|
# 0: on success
|
|
#######################################
|
|
write_google_authenticator_file() {
|
|
### Declare Arrays, HashMaps, and Variables.
|
|
declare -r var_user="${1}" var_user_id="${2}" var_group_id="${3}" var_target="${4}"
|
|
declare -i i=0
|
|
declare var_secret="" __umask=""
|
|
|
|
__umask=$(umask)
|
|
|
|
case "${1}" in
|
|
root) declare var_base="${var_target}/root" ;;
|
|
*) declare var_base="${var_target}/home/${var_user}" ;;
|
|
esac
|
|
|
|
### SECRETS handling ---------------------------------------------------------------------------------------------------------
|
|
guard_trace on
|
|
|
|
var_secret="$(generate_totp_secret "${var_user}")"
|
|
|
|
umask 0077
|
|
{
|
|
declare accept="" hex="" val=""
|
|
|
|
printf '%s\n' "${var_secret}"
|
|
printf '" RATE_LIMIT 3 30\n'
|
|
printf '" WINDOW_SIZE 04\n'
|
|
printf '" DISALLOW_REUSE\n'
|
|
printf '" TOTP_AUTH\n'
|
|
|
|
### Emergency Codes (10x unbiased 8-digit, CSPRNG via OpenSSL).
|
|
for i in {1..10}; do
|
|
|
|
### Draw 32 bits; rejection sampling to avoid modulo bias.
|
|
while :; do
|
|
|
|
hex="$(openssl rand -hex 4)" || exit 1
|
|
val=$((16#${hex}))
|
|
accept=$(( (1<<32) / 100000000 * 100000000 ))
|
|
|
|
if (( val < accept )); then
|
|
|
|
printf '%08d\n' "$(( val % 100000000 ))"
|
|
break
|
|
|
|
fi
|
|
|
|
done
|
|
|
|
done
|
|
} >| "${var_base}/.google_authenticator"
|
|
chown "${var_user_id}:${var_group_id}" "${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"
|
|
|
|
guard_trace off
|
|
### SECRETS handling ---------------------------------------------------------------------------------------------------------
|
|
|
|
umask "${__umask}"
|
|
|
|
return 0
|
|
}
|
|
### Prevents accidental 'unset -f'.
|
|
# shellcheck disable=SC2034
|
|
readonly -f write_google_authenticator_file
|
|
|
|
#######################################
|
|
# Writes CISS Header for '/etc/pam.d/common-auth'.
|
|
# Globals:
|
|
# None
|
|
# Arguments:
|
|
# 1: TARGET
|
|
# Returns:
|
|
# 0: on success
|
|
#######################################
|
|
write_pam_common_auth() {
|
|
### Declare Arrays, HashMaps, and Variables.
|
|
declare -r var_target="$1"
|
|
|
|
mv "${var_target}/etc/pam.d/common-auth" "${var_target}/root/.ciss/cdi/backup/etc/pam.d/common-auth"
|
|
|
|
insert_header "${var_target}/etc/pam.d/common-auth"
|
|
insert_comments "${var_target}/etc/pam.d/common-auth"
|
|
cat << EOF >> "${var_target}/etc/pam.d/common-auth"
|
|
#
|
|
# /etc/pam.d/common-auth - authentication settings common to all services
|
|
#
|
|
|
|
# This file is included from other service-specific PAM config files, and should contain a list of the authentication modules
|
|
# that define the central authentication scheme for use on the system (e.g., /etc/shadow, LDAP, Kerberos, etc.). The default is
|
|
# to use the traditional Unix authentication mechanisms.
|
|
|
|
# As of pam 1.0.1-6, this file is managed by pam-auth-update by default. To take advantage of this, it is recommended that you
|
|
# configure any local modules either before or after the default block, and use pam-auth-update to manage selection of other
|
|
# modules. See pam-auth-update(8) for details.
|
|
|
|
# Here are the per-package modules (the "Primary" block):
|
|
auth [success=1 default=ignore] pam_unix.so try_first_pass nodelay
|
|
|
|
# Here is the fallback if no module succeeds:
|
|
auth requisite pam_deny.so
|
|
|
|
# Prime the stack with a positive return value if there is not one already; this avoids us returning an error just because
|
|
# nothing sets a success code since the modules above will each just jump around.
|
|
auth required pam_permit.so
|
|
|
|
# And here are more per-package modules (the "Additional" block):
|
|
|
|
# vim: number et ts=2 sw=2 sts=2 ai tw=128 ft=conf
|
|
EOF
|
|
do_log "info" "file_only" "4520() Written: [/etc/pam.d/common-auth]."
|
|
|
|
return 0
|
|
}
|
|
### Prevents accidental 'unset -f'.
|
|
# shellcheck disable=SC2034
|
|
readonly -f write_pam_common_auth
|
|
|
|
#######################################
|
|
# Writes CISS Header for '/etc/pam.d/common-session'.
|
|
# Globals:
|
|
# None
|
|
# Arguments:
|
|
# 1: TARGET
|
|
# Returns:
|
|
# 0: on success
|
|
#######################################
|
|
write_pam_common_session() {
|
|
### Declare Arrays, HashMaps, and Variables.
|
|
declare -r var_target="$1"
|
|
|
|
mv "${var_target}/etc/pam.d/common-session" "${var_target}/root/.ciss/cdi/backup/etc/pam.d/common-session"
|
|
|
|
insert_header "${var_target}/etc/pam.d/common-session"
|
|
insert_comments "${var_target}/etc/pam.d/common-session"
|
|
cat << EOF >> "${var_target}/etc/pam.d/common-session"
|
|
#
|
|
# /etc/pam.d/common-session - session-related modules common to all services
|
|
#
|
|
|
|
# This file is included from other service-specific PAM config files, and should contain a list of modules that define tasks to
|
|
# be performed at the start and end of interactive sessions.
|
|
|
|
# As of pam 1.0.1-6, this file is managed by pam-auth-update by default. To take advantage of this, it is recommended that you
|
|
# configure any local modules either before or after the default block, and use pam-auth-update to manage selection of other
|
|
# modules. See pam-auth-update(8) for details.
|
|
|
|
# Here are the per-package modules (the "Primary" block).
|
|
session [default=1] pam_permit.so
|
|
|
|
# Reset the umask for new sessions.
|
|
session optional pam_umask.so
|
|
|
|
# Here is the fallback if no module succeeds.
|
|
session requisite pam_deny.so
|
|
|
|
# Prime the stack with a positive return value if there is not one already; this avoids us returning an error just because
|
|
# nothing sets a success code since the modules above will each just jump around.
|
|
session required pam_permit.so
|
|
|
|
# And here are more per-package modules (the "Additional" block).
|
|
session required pam_unix.so
|
|
|
|
session optional pam_systemd.so
|
|
|
|
# vim: number et ts=2 sw=2 sts=2 ai tw=128 ft=conf
|
|
EOF
|
|
do_log "info" "file_only" "4520() Written: [/etc/pam.d/common-session]."
|
|
|
|
return 0
|
|
}
|
|
### Prevents accidental 'unset -f'.
|
|
# shellcheck disable=SC2034
|
|
readonly -f write_pam_common_session
|
|
|
|
#######################################
|
|
# Writes CISS Header for '/etc/pam.d/login'.
|
|
# Globals:
|
|
# None
|
|
# Arguments:
|
|
# 1: TARGET
|
|
# Returns:
|
|
# 0: on success
|
|
#######################################
|
|
write_pam_login() {
|
|
### Declare Arrays, HashMaps, and Variables.
|
|
declare -r var_target="$1"
|
|
|
|
mv "${var_target}/etc/pam.d/login" "${var_target}/root/.ciss/cdi/backup/etc/pam.d/login"
|
|
|
|
insert_header "${var_target}/etc/pam.d/login"
|
|
insert_comments "${var_target}/etc/pam.d/login"
|
|
cat << EOF >> "${var_target}/etc/pam.d/login"
|
|
#
|
|
# The PAM configuration file for the Shadow 'login' service
|
|
#
|
|
|
|
# --- AUTH phase ---------------------------------------------------------------------------------------------------------------
|
|
|
|
# Root only on secure ttys listed in '/etc/securetty' (fail fast, no prompts).
|
|
auth requisite pam_securetty.so
|
|
|
|
# Disallows other than root logins when /etc/nologin exists. (Replaces the 'NOLOGINS_FILE' option from login.defs).
|
|
auth requisite pam_nologin.so
|
|
|
|
# Enforce a minimal delay in case of failure (in microseconds). (Replaces the 'FAIL_DELAY' setting from login.defs).
|
|
# Note that other modules may require another minimal delay. (For example, to disable any delay, you should add the 'nodelay'
|
|
# option to pam_unix).
|
|
auth optional pam_faildelay.so delay=3200000 # 3.2 seconds
|
|
|
|
# Outputs an issue file prior to each login prompt (Replaces the ISSUE_FILE option from login.defs). Uncomment for use.
|
|
#auth required pam_issue.so issue=/etc/issue
|
|
|
|
# SELinux needs to be the first session rule. This ensures that any lingering context has been cleared. Without this it is
|
|
# possible that a module could execute code in the wrong domain. When the module is present, "required" would be sufficient
|
|
# (When SELinux is disabled, this returns success.)
|
|
session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so close
|
|
|
|
# Sets the loginuid process attribute
|
|
session required pam_loginuid.so
|
|
|
|
# Prints the message of the day upon successful login. (Replaces the 'MOTD_FILE' option in login.defs). This includes a
|
|
# dynamically generated part from /run/motd.dynamic, and a static (admin-editable) part from /etc/motd.
|
|
session optional pam_motd.so motd=/run/motd.dynamic
|
|
session optional pam_motd.so noupdate
|
|
|
|
# SELinux needs to intervene at login time to ensure that the process starts in the proper default security context. Only
|
|
# sessions which are intended to run in the user's context should be run after this. The module pam_selinux.so changes the
|
|
# SELinux context of the used TTY and configures SELinux in order to transition to the user context with the next execve()
|
|
# call.
|
|
session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so open
|
|
# When the module is present, "required" would be sufficient (When SELinux is disabled, this returns success.)
|
|
|
|
# This module parses environment configuration file(s) and also allows you to use an extended config file
|
|
# /etc/security/pam_env.conf. Parsing /etc/environment needs "readenv=1"
|
|
session required pam_env.so readenv=1
|
|
# Locale variables can also be set in /etc/default/locale reading this file *in addition to /etc/environment* does not hurt.
|
|
session required pam_env.so readenv=1 envfile=/etc/default/locale
|
|
|
|
# Standard password for the target account (root or other). pam_unix.so with: try_first_pass nodelay (without nullok).
|
|
@include common-auth
|
|
|
|
|
|
# ===== CISS 2FA block ========
|
|
|
|
# If gate returns SUCCESS => skip next two lines (no TOTP).
|
|
auth [success=2 default=ignore] pam_exec.so quiet /usr/local/libexec/ciss_pam_2fa_gate.sh
|
|
|
|
# For listed users: enforce that the secret file exists, else deny without prompting.
|
|
# pam_google_authenticator will itself fail if the file is absent.
|
|
# No 'nullok' here: listed users MUST have a secret; missing -> hard fail.
|
|
auth required pam_echo.so file=/etc/ciss/pam_login_totp.prompt
|
|
auth required pam_google_authenticator.so
|
|
|
|
# ===== CISS 2FA block end =====
|
|
|
|
|
|
# This allows certain extra groups to be granted to a user based on things like time of day, tty, service, and user. Please
|
|
# edit /etc/security/group.conf to fit your needs (Replaces the 'CONSOLE_GROUPS' option in login.defs).
|
|
auth optional pam_group.so
|
|
|
|
# Uncomment and edit /etc/security/time.conf if you need to set time restraint on logins. (Replaces the 'PORTTIME_CHECKS_ENAB'
|
|
# option from login.defs as well as /etc/porttime).
|
|
#account requisite pam_time.so
|
|
|
|
# Uncomment and edit /etc/security/access.conf if you need to set access limits. (Replaces /etc/login.access file).
|
|
#account required pam_access.so
|
|
|
|
# Sets up user limits according to /etc/security/limits.conf. (Replaces the use of /etc/limits in old login).
|
|
session required pam_limits.so
|
|
|
|
# Prints the status of the user's mailbox upon successful login (Replaces the 'MAIL_CHECK_ENAB' option from login.defs).
|
|
# This also defines the MAIL environment variable. However, userdel also needs MAIL_DIR and MAIL_FILE variables in
|
|
# /etc/login.defs to make sure that removing a user also removes the user's mail spool file. See comments in /etc/login.defs.
|
|
session optional pam_mail.so standard
|
|
|
|
# Create a new session keyring.
|
|
session optional pam_keyinit.so force revoke
|
|
|
|
# Console-only access control for this service (do NOT also enable in common-account).
|
|
account requisite pam_access.so
|
|
|
|
# Standard Un*x account and session
|
|
@include common-account
|
|
@include common-session
|
|
@include common-password
|
|
|
|
# vim: number et ts=2 sw=2 sts=2 ai tw=128 ft=conf
|
|
EOF
|
|
do_log "info" "file_only" "4520() Written: [/etc/pam.d/login]."
|
|
|
|
cat << 'EOF' >| "${var_target}/etc/ciss/pam_login_totp.prompt"
|
|
Please enter your 6-digit TOTP or 8-digit Backup code:
|
|
EOF
|
|
chmod 0444 "${var_target}/etc/ciss/pam_login_totp.prompt"
|
|
do_log "info" "file_only" "4520() Written: [/etc/ciss/pam_login_totp.prompt]."
|
|
|
|
return 0
|
|
}
|
|
### Prevents accidental 'unset -f'.
|
|
# shellcheck disable=SC2034
|
|
readonly -f write_pam_login
|
|
|
|
#######################################
|
|
# Writes CISS Header for '/etc/pam.d/sshd'.
|
|
# Globals:
|
|
# None
|
|
# Arguments:
|
|
# 1: TARGET
|
|
# Returns:
|
|
# 0: on success
|
|
#######################################
|
|
write_pam_sshd() {
|
|
### Declare Arrays, HashMaps, and Variables.
|
|
declare -r var_target="$1"
|
|
|
|
mv "${var_target}/etc/pam.d/sshd" "${var_target}/root/.ciss/cdi/backup/etc/pam.d/sshd"
|
|
|
|
insert_header "${var_target}/etc/pam.d/sshd"
|
|
insert_comments "${var_target}/etc/pam.d/sshd"
|
|
cat << EOF >> "${var_target}/etc/pam.d/sshd"
|
|
#
|
|
# PAM configuration for the Secure Shell service
|
|
#
|
|
|
|
auth optional pam_warn.so
|
|
|
|
# ===== CISS 2FA block ========
|
|
|
|
# If gate returns SUCCESS => skip next two lines (no TOTP).
|
|
auth [success=2 default=ignore] pam_exec.so quiet /usr/local/libexec/ciss_pam_2fa_gate.sh
|
|
|
|
# For listed users: enforce that the secret file exists, else deny without prompting.
|
|
# pam_google_authenticator will itself fail if the file is absent.
|
|
# No 'nullok' here: listed users MUST have a secret; missing -> hard fail.
|
|
auth required pam_echo.so file=/etc/ciss/pam_ssh_totp.prompt
|
|
auth required pam_google_authenticator.so
|
|
|
|
# For non-2FA users KI must be a silent success to satisfy AuthenticationMethods.
|
|
auth sufficient pam_permit.so
|
|
|
|
# ===== CISS 2FA block end =====
|
|
|
|
|
|
# Keep the rest as shipped by Debian. It will be short-circuited by pam_permit for KI and never reached for 2FA users after
|
|
# successful GA.
|
|
|
|
# Standard password for the target account (root or other). pam_unix.so with: try_first_pass nodelay (without nullok).
|
|
# (omitted deliberately: CISS SSH uses KI/TOTP only; no password path)
|
|
# @include common-auth
|
|
|
|
# Disallow non-root logins when /etc/nologin exists.
|
|
account required pam_nologin.so
|
|
|
|
# Uncomment and edit /etc/security/access.conf if you need to set complex access limits that are hard to express in sshd_config.
|
|
#account required pam_access.so
|
|
|
|
# Standard Un*x authorization.
|
|
@include common-account
|
|
|
|
# SELinux needs to be the first session rule. This ensures that any lingering context has been cleared. Without this it is
|
|
# possible that a module could execute code in the wrong domain.
|
|
session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so close
|
|
|
|
# Set the loginuid process attribute.
|
|
session required pam_loginuid.so
|
|
|
|
# Create a new session keyring.
|
|
session optional pam_keyinit.so force revoke
|
|
|
|
# Standard Un*x session setup and teardown.
|
|
@include common-session
|
|
|
|
# Print the message of the day upon successful login. This includes a dynamically generated part from /run/motd.dynamic and a
|
|
# static (admin-editable) part from /etc/motd.
|
|
session optional pam_motd.so motd=/run/motd.dynamic
|
|
session optional pam_motd.so noupdate
|
|
|
|
# Print the status of the user's mailbox upon successful login.
|
|
session optional pam_mail.so standard noenv
|
|
|
|
# Sets up user limits according to /etc/security/limits.conf. (Replaces the use of /etc/limits in old login).
|
|
session required pam_limits.so
|
|
|
|
# Read environment variables from /etc/environment and /etc/security/pam_env.conf.
|
|
session required pam_env.so
|
|
|
|
# In Debian 4.0 (etch), locale-related environment variables were moved to /etc/default/locale, so read that as well.
|
|
session required pam_env.so envfile=/etc/default/locale
|
|
|
|
# SELinux needs to intervene at login time to ensure that the process starts in the proper default security context. Only
|
|
# sessions which are intended to run in the user's context should be run after this.
|
|
session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so open
|
|
|
|
# Standard Un*x password updating.
|
|
@include common-password
|
|
|
|
# vim: number et ts=2 sw=2 sts=2 ai tw=128 ft=conf
|
|
EOF
|
|
|
|
do_log "info" "file_only" "4520() Written: [/etc/pam.d/sshd]."
|
|
|
|
cat << 'EOF' >| "${var_target}/etc/ciss/pam_ssh_totp.prompt"
|
|
Please enter your 6-digit TOTP or 8-digit Backup code:
|
|
EOF
|
|
chmod 0444 "${var_target}/etc/ciss/pam_ssh_totp.prompt"
|
|
do_log "info" "file_only" "4520() Written: [/etc/ciss/pam_ssh_totp.prompt]."
|
|
|
|
return 0
|
|
}
|
|
### Prevents accidental 'unset -f'.
|
|
# shellcheck disable=SC2034
|
|
readonly -f write_pam_sshd
|
|
|
|
#######################################
|
|
# Writes CISS Header for '/etc/pam.d/su'.
|
|
# Globals:
|
|
# None
|
|
# Arguments:
|
|
# 1: TARGET
|
|
# Returns:
|
|
# 0: on success
|
|
#######################################
|
|
write_pam_su() {
|
|
### Declare Arrays, HashMaps, and Variables.
|
|
declare -r var_target="$1"
|
|
|
|
mv "${var_target}/etc/pam.d/su" "${var_target}/root/.ciss/cdi/backup/etc/pam.d/su"
|
|
|
|
insert_header "${var_target}/etc/pam.d/su"
|
|
insert_comments "${var_target}/etc/pam.d/su"
|
|
cat << EOF >> "${var_target}/etc/pam.d/su"
|
|
#
|
|
# The PAM configuration file for the Shadow 'su' service
|
|
#
|
|
|
|
# If caller is already root, allow quickly without further auth:
|
|
auth sufficient pam_rootok.so
|
|
|
|
# Hardening of '/bin/su': only members of the group 'sudo' can su to root.
|
|
auth required pam_wheel.so group=sudo
|
|
|
|
# Standard password for the target account (root or other). pam_unix.so with: try_first_pass nodelay (without nullok).
|
|
@include common-auth
|
|
|
|
|
|
# ===== CISS 2FA block ========
|
|
|
|
# If gate returns SUCCESS => skip next two lines (no TOTP).
|
|
auth [success=2 default=ignore] pam_exec.so quiet /usr/local/libexec/ciss_pam_2fa_gate.sh
|
|
|
|
# For listed users: enforce that the secret file exists, else deny without prompting.
|
|
# pam_google_authenticator will itself fail if the file is absent.
|
|
# No 'nullok' here: listed users MUST have a secret; missing -> hard fail.
|
|
auth required pam_echo.so file=/etc/ciss/pam_su_totp.prompt
|
|
auth required pam_google_authenticator.so
|
|
|
|
# ===== CISS 2FA block end =====
|
|
|
|
|
|
@include common-account
|
|
session required pam_env.so
|
|
session required pam_env.so envfile=/etc/default/locale
|
|
@include common-session
|
|
|
|
# Sets up user limits according to /etc/security/limits.conf. (Replaces the use of /etc/limits in old login).
|
|
session required pam_limits.so
|
|
|
|
# vim: number et ts=2 sw=2 sts=2 ai tw=128 ft=conf
|
|
EOF
|
|
|
|
do_log "info" "file_only" "4520() Written: [/etc/pam.d/su]."
|
|
|
|
cat << 'EOF' >| "${var_target}/etc/ciss/pam_su_totp.prompt"
|
|
Please enter the 6-digit TOTP or 8-digit Backup code of the target user:
|
|
EOF
|
|
chmod 0444 "${var_target}/etc/ciss/pam_su_totp.prompt"
|
|
do_log "info" "file_only" "4520() Written: [/etc/ciss/pam_su_totp.prompt]."
|
|
|
|
return 0
|
|
}
|
|
### Prevents accidental 'unset -f'.
|
|
# shellcheck disable=SC2034
|
|
readonly -f write_pam_su
|
|
|
|
#######################################
|
|
# Writes CISS Header for '/etc/pam.d/su-l'.
|
|
# Globals:
|
|
# None
|
|
# Arguments:
|
|
# 1: TARGET
|
|
# Returns:
|
|
# 0: on success
|
|
#######################################
|
|
write_pam_su-l() {
|
|
### Declare Arrays, HashMaps, and Variables.
|
|
declare -r var_target="$1"
|
|
|
|
mv "${var_target}/etc/pam.d/su-l" "${var_target}/root/.ciss/cdi/backup/etc/pam.d/su-l"
|
|
|
|
cat << EOF >| "${var_target}/etc/pam.d/su-l"
|
|
#%PAM-1.0
|
|
# su-l: login-shell semantics; reuse 'su' stacks.
|
|
|
|
# Reuse exactly the 'su' stacks (incl. CISS 2FA in auth):
|
|
auth include su
|
|
account include su
|
|
password include su
|
|
|
|
# Login-shell extra, then reuse 'su' session (which already has pam_env):
|
|
session optional pam_keyinit.so force revoke
|
|
session include su
|
|
|
|
# vim: number et ts=2 sw=2 sts=2 ai tw=128 ft=conf
|
|
EOF
|
|
|
|
do_log "info" "file_only" "4520() Written: [/etc/pam.d/su-l]."
|
|
|
|
return 0
|
|
}
|
|
### Prevents accidental 'unset -f'.
|
|
# shellcheck disable=SC2034
|
|
readonly -f write_pam_su-l
|
|
|
|
#######################################
|
|
# Writes CISS Header for '/etc/pam.d/sudo'.
|
|
# Globals:
|
|
# None
|
|
# Arguments:
|
|
# 1: TARGET
|
|
# Returns:
|
|
# 0: on success
|
|
#######################################
|
|
write_pam_sudo() {
|
|
### Declare Arrays, HashMaps, and Variables.
|
|
declare -r var_target="$1"
|
|
|
|
mv "${var_target}/etc/pam.d/sudo-i" "${var_target}/root/.ciss/cdi/backup/etc/pam.d/sudo"
|
|
|
|
insert_header "${var_target}/etc/pam.d/sudo"
|
|
insert_comments "${var_target}/etc/pam.d/sudo"
|
|
cat << EOF >> "${var_target}/etc/pam.d/sudo"
|
|
#
|
|
# PAM configuration for the sudo service
|
|
#
|
|
|
|
# Sets up user limits according to /etc/security/limits.conf. (Replaces the use of /etc/limits in old login).
|
|
session required pam_limits.so
|
|
|
|
# Standard password for the target account (root or other). pam_unix.so with: try_first_pass nodelay (without nullok).
|
|
@include common-auth
|
|
@include common-account
|
|
@include common-session-noninteractive
|
|
|
|
# vim: number et ts=2 sw=2 sts=2 ai tw=128 ft=conf
|
|
EOF
|
|
|
|
do_log "info" "file_only" "4520() Written: [/etc/pam.d/sudo]."
|
|
|
|
return 0
|
|
}
|
|
### Prevents accidental 'unset -f'.
|
|
# shellcheck disable=SC2034
|
|
readonly -f write_pam_sudo
|
|
|
|
#######################################
|
|
# Writes CISS Header for '/etc/pam.d/sudo-i'.
|
|
# Globals:
|
|
# None
|
|
# Arguments:
|
|
# 1: TARGET
|
|
# Returns:
|
|
# 0: on success
|
|
#######################################
|
|
write_pam_sudo-i() {
|
|
### Declare Arrays, HashMaps, and Variables.
|
|
declare -r var_target="$1"
|
|
|
|
[[ -f "${var_target}/etc/pam.d/sudo-i" ]] && mv "${var_target}/etc/pam.d/sudo-i" "${var_target}/root/.ciss/cdi/backup/etc/pam.d/sudo-i"
|
|
|
|
insert_header "${var_target}/etc/pam.d/sudo-i"
|
|
insert_comments "${var_target}/etc/pam.d/sudo-i"
|
|
cat << EOF >> "${var_target}/etc/pam.d/sudo-i"
|
|
#
|
|
# PAM configuration for the sudo-i service
|
|
#
|
|
|
|
# Reuse of recent auth: handled by sudoers 'timestamp_timeout', not by PAM.
|
|
#auth sufficient pam_timestamp.so
|
|
|
|
# Standard password for the target account (root or other). pam_unix.so with: try_first_pass nodelay (without nullok).
|
|
@include common-auth
|
|
|
|
|
|
# ===== CISS 2FA block ========
|
|
|
|
# If gate returns SUCCESS => skip next two lines (no TOTP).
|
|
auth [success=2 default=ignore] pam_exec.so quiet /usr/local/libexec/ciss_pam_2fa_gate.sh
|
|
|
|
# For listed users: enforce that the secret file exists, else deny without prompting.
|
|
# pam_google_authenticator will itself fail if the file is absent; we add a clear hint before it.
|
|
# No 'nullok' here: listed users MUST have a secret; missing -> hard fail.
|
|
auth required pam_echo.so file=/etc/ciss/pam_sudo_i_totp.prompt
|
|
auth required pam_google_authenticator.so
|
|
|
|
# ===== CISS 2FA block end =====
|
|
|
|
|
|
# Accounts, sessions:
|
|
@include common-account
|
|
@include common-session
|
|
|
|
# Sets up user limits according to /etc/security/limits.conf. (Replaces the use of /etc/limits in old login).
|
|
session required pam_limits.so
|
|
|
|
# Maintain a pam_timestamp ticket on successful sudo to suppress re-prompts, handled by sudoers 'timestamp_timeout', not by PAM.
|
|
#session optional pam_timestamp.so
|
|
|
|
# vim: number et ts=2 sw=2 sts=2 ai tw=128 ft=conf
|
|
EOF
|
|
|
|
do_log "info" "file_only" "4520() Written: [/etc/pam.d/sudo-i]."
|
|
|
|
cat << 'EOF' >| "${var_target}/etc/ciss/pam_sudo_i_totp.prompt"
|
|
Please enter your 6-digit TOTP or 8-digit Backup code:
|
|
EOF
|
|
chmod 0444 "${var_target}/etc/ciss/pam_sudo_i_totp.prompt"
|
|
do_log "info" "file_only" "4520() Written: [/etc/ciss/pam_sudo_i_totp.prompt]."
|
|
|
|
return 0
|
|
}
|
|
### Prevents accidental 'unset -f'.
|
|
# shellcheck disable=SC2034
|
|
readonly -f write_pam_sudo-i
|
|
|
|
#######################################
|
|
# Use the official ohmyzsh-installer but force non-interactive behavior; do not run zsh; do not chsh.
|
|
# Globals:
|
|
# None
|
|
# Arguments:
|
|
# 1: Username
|
|
# 2: Target
|
|
# Returns:
|
|
# 0: on success
|
|
#######################################
|
|
zsh_omz_installer() {
|
|
### Declare Arrays, HashMaps, and Variables.
|
|
declare -r var_user="${1}" var_target="${2}"
|
|
|
|
### Install Oh My Zsh and two plugins for a given user (non-interactive, idempotent).
|
|
### Args to payload: $1 = username (e.g., "root" or "alice")
|
|
chroot_stdin "${var_target}" "__payload__" -- "${var_user}" <<'EOF'
|
|
export LC_ALL=C
|
|
user="$1"
|
|
|
|
### Resolve account data
|
|
pwline="$(getent passwd "${user}" || true)"
|
|
[[ -n "${pwline}" ]] || { echo "User not found: ${user}" >&2; exit 1; }
|
|
IFS=: read -r _ _ uid gid _ home _ <<<"${pwline}"
|
|
|
|
if [[ "${uid}" -eq 0 ]]; then
|
|
### root user: no su needed
|
|
/bin/bash -s <<'ZSHROOT'
|
|
#!/bin/bash
|
|
set -Ceuo pipefail
|
|
|
|
export LC_ALL=C
|
|
|
|
export XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-${HOME}/.config}"
|
|
export XDG_DATA_HOME="${XDG_DATA_HOME:-${HOME}/.local/share}"
|
|
export XDG_CACHE_HOME="${XDG_CACHE_HOME:-${HOME}/.cache}"
|
|
export XDG_STATE_HOME="${XDG_STATE_HOME:-${HOME}/.local/state}"
|
|
export XDG_CONFIG_DIRS="${XDG_CONFIG_DIRS:-/etc/xdg}"
|
|
export XDG_DATA_DIRS="${XDG_DATA_DIRS:-/usr/local/share:/usr/share}"
|
|
|
|
umask 077
|
|
|
|
### We are running as the target user here
|
|
ZSH_DIR="${HOME}/.oh-my-zsh"
|
|
|
|
### If ZSH_DIR exists but is EMPTY (e.g., previous aborted run), remove it, so the installer can proceed.
|
|
if [[ -d "${ZSH_DIR}" ]] && [[ -z "$(ls -A "${ZSH_DIR}")" ]]; then
|
|
rm -rf "${ZSH_DIR}"
|
|
fi
|
|
|
|
### If already installed (git repo present), skip the installer.
|
|
if [ -d "${ZSH_DIR}/.git" ]; then
|
|
:
|
|
else
|
|
### Download installer to a temp file and run it with non-interactive env.
|
|
inst="$(mktemp)"
|
|
if command -v wget >/dev/null 2>&1; then
|
|
wget -qO "${inst}" https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh
|
|
else
|
|
curl -fsSL -o "${inst}" https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh
|
|
fi
|
|
### Ensure that ZSH is not set for the installer, and keep it fully non-interactive.
|
|
RUNZSH=no CHSH=no KEEP_ZSHRC=yes env -u ZSH sh "${inst}"
|
|
rm -f "${inst}"
|
|
fi
|
|
|
|
### Install plugins (shallow clone; idempotent)
|
|
ZSH_CUSTOM="${ZSH_DIR}/custom"
|
|
mkdir -p "${ZSH_CUSTOM}/plugins"
|
|
[[ -d "${ZSH_CUSTOM}/plugins/zsh-autosuggestions/.git" ]] || \
|
|
git clone --depth 1 https://github.com/zsh-users/zsh-autosuggestions "${ZSH_CUSTOM}/plugins/zsh-autosuggestions"
|
|
[[ -d "${ZSH_CUSTOM}/plugins/zsh-syntax-highlighting/.git" ]] || \
|
|
git clone --depth 1 https://github.com/zsh-users/zsh-syntax-highlighting "${ZSH_CUSTOM}/plugins/zsh-syntax-highlighting"
|
|
|
|
### '~/.zshrc' will be updated later in the main CISS.debian.installer environment.
|
|
|
|
### Do NOT start zsh here and do NOT chsh (RUNZSH/CHSH handled above).
|
|
:
|
|
ZSHROOT
|
|
|
|
### ----------------------------------------------------------------------------------------------------------------------------
|
|
|
|
else
|
|
su - "${user}" -s /bin/bash <<'ZSHUSER'
|
|
#!/bin/bash
|
|
set -Ceuo pipefail
|
|
|
|
export LC_ALL=C
|
|
|
|
export XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-${HOME}/.config}"
|
|
export XDG_DATA_HOME="${XDG_DATA_HOME:-${HOME}/.local/share}"
|
|
export XDG_CACHE_HOME="${XDG_CACHE_HOME:-${HOME}/.cache}"
|
|
export XDG_STATE_HOME="${XDG_STATE_HOME:-${HOME}/.local/state}"
|
|
export XDG_CONFIG_DIRS="${XDG_CONFIG_DIRS:-/etc/xdg}"
|
|
export XDG_DATA_DIRS="${XDG_DATA_DIRS:-/usr/local/share:/usr/share}"
|
|
|
|
umask 077
|
|
|
|
### We are running as the target user here
|
|
ZSH_DIR="${HOME}/.oh-my-zsh"
|
|
|
|
### If ZSH_DIR exists but is EMPTY (e.g., previous aborted run), remove it, so the installer can proceed.
|
|
if [[ -d "${ZSH_DIR}" ]] && [[ -z "$(ls -A "${ZSH_DIR}")" ]]; then
|
|
rm -rf "${ZSH_DIR}"
|
|
fi
|
|
|
|
### If already installed (git repo present), skip the installer.
|
|
if [ -d "${ZSH_DIR}/.git" ]; then
|
|
:
|
|
else
|
|
### Download installer to a temp file and run it with non-interactive env.
|
|
inst="$(mktemp)"
|
|
if command -v wget >/dev/null 2>&1; then
|
|
wget -qO "${inst}" https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh
|
|
else
|
|
curl -fsSL -o "${inst}" https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh
|
|
fi
|
|
### Ensure that ZSH is not set for the installer, and keep it fully non-interactive.
|
|
RUNZSH=no CHSH=no KEEP_ZSHRC=yes env -u ZSH sh "${inst}"
|
|
rm -f "${inst}"
|
|
fi
|
|
|
|
### Install plugins (shallow clone; idempotent)
|
|
ZSH_CUSTOM="${ZSH_DIR}/custom"
|
|
mkdir -p "${ZSH_CUSTOM}/plugins"
|
|
[[ -d "${ZSH_CUSTOM}/plugins/zsh-autosuggestions/.git" ]] || \
|
|
git clone --depth 1 https://github.com/zsh-users/zsh-autosuggestions "${ZSH_CUSTOM}/plugins/zsh-autosuggestions"
|
|
[[ -d "${ZSH_CUSTOM}/plugins/zsh-syntax-highlighting/.git" ]] || \
|
|
git clone --depth 1 https://github.com/zsh-users/zsh-syntax-highlighting "${ZSH_CUSTOM}/plugins/zsh-syntax-highlighting"
|
|
|
|
### '~/.zshrc' will be updated later in the main CISS.debian.installer environment.
|
|
|
|
### Do NOT start zsh here and do NOT chsh (RUNZSH/CHSH handled above).
|
|
:
|
|
ZSHUSER
|
|
|
|
### ----------------------------------------------------------------------------------------------------------------------------
|
|
fi
|
|
EOF
|
|
|
|
return 0
|
|
}
|
|
### Prevents accidental 'unset -f'.
|
|
# shellcheck disable=SC2034
|
|
readonly -f zsh_omz_installer
|
|
# vim: number et ts=2 sw=2 sts=2 ai tw=128 ft=sh
|