* added support for standard home directory location for public keys ($HOME/.ssh). Set $key_location=use_sshd in update_ssh.conf[.local] with AuthorizedKeysFile set to the default value in sshd_config (or use the value .ssh/authorized_keys).

* added support for SELinux (CentOS/RHEL 8.x)
* various fixes (incl. shellcheck + quoting)
This commit is contained in:
Patrick Van der Veken 2020-12-30 17:06:02 +01:00
parent b8004afe62
commit 77a332e324
4 changed files with 321 additions and 157 deletions

View File

@ -1,5 +1,14 @@
<p align="center"><img src="logo.png" alt="SSH Controls Logo"></p> <p align="center"><img src="logo.png" alt="SSH Controls Logo"></p>
## What's new
:loudspeaker: **30/12/2020**:
* added support for standard home directory location for public keys (`$HOME/.ssh`). Set `$key_location=use_sshd` in `update_ssh.conf[.local]` with `AuthorizedKeysFile` set to the default value in `sshd_config` (or use the value `.ssh/authorized_keys`). *Caveat*: SSH Controls will not create parent nor intermediate directories in the public key file path if they are missing.
* added support for SELinux (CentOS/RHEL 8.x)
* various fixes
## About
SSH Controls is a light-weight SSH **public key** distribution & management framework SSH Controls is a light-weight SSH **public key** distribution & management framework
* uses a **desired state** model: SSH Controls *pushes* public keys from a key master (or slave) server onto client host(s) and applies them according to the central configuration. * uses a **desired state** model: SSH Controls *pushes* public keys from a key master (or slave) server onto client host(s) and applies them according to the central configuration.
@ -8,7 +17,7 @@ SSH Controls is a light-weight SSH **public key** distribution & management fram
* supports a **Master→Slave→Client** model so that information can be propagated within more complex LAN set-ups. * supports a **Master→Slave→Client** model so that information can be propagated within more complex LAN set-ups.
* **shields** public keys from owners/users on client systems: SSH Controls requires the standard `sshd_config` to be reconfigured with an alternate path for the `AuthorizedKeysFile` setting so that public keys are stored in common location which cannot be manipulated by the owners of the public keys. This allows for more administrative control and better security. * can **shield** public keys from owners/users on client systems: SSH Controls may require the standard `sshd_config` to be reconfigured with an alternate path for the `AuthorizedKeysFile` setting so that public keys are stored in common location which cannot be manipulated by the owners of the public keys. This allows for more administrative control and better security.
* performs operations with **least privileges**: copy/distribute operations are performed with a low-privileged account. Only the actual key updates requires super-user privileges which need to be configured via SUDO. * performs operations with **least privileges**: copy/distribute operations are performed with a low-privileged account. Only the actual key updates requires super-user privileges which need to be configured via SUDO.

View File

@ -43,7 +43,7 @@
# or LOCAL_CONFIG_FILE instead # or LOCAL_CONFIG_FILE instead
# define the version (YYYY-MM-DD) # define the version (YYYY-MM-DD)
typeset -r SCRIPT_VERSION="2020-05-28" typeset -r SCRIPT_VERSION="2020-12-30"
# name of the global configuration file (script) # name of the global configuration file (script)
typeset -r GLOBAL_CONFIG_FILE="manage_ssh.conf" typeset -r GLOBAL_CONFIG_FILE="manage_ssh.conf"
# name of the local configuration file (script) # name of the local configuration file (script)
@ -289,11 +289,11 @@ fi
# --targets # --targets
if [[ -n "${ARG_TARGETS}" ]] if [[ -n "${ARG_TARGETS}" ]]
then then
: > ${TMP_FILE} : > "${TMP_FILE}"
# write comma-separated target list to the temporary file # write comma-separated target list to the temporary file
print "${ARG_TARGETS}" | tr -s ',' '\n' | while read TARGET_HOST print "${ARG_TARGETS}" | tr -s ',' '\n' | while read -r TARGET_HOST
do do
print ${TARGET_HOST} >>${TMP_FILE} print "${TARGET_HOST}" >>"${TMP_FILE}"
done done
fi fi
# --update + --fix-local + --resolve-alias # --update + --fix-local + --resolve-alias
@ -333,7 +333,7 @@ function check_root_user
typeset UID="" typeset UID=""
# shellcheck disable=SC2046 # shellcheck disable=SC2046
(IFS='()'; set -- $(id); print $2) | read UID (IFS='()'; set -- $(id); print "$2") | read -r UID
if [[ "${UID}" = "root" ]] if [[ "${UID}" = "root" ]]
then then
return 0 return 0
@ -427,6 +427,7 @@ if (( DO_SSH_AGENT ))
then then
# ssh-agent # ssh-agent
which ssh-agent >/dev/null 2>/dev/null which ssh-agent >/dev/null 2>/dev/null
# shellcheck disable=SC2181
if (( $? > 0 )) if (( $? > 0 ))
then then
print -u2 "WARN: ssh-agent not available on ${HOST_NAME}" print -u2 "WARN: ssh-agent not available on ${HOST_NAME}"
@ -457,24 +458,25 @@ typeset KEY_FILE=""
typeset KEY_FIELDS="" typeset KEY_FIELDS=""
# access should have 3 fields # access should have 3 fields
grep -v -E -e '^#|^$' "${LOCAL_DIR}/access" 2>/dev/null | while read ACCESS_LINE grep -v -E -e '^#|^$' "${LOCAL_DIR}/access" 2>/dev/null | while read -r ACCESS_LINE
do do
ACCESS_FIELDS=$(count_fields "${ACCESS_LINE}" ":") ACCESS_FIELDS=$(count_fields "${ACCESS_LINE}" ":")
(( ACCESS_FIELDS != 3 )) && die "line '${ACCESS_LINE}' in access file has missing or too many field(s) (should be 3)" (( ACCESS_FIELDS != 3 )) && die "line '${ACCESS_LINE}' in access file has missing or too many field(s) (should be 3)"
done done
# alias should have 2 fields # alias should have 2 fields
grep -v -E -e '^#|^$' "${LOCAL_DIR}/alias" 2>/dev/null | while read ALIASES_LINE grep -v -E -e '^#|^$' "${LOCAL_DIR}/alias" 2>/dev/null | while read -r ALIASES_LINE
do do
ALIAS_FIELDS=$(count_fields "${ALIASES_LINE}" ":") ALIAS_FIELDS=$(count_fields "${ALIASES_LINE}" ":")
(( ALIAS_FIELDS != 2 )) && die "line '${ALIASES_LINE}' in alias file has missing or too many field(s) (should be 2)" (( ALIAS_FIELDS != 2 )) && die "line '${ALIASES_LINE}' in alias file has missing or too many field(s) (should be 2)"
done done
# key files should have 3 fields # key files should have 3 fields
ls -1 ${LOCAL_DIR}/keys.d/* ${LOCAL_DIR}/keys 2>/dev/null | while read KEY_FILE # shellcheck disable=SC2012
ls -1 "${LOCAL_DIR}"/keys.d/* "${LOCAL_DIR}"/keys 2>/dev/null | while read -r KEY_FILE
do do
grep -v -E -e '^#|^$' ${KEY_FILE} 2>/dev/null |\ grep -v -E -e '^#|^$' "${KEY_FILE}" 2>/dev/null |\
while read KEY_LINE while read -r KEY_LINE
do do
KEY_FIELDS=$(count_fields "${KEY_LINE}" ",") KEY_FIELDS=$(count_fields "${KEY_LINE}" ",")
(( KEY_FIELDS != 3 )) && die "line '${KEY_LINE}' in a keys file has missing or too many field(s) (should be 3)" (( KEY_FIELDS != 3 )) && die "line '${KEY_LINE}' in a keys file has missing or too many field(s) (should be 3)"
@ -530,7 +532,7 @@ typeset NUM_FIELDS=0
NUM_FIELDS=$(print "${CHECK_LINE}" | awk -F "${CHECK_DELIM}" '{ print NF }' 2>/dev/null) NUM_FIELDS=$(print "${CHECK_LINE}" | awk -F "${CHECK_DELIM}" '{ print NF }' 2>/dev/null)
print ${NUM_FIELDS} print "${NUM_FIELDS}"
return 0 return 0
} }
@ -547,7 +549,7 @@ if [[ -n "$1" ]]
then then
if (( ARG_LOG > 0 )) if (( ARG_LOG > 0 ))
then then
print - "$*" | while read LOG_LINE print - "$*" | while read -r LOG_LINE
do do
# check for leading log sigils and retain them # check for leading log sigils and retain them
case "${LOG_LINE}" in case "${LOG_LINE}" in
@ -567,10 +569,10 @@ then
LOG_SIGIL="ERROR" LOG_SIGIL="ERROR"
;; ;;
esac esac
print "${NOW}: ${LOG_SIGIL}: [$$]:" "${LOG_LINE}" >>${LOG_FILE} print "${NOW}: ${LOG_SIGIL}: [$$]:" "${LOG_LINE}" >>"${LOG_FILE}"
done done
fi fi
print - "$*" | while read LOG_LINE print - "$*" | while read -r LOG_LINE
do do
# check for leading log sigils and retain them # check for leading log sigils and retain them
case "${LOG_LINE}" in case "${LOG_LINE}" in
@ -622,6 +624,7 @@ Parameters:
--fix-local : fix permissions on the local SSH controls repository --fix-local : fix permissions on the local SSH controls repository
(local SSH controls repository given by --fix-dir) (local SSH controls repository given by --fix-dir)
--fix-remote : fix permissions on the remote SSH controls repository --fix-remote : fix permissions on the remote SSH controls repository
(only valud when using a centralized public key location)
--fix-user : UNIX account to own SSH controls files [default: current user] --fix-user : UNIX account to own SSH controls files [default: current user]
--help|-h : this help text --help|-h : this help text
--local-dir : location of the SSH control files on the local filesystem. --local-dir : location of the SSH control files on the local filesystem.
@ -668,8 +671,9 @@ typeset TMP_WORK_DIR=""
typeset BLACKLIST_FILE="" typeset BLACKLIST_FILE=""
# convert line to hostname # convert line to hostname
SERVER=${SERVER%%;*} SERVER="${SERVER%%;*}"
resolve_host ${SERVER} resolve_host "${SERVER}"
# shellcheck disable=SC2181
if (( $? > 0 )) if (( $? > 0 ))
then then
warn "could not lookup host ${SERVER}, skipping" warn "could not lookup host ${SERVER}, skipping"
@ -686,10 +690,11 @@ for FILE in "${LOCAL_DIR}/access!660" \
"${SCRIPT_DIR}/${GLOBAL_CONFIG_FILE}!660" "${SCRIPT_DIR}/${GLOBAL_CONFIG_FILE}!660"
do do
# sftp transfer # sftp transfer
sftp_file ${FILE} ${SERVER} sftp_file "${FILE}" "${SERVER}"
COPY_RC=$? COPY_RC=$?
if (( COPY_RC == 0 )) if (( COPY_RC == 0 ))
then then
# shellcheck disable=SC2086
log "transferred ${FILE%!*} to ${SERVER}:${REMOTE_DIR}" log "transferred ${FILE%!*} to ${SERVER}:${REMOTE_DIR}"
else else
warn "failed to transfer ${FILE%!*} to ${SERVER}:${REMOTE_DIR} [RC=${COPY_RC}]" warn "failed to transfer ${FILE%!*} to ${SERVER}:${REMOTE_DIR} [RC=${COPY_RC}]"
@ -702,16 +707,17 @@ if [[ -n "${KEYS_DIR}" ]]
then then
# merge keys file(s) before copy (in a temporary location) # merge keys file(s) before copy (in a temporary location)
TMP_WORK_DIR="${TMP_DIR}/$0.${RANDOM}" TMP_WORK_DIR="${TMP_DIR}/$0.${RANDOM}"
mkdir -p ${TMP_WORK_DIR} mkdir -p "${TMP_WORK_DIR}"
# shellcheck disable=SC2181
if (( $? > 0 )) if (( $? > 0 ))
then then
die "unable to create temporary directory ${TMP_WORK_DIR} for mangling of 'keys' file" die "unable to create temporary directory ${TMP_WORK_DIR} for mangling of 'keys' file"
fi fi
TMP_MERGE_FILE="${TMP_WORK_DIR}/keys" TMP_MERGE_FILE="${TMP_WORK_DIR}/keys"
log "keys are stored in a DIRECTORY, first merging all keys into ${TMP_MERGE_FILE}" log "keys are stored in a DIRECTORY, first merging all keys into ${TMP_MERGE_FILE}"
cat ${KEYS_DIR}/* >${TMP_MERGE_FILE} cat "${KEYS_DIR}"/* >"${TMP_MERGE_FILE}"
# sftp transfer # sftp transfer
sftp_file "${TMP_MERGE_FILE}!640" ${SERVER} sftp_file "${TMP_MERGE_FILE}!640" "${SERVER}"
COPY_RC=$? COPY_RC=$?
if (( COPY_RC == 0 )) if (( COPY_RC == 0 ))
then then
@ -720,9 +726,9 @@ then
warn "failed to transfer ${TMP_MERGE_FILE%!*} to ${SERVER}:${REMOTE_DIR} [RC=${COPY_RC}]" warn "failed to transfer ${TMP_MERGE_FILE%!*} to ${SERVER}:${REMOTE_DIR} [RC=${COPY_RC}]"
ERROR_COUNT=$(( ERROR_COUNT + 1 )) ERROR_COUNT=$(( ERROR_COUNT + 1 ))
fi fi
[[ -d ${TMP_WORK_DIR} ]] && rm -rf ${TMP_WORK_DIR} 2>/dev/null [[ -d ${TMP_WORK_DIR} ]] && rm -rf "${TMP_WORK_DIR}" 2>/dev/null
else else
sftp_file "${KEYS_FILE}!640" ${SERVER} sftp_file "${KEYS_FILE}!640" "${SERVER}"
COPY_RC=$? COPY_RC=$?
if (( COPY_RC == 0 )) if (( COPY_RC == 0 ))
then then
@ -735,14 +741,14 @@ fi
# discover a keys blacklist file, also copy it across if we find one # discover a keys blacklist file, also copy it across if we find one
# never use a keys blacklist file from the local config though # never use a keys blacklist file from the local config though
[[ -r ${LOCAL_DIR}/update_ssh.conf ]] && \ [[ -r ${LOCAL_DIR}/update_ssh.conf ]] && \
BLACKLIST_FILE="$(grep -E -e '^blacklist_file' ${LOCAL_DIR}/update_ssh.conf 2>/dev/null | cut -f2 -d'=')" BLACKLIST_FILE=$(grep -E -e '^blacklist_file' "${LOCAL_DIR}/update_ssh.conf" 2>/dev/null | cut -f2 -d'=')
if [[ -n "${BLACKLIST_FILE}" ]] if [[ -n "${BLACKLIST_FILE}" ]]
then then
if [[ -r "${BLACKLIST_FILE}" ]] if [[ -r "${BLACKLIST_FILE}" ]]
then then
log "keys blacklist file found at ${BLACKLIST_FILE}" log "keys blacklist file found at ${BLACKLIST_FILE}"
# sftp transfer # sftp transfer
sftp_file "${BLACKLIST_FILE}!660" ${SERVER} sftp_file "${BLACKLIST_FILE}!660" "${SERVER}"
COPY_RC=$? COPY_RC=$?
if (( COPY_RC == 0 )) if (( COPY_RC == 0 ))
then then
@ -768,8 +774,9 @@ typeset DISTRIBUTE_OPTS=""
typeset RC=0 typeset RC=0
# convert line to hostname # convert line to hostname
SERVER=${SERVER%%;*} SERVER="${SERVER%%;*}"
resolve_host ${SERVER} resolve_host "${SERVER}"
# shellcheck disable=SC2181
if (( $? > 0 )) if (( $? > 0 ))
then then
warn "could not lookup host ${SERVER}, skipping" warn "could not lookup host ${SERVER}, skipping"
@ -782,13 +789,15 @@ then
fi fi
log "copying SSH controls on ${SERVER} in slave mode, this may take a while ..." log "copying SSH controls on ${SERVER} in slave mode, this may take a while ..."
( RC=0; ssh -A ${SSH_ARGS} ${SERVER} ${REMOTE_DIR}/${SCRIPT_NAME} --copy ${DISTRIBUTE_OPTS}; # shellcheck disable=SC2029
print "$?" > ${TMP_RC_FILE}; exit ( RC=0; ssh -A ${SSH_ARGS} "${SERVER}" "${REMOTE_DIR}/${SCRIPT_NAME} --copy ${DISTRIBUTE_OPTS}";
) 2>&1 | logc print "$?" > "${TMP_RC_FILE}"; exit
) 2>&1 | logc ""
# fetch return code from subshell # fetch return code from subshell
RC="$(< ${TMP_RC_FILE})" RC=$(< "${TMP_RC_FILE}")
# shellcheck disable=SC2086
return ${RC} return ${RC}
} }
@ -799,9 +808,9 @@ function do_cleanup
log "performing cleanup ..." log "performing cleanup ..."
# remove temporary file(s) # remove temporary file(s)
[[ -f ${TMP_FILE} ]] && rm -f ${TMP_FILE} >/dev/null 2>&1 [[ -f ${TMP_FILE} ]] && rm -f "${TMP_FILE}" >/dev/null 2>&1
[[ -f ${TMP_MERGE_FILE} ]] && rm -f ${TMP_MERGE_FILE} >/dev/null 2>&1 [[ -f ${TMP_MERGE_FILE} ]] && rm -f "${TMP_MERGE_FILE}" >/dev/null 2>&1
[[ -f ${TMP_RC_FILE} ]] && rm -f ${TMP_RC_FILE} >/dev/null 2>&1 [[ -f ${TMP_RC_FILE} ]] && rm -f "${TMP_RC_FILE}" >/dev/null 2>&1
log "*** finish of ${SCRIPT_NAME} [${CMD_LINE}] /$$@${HOST_NAME}/ ***" log "*** finish of ${SCRIPT_NAME} [${CMD_LINE}] /$$@${HOST_NAME}/ ***"
return 0 return 0
@ -820,8 +829,9 @@ typeset FIX_OPTS=""
typeset RC=0 typeset RC=0
# convert line to hostname # convert line to hostname
SERVER=${SERVER%%;*} SERVER="${SERVER%%;*}"
resolve_host ${SERVER} resolve_host "${SERVER}"
# shellcheck disable=SC2181
if (( $? > 0 )) if (( $? > 0 ))
then then
warn "could not lookup host ${SERVER}, skipping" warn "could not lookup host ${SERVER}, skipping"
@ -842,25 +852,29 @@ log "fixing SSH controls on ${SERVER} ..."
if [[ -z "${SSH_UPDATE_USER}" ]] if [[ -z "${SSH_UPDATE_USER}" ]]
then then
# own user w/ sudo # own user w/ sudo
( RC=0; ssh ${SSH_ARGS} ${SERVER} sudo -n ${REMOTE_DIR}/${SCRIPT_NAME} --fix-local --fix-dir=${SERVER_DIR} --fix-user=${SERVER_USER} ${FIX_OPTS}; # shellcheck disable=SC2029
print "$?" > ${TMP_RC_FILE}; exit ( RC=0; ssh ${SSH_ARGS} "${SERVER}" "sudo -n ${REMOTE_DIR}/${SCRIPT_NAME} --fix-local --fix-dir=${SERVER_DIR} --fix-user=${SERVER_USER} ${FIX_OPTS}";
) 2>&1 | logc print "$?" > "${TMP_RC_FILE}"; exit
) 2>&1 | logc ""
elif [[ "${SSH_UPDATE_USER}" != "root" ]] elif [[ "${SSH_UPDATE_USER}" != "root" ]]
then then
# other user w/ sudo # other user w/ sudo
( RC=0; ssh ${SSH_ARGS} ${SSH_UPDATE_USER}@${SERVER} sudo -n ${REMOTE_DIR}/${SCRIPT_NAME} --fix-local --fix-dir=${SERVER_DIR} --fix-user=${SERVER_USER} ${FIX_OPTS}; # shellcheck disable=SC2029
print "$?" > ${TMP_RC_FILE}; exit ( RC=0; ssh ${SSH_ARGS} "${SSH_UPDATE_USER}@${SERVER}" "sudo -n ${REMOTE_DIR}/${SCRIPT_NAME} --fix-local --fix-dir=${SERVER_DIR} --fix-user=${SERVER_USER} ${FIX_OPTS}";
) 2>&1 | logc print "$?" > "${TMP_RC_FILE}"; exit
) 2>&1 | logc ""
else else
# root user w/o sudo # root user w/o sudo
( RC=0; ssh ${SSH_ARGS} root@${SERVER} ${REMOTE_DIR}/${SCRIPT_NAME} --fix-local --fix-dir=${SERVER_DIR} --fix-user="root" ${FIX_OPTS}; # shellcheck disable=SC2029
print "$?" > ${TMP_RC_FILE}; exit ( RC=0; ssh ${SSH_ARGS} "root@${SERVER}" "${REMOTE_DIR}/${SCRIPT_NAME} --fix-local --fix-dir=${SERVER_DIR} --fix-user=root ${FIX_OPTS}";
) 2>&1 | logc print "$?" > "${TMP_RC_FILE}"; exit
) 2>&1 | logc ""
fi fi
# fetch return code from subshell # fetch return code from subshell
RC="$(< ${TMP_RC_FILE})" RC=$(< "${TMP_RC_FILE}")
# shellcheck disable=SC2086
return ${RC} return ${RC}
} }
@ -877,8 +891,9 @@ typeset FIX_OPTS=""
typeset RC=0 typeset RC=0
# convert line to hostname # convert line to hostname
SERVER=${SERVER%%;*} SERVER="${SERVER%%;*}"
resolve_host ${SERVER} resolve_host "${SERVER}"
# shellcheck disable=SC2181
if (( $? > 0 )) if (( $? > 0 ))
then then
warn "could not lookup host ${SERVER}, skipping" warn "could not lookup host ${SERVER}, skipping"
@ -896,13 +911,15 @@ then
fi fi
log "fixing SSH controls on ${SERVER} in slave mode, this may take a while ..." log "fixing SSH controls on ${SERVER} in slave mode, this may take a while ..."
( RC=0; ssh -A ${SSH_ARGS} ${SERVER} ${REMOTE_DIR}/${SCRIPT_NAME} --fix-remote --fix-dir=${SERVER_DIR} --fix-user=${SERVER_USER} ${FIX_OPTS}; # shellcheck disable=SC2029
print "$?" > ${TMP_RC_FILE}; exit ( RC=0; ssh -A ${SSH_ARGS} "${SERVER}" "${REMOTE_DIR}/${SCRIPT_NAME} --fix-remote --fix-dir=${SERVER_DIR} --fix-user=${SERVER_USER} ${FIX_OPTS}";
) 2>&1 | logc print "$?" > "${TMP_RC_FILE}"; exit
) 2>&1 | logc ""
# fetch return code from subshell # fetch return code from subshell
RC="$(< ${TMP_RC_FILE})" RC=$(< "${TMP_RC_FILE}")
# shellcheck disable=SC2086
return ${RC} return ${RC}
} }
@ -941,6 +958,9 @@ then
*release\ 7*) *release\ 7*)
RHEL_VERSION=7 RHEL_VERSION=7
;; ;;
*release\ 7*)
RHEL_VERSION=8
;;
*) *)
RHEL_VERSION="" RHEL_VERSION=""
;; ;;
@ -967,7 +987,7 @@ if [[ -n "$1" ]]
then then
if (( ARG_LOG > 0 )) if (( ARG_LOG > 0 ))
then then
print - "$*" | while read LOG_LINE print - "$*" | while read -r LOG_LINE
do do
# check for leading log sigils and retain them # check for leading log sigils and retain them
case "${LOG_LINE}" in case "${LOG_LINE}" in
@ -987,12 +1007,12 @@ then
LOG_SIGIL="INFO" LOG_SIGIL="INFO"
;; ;;
esac esac
print "${NOW}: ${LOG_SIGIL}: [$$]:" "${LOG_LINE}" >>${LOG_FILE} print "${NOW}: ${LOG_SIGIL}: [$$]:" "${LOG_LINE}" >>"${LOG_FILE}"
done done
fi fi
if (( ARG_VERBOSE > 0 )) if (( ARG_VERBOSE > 0 ))
then then
print - "$*" | while read LOG_LINE print - "$*" | while read -r LOG_LINE
do do
# check for leading log sigils and retain them # check for leading log sigils and retain them
case "${LOG_LINE}" in case "${LOG_LINE}" in
@ -1027,7 +1047,7 @@ if [[ -n "${LOG_STDIN}" ]]
then then
if (( ARG_LOG > 0 )) if (( ARG_LOG > 0 ))
then then
print - "${LOG_STDIN}" | while read LOG_LINE print - "${LOG_STDIN}" | while read -r LOG_LINE
do do
# check for leading log sigils and retain them # check for leading log sigils and retain them
case "${LOG_LINE}" in case "${LOG_LINE}" in
@ -1047,12 +1067,12 @@ then
LOG_SIGIL="INFO" LOG_SIGIL="INFO"
;; ;;
esac esac
print "${NOW}: ${LOG_SIGIL}: [$$]:" "${LOG_LINE}" >>${LOG_FILE} print "${NOW}: ${LOG_SIGIL}: [$$]:" "${LOG_LINE}" >>"${LOG_FILE}"
done done
fi fi
if (( ARG_VERBOSE > 0 )) if (( ARG_VERBOSE > 0 ))
then then
print - "${LOG_STDIN}" | while read LOG_LINE print - "${LOG_STDIN}" | while read -r LOG_LINE
do do
# check for leading log sigils and retain them # check for leading log sigils and retain them
case "${LOG_LINE}" in case "${LOG_LINE}" in
@ -1072,7 +1092,7 @@ if [[ -n "$1" ]]
then then
if (( ARG_LOG > 0 )) if (( ARG_LOG > 0 ))
then then
print - "$*" | while read LOG_LINE print - "$*" | while read -r LOG_LINE
do do
# check for leading log sigils and retain them # check for leading log sigils and retain them
case "${LOG_LINE}" in case "${LOG_LINE}" in
@ -1092,12 +1112,12 @@ then
LOG_SIGIL="INFO" LOG_SIGIL="INFO"
;; ;;
esac esac
print "${NOW}: ${LOG_SIGIL}: [$$]:" "${LOG_LINE}" >>${LOG_FILE} print "${NOW}: ${LOG_SIGIL}: [$$]:" "${LOG_LINE}" >>"${LOG_FILE}"
done done
fi fi
if (( ARG_VERBOSE > 0 )) if (( ARG_VERBOSE > 0 ))
then then
print - "$*" | while read LOG_LINE print - "$*" | while read -r LOG_LINE
do do
case "${LOG_LINE}" in case "${LOG_LINE}" in
INFO:*|WARN:*|ERROR*) INFO:*|WARN:*|ERROR*)
@ -1138,7 +1158,7 @@ then
fi fi
# get aliases from alias line # get aliases from alias line
ALIASES_LINE=$(grep -E -e "^${NEEDLE}.*:" ${LOCAL_DIR}/alias 2>/dev/null | cut -f2 -d':' 2>/dev/null) ALIASES_LINE=$(grep -E -e "^${NEEDLE}.*:" "${LOCAL_DIR}/alias" 2>/dev/null | cut -f2 -d':' 2>/dev/null)
if [[ -z "${ALIASES_LINE}" ]] if [[ -z "${ALIASES_LINE}" ]]
then then
@ -1159,6 +1179,7 @@ do
RECURSION_COUNT=$(( RECURSION_COUNT + 1 )) RECURSION_COUNT=$(( RECURSION_COUNT + 1 ))
EXPANDED_ALIASES=$(resolve_alias "${ALIAS}" ${RECURSION_COUNT}) EXPANDED_ALIASES=$(resolve_alias "${ALIAS}" ${RECURSION_COUNT})
RECURSION_COUNT=$(( RECURSION_COUNT - 1 )) RECURSION_COUNT=$(( RECURSION_COUNT - 1 ))
# shellcheck disable=SC2181
if (( $? == 0 )) if (( $? == 0 ))
then then
if [[ -z "${ALIAS_LIST}" ]] if [[ -z "${ALIAS_LIST}" ]]
@ -1217,6 +1238,7 @@ do
if (( IS_TARGET > 0 )) if (( IS_TARGET > 0 ))
then then
EXPANDED_TARGETS=$(resolve_alias "${TARGET}" 0) EXPANDED_TARGETS=$(resolve_alias "${TARGET}" 0)
# shellcheck disable=SC2181
if (( $? == 0 )) if (( $? == 0 ))
then then
if [[ -z "${TARGETS_LIST}" ]] if [[ -z "${TARGETS_LIST}" ]]
@ -1240,6 +1262,7 @@ done
# sort final output and hand it back to the caller # sort final output and hand it back to the caller
print "${TARGETS_LIST}" | grep -v '^$' 2>/dev/null | sort -u 2>/dev/null print "${TARGETS_LIST}" | grep -v '^$' 2>/dev/null | sort -u 2>/dev/null
# shellcheck disable=SC2086
return $0 return $0
} }
@ -1264,26 +1287,26 @@ TRANSFER_FILE="${TRANSFER_FILE%!*}"
SOURCE_FILE="${TRANSFER_FILE##*/}" SOURCE_FILE="${TRANSFER_FILE##*/}"
# shellcheck disable=SC2164 # shellcheck disable=SC2164
OLD_PWD=$(pwd) OLD_PWD=$(pwd)
cd ${TRANSFER_DIR} || return 1 cd "${TRANSFER_DIR}" || return 1
# transfer, (possibly) chmod the file to/on the target server (keep STDERR) # transfer, (possibly) chmod the file to/on the target server (keep STDERR)
if (( DO_SFTP_CHMOD > 1 )) if (( DO_SFTP_CHMOD > 1 ))
then then
sftp ${SFTP_ARGS} ${SSH_TRANSFER_USER}@${TRANSFER_HOST} >/dev/null <<EOT sftp ${SFTP_ARGS} "${SSH_TRANSFER_USER}@${TRANSFER_HOST}" >/dev/null <<EOT
cd ${REMOTE_DIR} cd ${REMOTE_DIR}
put ${SOURCE_FILE} put ${SOURCE_FILE}
chmod ${TRANSFER_PERMS} ${SOURCE_FILE} chmod ${TRANSFER_PERMS} ${SOURCE_FILE}
EOT EOT
SFTP_RC=$? SFTP_RC=$?
else else
sftp ${SFTP_ARGS} ${SSH_TRANSFER_USER}@${TRANSFER_HOST} >/dev/null <<EOT sftp ${SFTP_ARGS} "${SSH_TRANSFER_USER}@${TRANSFER_HOST}" >/dev/null <<EOT
cd ${REMOTE_DIR} cd ${REMOTE_DIR}
put ${SOURCE_FILE} put ${SOURCE_FILE}
EOT EOT
SFTP_RC=$? SFTP_RC=$?
fi fi
cd ${OLD_PWD} || return 1 cd "${OLD_PWD}" || return 1
return ${SFTP_RC} return ${SFTP_RC}
} }
@ -1310,6 +1333,7 @@ else
return 1 return 1
else else
log "SSH agent started on ${HOST_NAME}:" log "SSH agent started on ${HOST_NAME}:"
# shellcheck disable=SC2086
log "$(ps -fp ${SSH_AGENT_PID})" log "$(ps -fp ${SSH_AGENT_PID})"
fi fi
fi fi
@ -1338,7 +1362,9 @@ if [[ -n "${SSH_AGENT_PID}" ]]
then then
# SIGTERM # SIGTERM
log "stopping (TERM) process on ${HOST_NAME} with PID: ${SSH_AGENT_PID}" log "stopping (TERM) process on ${HOST_NAME} with PID: ${SSH_AGENT_PID}"
# shellcheck disable=SC2086
log "$(ps -fp ${SSH_AGENT_PID})" log "$(ps -fp ${SSH_AGENT_PID})"
# shellcheck disable=SC2086
kill -s TERM ${SSH_AGENT_PID} kill -s TERM ${SSH_AGENT_PID}
sleep 3 sleep 3
@ -1346,7 +1372,9 @@ then
if (( $(pgrep -u "${USER}" ssh-agent | grep -c "${SSH_AGENT_PID}") )) if (( $(pgrep -u "${USER}" ssh-agent | grep -c "${SSH_AGENT_PID}") ))
then then
log "stopping (KILL) process on ${HOST_NAME} with PID: ${SSH_AGENT_PID}" log "stopping (KILL) process on ${HOST_NAME} with PID: ${SSH_AGENT_PID}"
# shellcheck disable=SC2086
log "$(ps -fp ${SSH_AGENT_PID})" log "$(ps -fp ${SSH_AGENT_PID})"
# shellcheck disable=SC2086
kill -s kill ${SSH_AGENT_PID} kill -s kill ${SSH_AGENT_PID}
fi fi
sleep 3 sleep 3
@ -1375,8 +1403,9 @@ typeset UPDATE_OPTS=""
typeset RC=0 typeset RC=0
# convert line to hostname # convert line to hostname
SERVER=${SERVER%%;*} SERVER="${SERVER%%;*}"
resolve_host ${SERVER} resolve_host "${SERVER}"
# shellcheck disable=SC2181
if (( $? > 0 )) if (( $? > 0 ))
then then
warn "could not lookup host ${SERVER}, skipping" warn "could not lookup host ${SERVER}, skipping"
@ -1392,25 +1421,29 @@ log "setting SSH controls on ${SERVER} ..."
if [[ -z "${SSH_UPDATE_USER}" ]] if [[ -z "${SSH_UPDATE_USER}" ]]
then then
# own user w/ sudo # own user w/ sudo
( RC=0; ssh ${SSH_ARGS} ${SERVER} sudo -n ${REMOTE_DIR}/${SCRIPT_NAME} --update ${UPDATE_OPTS}; # shellcheck disable=SC2029
print "$?" > ${TMP_RC_FILE}; exit ( RC=0; ssh ${SSH_ARGS} "${SERVER}" "sudo -n ${REMOTE_DIR}/${SCRIPT_NAME} --update ${UPDATE_OPTS}";
) 2>&1 | logc print "$?" > "${TMP_RC_FILE}"; exit
) 2>&1 | logc ""
elif [[ "${SSH_UPDATE_USER}" != "root" ]] elif [[ "${SSH_UPDATE_USER}" != "root" ]]
then then
# other user w/ sudo # other user w/ sudo
( RC=0; ssh ${SSH_ARGS} ${SSH_UPDATE_USER}@${SERVER} sudo -n ${REMOTE_DIR}/${SCRIPT_NAME} --update ${UPDATE_OPTS}; # shellcheck disable=SC2029
print "$?" > ${TMP_RC_FILE}; exit ( RC=0; ssh ${SSH_ARGS} "${SSH_UPDATE_USER}@${SERVER}" "sudo -n ${REMOTE_DIR}/${SCRIPT_NAME} --update ${UPDATE_OPTS}";
) 2>&1 | logc print "$?" > "${TMP_RC_FILE}"; exit
) 2>&1 | logc ""
else else
# root user w/o sudo # root user w/o sudo
( RC=0; ssh ${SSH_ARGS} root@${SERVER} ${REMOTE_DIR}/${SCRIPT_NAME} --update ${UPDATE_OPTS}; # shellcheck disable=SC2029
print "$?" > ${TMP_RC_FILE}; exit ( RC=0; ssh ${SSH_ARGS} "root@${SERVER}" "${REMOTE_DIR}/${SCRIPT_NAME} --update ${UPDATE_OPTS}";
) 2>&1 | logc print "$?" > "${TMP_RC_FILE}"; exit
) 2>&1 | logc ""
fi fi
# fetch return code from subshell # fetch return code from subshell
RC="$(< ${TMP_RC_FILE})" RC=$(< "${TMP_RC_FILE}")
# shellcheck disable=SC2086
return ${RC} return ${RC}
} }
@ -1424,8 +1457,9 @@ typeset UPDATE_OPTS=""
typeset RC=0 typeset RC=0
# convert line to hostname # convert line to hostname
SERVER=${SERVER%%;*} SERVER="${SERVER%%;*}"
resolve_host ${SERVER} resolve_host "${SERVER}"
# shellcheck disable=SC2181
if (( $? > 0 )) if (( $? > 0 ))
then then
warn "could not lookup host ${SERVER}, skipping" warn "could not lookup host ${SERVER}, skipping"
@ -1438,13 +1472,15 @@ then
fi fi
log "applying SSH controls on ${SERVER} in slave mode, this may take a while ..." log "applying SSH controls on ${SERVER} in slave mode, this may take a while ..."
( RC=0; ssh -A ${SSH_ARGS} ${SERVER} ${REMOTE_DIR}/${SCRIPT_NAME} --apply ${UPDATE_OPTS}; # shellcheck disable=SC2029
print "$?" > ${TMP_RC_FILE}; exit ( RC=0; ssh -A ${SSH_ARGS} "${SERVER}" "${REMOTE_DIR}/${SCRIPT_NAME} --apply ${UPDATE_OPTS}";
) 2>&1 | logc print "$?" > "${TMP_RC_FILE}"; exit
) 2>&1 | logc ""
# fetch return code from subshell # fetch return code from subshell
RC="$(< ${TMP_RC_FILE})" RC=$(< "${TMP_RC_FILE}")
# shellcheck disable=SC2086
return ${RC} return ${RC}
} }
@ -1467,18 +1503,20 @@ FINGER_FIELDS=$(count_fields "${FINGER_LINE}" ",")
(( FINGER_FIELDS != 3 )) && die "line '${FINGER_LINE}' has missing or too many field(s) (should be 3))" (( FINGER_FIELDS != 3 )) && die "line '${FINGER_LINE}' has missing or too many field(s) (should be 3))"
# create fingerprint # create fingerprint
FINGER_USER="$(print ${FINGER_LINE} | awk -F, '{print $1}')" FINGER_USER=$(print "${FINGER_LINE}" | awk -F, '{print $1}')
print "${FINGER_LINE}" | awk -F, '{print $2 " " $3}' > ${TMP_FILE} print "${FINGER_LINE}" | awk -F, '{print $2 " " $3}' > "${TMP_FILE}"
# check if fingerprint is valid # check if fingerprint is valid
FINGERPRINT="$(ssh-keygen ${SSH_KEYGEN_OPTS} -l -f ${TMP_FILE} 2>&1)" FINGERPRINT=$(ssh-keygen ${SSH_KEYGEN_OPTS} -l -f "${TMP_FILE}" 2>&1)
FINGER_RC=$? FINGER_RC=$?
if (( FINGER_RC == 0 )) if (( FINGER_RC == 0 ))
then then
case "${OS_NAME}" in case "${OS_NAME}" in
HP-UX) HP-UX)
# shellcheck disable=SC2086
FINGER_ENTRY="$(print ${FINGERPRINT} | awk '{print $1,$2,$4}')" FINGER_ENTRY="$(print ${FINGERPRINT} | awk '{print $1,$2,$4}')"
;; ;;
*) *)
# shellcheck disable=SC2086
FINGER_ENTRY="$(print ${FINGERPRINT} | awk '{print $1,$2,$5}')" FINGER_ENTRY="$(print ${FINGERPRINT} | awk '{print $1,$2,$5}')"
;; ;;
esac esac
@ -1523,6 +1561,7 @@ do
do do
shift shift
# child is still alive? # child is still alive?
# shellcheck disable=SC2086
if kill -0 ${PID} 2>/dev/null if kill -0 ${PID} 2>/dev/null
then then
(( ARG_DEBUG > 0 )) && print -u2 "DEBUG: ${PID} is still alive" (( ARG_DEBUG > 0 )) && print -u2 "DEBUG: ${PID} is still alive"
@ -1530,6 +1569,7 @@ do
# wait for sigchild, catching child exit codes is unreliable because # wait for sigchild, catching child exit codes is unreliable because
# the child might have already ended before we get here (caveat emptor) # the child might have already ended before we get here (caveat emptor)
else else
# shellcheck disable=SC2086
wait ${PID} wait ${PID}
RC=$? RC=$?
if (( RC > 0 )) if (( RC > 0 ))
@ -1561,7 +1601,7 @@ if [[ -n "$1" ]]
then then
if (( ARG_LOG > 0 )) if (( ARG_LOG > 0 ))
then then
print - "$*" | while read LOG_LINE print - "$*" | while read -r LOG_LINE
do do
# check for leading log sigils and retain them # check for leading log sigils and retain them
case "${LOG_LINE}" in case "${LOG_LINE}" in
@ -1581,12 +1621,12 @@ then
LOG_SIGIL="WARN" LOG_SIGIL="WARN"
;; ;;
esac esac
print "${NOW}: ${LOG_SIGIL}: [$$]:" "${LOG_LINE}" >>${LOG_FILE} print "${NOW}: ${LOG_SIGIL}: [$$]:" "${LOG_LINE}" >>"${LOG_FILE}"
done done
fi fi
if (( ARG_VERBOSE > 0 )) if (( ARG_VERBOSE > 0 ))
then then
print - "$*" | while read LOG_LINE print - "$*" | while read -r LOG_LINE
do do
# check for leading log sigils and retain them # check for leading log sigils and retain them
case "${LOG_LINE}" in case "${LOG_LINE}" in
@ -1613,7 +1653,7 @@ return 0
CMD_LINE="$*" CMD_LINE="$*"
for PARAMETER in ${CMD_LINE} for PARAMETER in ${CMD_LINE}
do do
case ${PARAMETER} in case "${PARAMETER}" in
-a|-apply|--apply) -a|-apply|--apply)
(( ARG_ACTION > 0 )) && { (( ARG_ACTION > 0 )) && {
print -u2 "ERROR: multiple actions specified" print -u2 "ERROR: multiple actions specified"
@ -1821,6 +1861,7 @@ case ${ARG_ACTION} in
then then
die "no targets to process" die "no targets to process"
else else
# shellcheck disable=SC2086
log "processing targets: $(print ${CLIENTS} | tr -s '\n' ' ' 2>/dev/null)" log "processing targets: $(print ${CLIENTS} | tr -s '\n' ' ' 2>/dev/null)"
fi fi
@ -1828,6 +1869,7 @@ case ${ARG_ACTION} in
if (( DO_SSH_AGENT > 0 && CAN_START_AGENT > 0 )) if (( DO_SSH_AGENT > 0 && CAN_START_AGENT > 0 ))
then then
start_ssh_agent start_ssh_agent
# shellcheck disable=SC2181
if (( $? > 0 )) if (( $? > 0 ))
then then
die "problem with launching an SSH agent, bailing out" die "problem with launching an SSH agent, bailing out"
@ -1840,9 +1882,9 @@ case ${ARG_ACTION} in
do do
if (( DO_SLAVE > 0 )) if (( DO_SLAVE > 0 ))
then then
update2slave ${CLIENT} & update2slave "${CLIENT}" &
else else
update2host ${CLIENT} & update2host "${CLIENT}" &
fi fi
PID=$! PID=$!
log "updating ${CLIENT} in background [PID=${PID}] ..." log "updating ${CLIENT} in background [PID=${PID}] ..."
@ -1852,6 +1894,7 @@ case ${ARG_ACTION} in
if (( COUNT <= 0 )) if (( COUNT <= 0 ))
then then
# wait until all background processes are completed # wait until all background processes are completed
# shellcheck disable=SC2086
wait_for_children ${PIDS} || \ wait_for_children ${PIDS} || \
warn "$? background jobs (possibly) failed to complete correctly" warn "$? background jobs (possibly) failed to complete correctly"
PIDS='' PIDS=''
@ -1881,6 +1924,7 @@ case ${ARG_ACTION} in
then then
die "no targets to process" die "no targets to process"
else else
# shellcheck disable=SC2086
log "processing targets: $(print ${CLIENTS} | tr -s '\n' ' ' 2>/dev/null)" log "processing targets: $(print ${CLIENTS} | tr -s '\n' ' ' 2>/dev/null)"
fi fi
@ -1888,6 +1932,7 @@ case ${ARG_ACTION} in
if (( DO_SSH_AGENT > 0 && CAN_START_AGENT > 0 )) if (( DO_SSH_AGENT > 0 && CAN_START_AGENT > 0 ))
then then
start_ssh_agent start_ssh_agent
# shellcheck disable=SC2181
if (( $? > 0 )) if (( $? > 0 ))
then then
die "problem with launching an SSH agent, bailing out" die "problem with launching an SSH agent, bailing out"
@ -1900,9 +1945,9 @@ case ${ARG_ACTION} in
do do
if (( DO_SLAVE )) if (( DO_SLAVE ))
then then
distribute2slave ${CLIENT} & distribute2slave "${CLIENT}" &
else else
distribute2host ${CLIENT} & distribute2host "${CLIENT}" &
fi fi
PID=$! PID=$!
log "copying/distributing to ${CLIENT} in background [PID=${PID}] ..." log "copying/distributing to ${CLIENT} in background [PID=${PID}] ..."
@ -1912,6 +1957,7 @@ case ${ARG_ACTION} in
if (( COUNT <= 0 )) if (( COUNT <= 0 ))
then then
# wait until all background processes are completed # wait until all background processes are completed
# shellcheck disable=SC2086
wait_for_children ${PIDS} || \ wait_for_children ${PIDS} || \
warn "$? background jobs (possibly) failed to complete correctly" warn "$? background jobs (possibly) failed to complete correctly"
PIDS='' PIDS=''
@ -1964,16 +2010,16 @@ case ${ARG_ACTION} in
# are keys stored in a file or a directory? # are keys stored in a file or a directory?
if [[ -n "${KEYS_DIR}" ]] if [[ -n "${KEYS_DIR}" ]]
then then
cat ${KEYS_DIR}/* | sort | while read LINE cat "${KEYS_DIR}"/* | sort | while read -r LINE
do do
update_fingerprints "${LINE}" update_fingerprints "${LINE}"
KEY_COUNT=$(( KEY_COUNT + 1 )) KEY_COUNT=$(( KEY_COUNT + 1 ))
done done
else else
while read LINE while read -r LINE
do do
update_fingerprints "${LINE}" update_fingerprints "${LINE}"
done < ${KEYS_FILE} done < "${KEYS_FILE}"
fi fi
log "${KEY_COUNT} public keys discovered with following bits distribution:" log "${KEY_COUNT} public keys discovered with following bits distribution:"
log " 1024 bits: ${KEY_1024_COUNT}" log " 1024 bits: ${KEY_1024_COUNT}"
@ -1984,11 +2030,11 @@ case ${ARG_ACTION} in
;; ;;
4) # apply SSH controls locally (root user) 4) # apply SSH controls locally (root user)
log "ACTION: apply SSH controls locally" log "ACTION: apply SSH controls locally"
( RC=0; ${LOCAL_DIR}/update_ssh.pl ${SSH_UPDATE_OPTS}; ( RC=0; "${LOCAL_DIR}/update_ssh.pl" ${SSH_UPDATE_OPTS};
print "$?" > ${TMP_RC_FILE}; exit print "$?" > "${TMP_RC_FILE}"; exit
) 2>&1 | logc ) 2>&1 | logc ""
# fetch return code from subshell # fetch return code from subshell
RC="$(< ${TMP_RC_FILE})" RC=$(< "${TMP_RC_FILE}")
if (( RC > 0 )) if (( RC > 0 ))
then then
die "failed to apply SSH controls locally [RC=${RC}]" die "failed to apply SSH controls locally [RC=${RC}]"
@ -2000,7 +2046,7 @@ case ${ARG_ACTION} in
log "ACTION: fix local SSH controls repository" log "ACTION: fix local SSH controls repository"
check_root_user || die "must be run as user 'root'" check_root_user || die "must be run as user 'root'"
log "resetting ownerships to UNIX user ${SUDO_FIX_USER}" log "resetting ownerships to UNIX user ${SUDO_FIX_USER}"
if [[ ${SSH_FIX_USER} = "root" ]] if [[ "${SSH_FIX_USER}" = "root" ]]
then then
warn "!!! resetting ownerships to user root !!!" warn "!!! resetting ownerships to user root !!!"
fi fi
@ -2072,7 +2118,7 @@ case ${ARG_ACTION} in
5) 5)
chcon -R -t sshd_key_t "${FIX_DIR}/keys.d" chcon -R -t sshd_key_t "${FIX_DIR}/keys.d"
;; ;;
6|7) 6|7|8)
chcon -R -t ssh_home_t "${FIX_DIR}/keys.d" chcon -R -t ssh_home_t "${FIX_DIR}/keys.d"
;; ;;
*) *)
@ -2116,6 +2162,7 @@ case ${ARG_ACTION} in
then then
die "no targets to process" die "no targets to process"
else else
# shellcheck disable=SC2086
log "processing targets: $(print ${CLIENTS} | tr -s '\n' ' ' 2>/dev/null)" log "processing targets: $(print ${CLIENTS} | tr -s '\n' ' ' 2>/dev/null)"
fi fi
@ -2123,6 +2170,7 @@ case ${ARG_ACTION} in
if (( DO_SSH_AGENT > 0 && CAN_START_AGENT > 0 )) if (( DO_SSH_AGENT > 0 && CAN_START_AGENT > 0 ))
then then
start_ssh_agent start_ssh_agent
# shellcheck disable=SC2181
if (( $? > 0 )) if (( $? > 0 ))
then then
die "problem with launching an SSH agent, bailing out" die "problem with launching an SSH agent, bailing out"
@ -2135,9 +2183,9 @@ case ${ARG_ACTION} in
do do
if (( DO_SLAVE > 0 )) if (( DO_SLAVE > 0 ))
then then
fix2slave ${CLIENT} "${FIX_DIR}" "${SSH_UPDATE_USER}" & fix2slave "${CLIENT}" "${FIX_DIR}" "${SSH_UPDATE_USER}" &
else else
fix2host ${CLIENT} "${FIX_DIR}" "${SSH_UPDATE_USER}" & fix2host "${CLIENT}" "${FIX_DIR}" "${SSH_UPDATE_USER}" &
fi fi
PID=$! PID=$!
log "fixing SSH controls on ${CLIENT} in background [PID=${PID}] ..." log "fixing SSH controls on ${CLIENT} in background [PID=${PID}] ..."
@ -2147,6 +2195,7 @@ case ${ARG_ACTION} in
if (( COUNT <= 0 )) if (( COUNT <= 0 ))
then then
# wait until all background processes are completed # wait until all background processes are completed
# shellcheck disable=SC2086
wait_for_children ${PIDS} || \ wait_for_children ${PIDS} || \
warn "$? background jobs (possibly) failed to complete correctly" warn "$? background jobs (possibly) failed to complete correctly"
PIDS='' PIDS=''
@ -2164,7 +2213,7 @@ case ${ARG_ACTION} in
;; ;;
7) # dump the configuration namespace 7) # dump the configuration namespace
log "ACTION: dumping the global access namespace with resolved aliases ..." log "ACTION: dumping the global access namespace with resolved aliases ..."
${LOCAL_DIR}/update_ssh.pl --preview --global "${LOCAL_DIR}/update_ssh.pl" --preview --global
log "finished dumping the global namespace" log "finished dumping the global namespace"
;; ;;
8) # check syntax of the access/alias/keys files 8) # check syntax of the access/alias/keys files
@ -2185,16 +2234,21 @@ case ${ARG_ACTION} in
# keys files # keys files
if [[ -n "${KEYS_DIR}" ]] if [[ -n "${KEYS_DIR}" ]]
then then
# shellcheck disable=SC2086
log "$(tar -cvf ${BACKUP_TAR_FILE} ${KEYS_DIR} 2>/dev/null)" log "$(tar -cvf ${BACKUP_TAR_FILE} ${KEYS_DIR} 2>/dev/null)"
else else
# shellcheck disable=SC2086
log "$(tar -cvf ${BACKUP_TAR_FILE} ${KEYS_FILE} 2>/dev/null)" log "$(tar -cvf ${BACKUP_TAR_FILE} ${KEYS_FILE} 2>/dev/null)"
fi fi
# configuration files # configuration files
for FILE in "${LOCAL_DIR}/access" "${LOCAL_DIR}/alias ${LOCAL_DIR}/targets" for FILE in "${LOCAL_DIR}/access" "${LOCAL_DIR}/alias ${LOCAL_DIR}/targets"
do do
# shellcheck disable=SC2086
log "$(tar -rvf ${BACKUP_TAR_FILE} ${FILE} 2>/dev/null)" log "$(tar -rvf ${BACKUP_TAR_FILE} ${FILE} 2>/dev/null)"
done done
# shellcheck disable=SC2086
log "$(gzip ${BACKUP_TAR_FILE} 2>/dev/null)" log "$(gzip ${BACKUP_TAR_FILE} 2>/dev/null)"
# shellcheck disable=SC2086
log "resulting backup file is: $(ls -1 ${BACKUP_TAR_FILE}* 2>/dev/null)" log "resulting backup file is: $(ls -1 ${BACKUP_TAR_FILE}* 2>/dev/null)"
else else
die "could not find backup directory ${BACKUP_DIR}. Host is not an SSH master?" die "could not find backup directory ${BACKUP_DIR}. Host is not an SSH master?"
@ -2210,6 +2264,7 @@ case ${ARG_ACTION} in
then then
die "no targets to process" die "no targets to process"
else else
# shellcheck disable=SC2086
log "processing targets: $(print ${CLIENTS} | tr -s '\n' ' ' 2>/dev/null)" log "processing targets: $(print ${CLIENTS} | tr -s '\n' ' ' 2>/dev/null)"
fi fi
print "${CLIENTS}" | ${SSH_KEYSCAN_BIN} ${SSH_KEYSCAN_ARGS} -f - 2>/dev/null print "${CLIENTS}" | ${SSH_KEYSCAN_BIN} ${SSH_KEYSCAN_ARGS} -f - 2>/dev/null
@ -2219,10 +2274,12 @@ case ${ARG_ACTION} in
11) # resolve an alias 11) # resolve an alias
log "ACTION: resolving alias ${ARG_ALIAS} ..." log "ACTION: resolving alias ${ARG_ALIAS} ..."
RESOLVE_ALIAS=$(resolve_alias "${ARG_ALIAS}" 0) RESOLVE_ALIAS=$(resolve_alias "${ARG_ALIAS}" 0)
# shellcheck disable=SC2181
if (( $? > 0 )) && [[ -z "${RESOLVE_ALIAS}" ]] if (( $? > 0 )) && [[ -z "${RESOLVE_ALIAS}" ]]
then then
die "alias ${ARG_ALIAS} did not resolve correctly" die "alias ${ARG_ALIAS} did not resolve correctly"
else else
# shellcheck disable=SC2086
log "alias ${ARG_ALIAS} resolves to: $(print ${RESOLVE_ALIAS} | tr -s '\n' ' ' 2>/dev/null)" log "alias ${ARG_ALIAS} resolves to: $(print ${RESOLVE_ALIAS} | tr -s '\n' ' ' 2>/dev/null)"
fi fi
log "finished resolving alias" log "finished resolving alias"

View File

@ -15,6 +15,14 @@ use_fqdn=1
# target directory for allowed SSH key files # target directory for allowed SSH key files
access_dir=/etc/ssh_controls/keys.d access_dir=/etc/ssh_controls/keys.d
# toggle to specify the final location of public keys by allowing to override
# the value of $access_dir with the 'AuthorizedKeysFile' in sshd (=enables the
# use of $HOME/.ssh for public keys for example):
# 'use_controls': take the value from the configured 'access_dir' option
# 'use_sshd' : use the value from 'AuthorizedKeysFile' setting in sshd
# [default: use_controls]
key_location=use_controls
# location of the keys blacklist file # location of the keys blacklist file
blacklist_file=/etc/ssh_controls/keys.blacklisted blacklist_file=/etc/ssh_controls/keys.blacklisted

View File

@ -42,7 +42,7 @@ use Pod::Usage;
# ------------------------- CONFIGURATION starts here ------------------------- # ------------------------- CONFIGURATION starts here -------------------------
# define the version (YYYY-MM-DD) # define the version (YYYY-MM-DD)
my $script_version = "2018-11-03"; my $script_version = "2020-12-30";
# name of global configuration file (no path, must be located in the script directory) # name of global configuration file (no path, must be located in the script directory)
my $global_config_file = "update_ssh.conf"; my $global_config_file = "update_ssh.conf";
# name of localized configuration file (no path, must be located in the script directory) # name of localized configuration file (no path, must be located in the script directory)
@ -52,13 +52,18 @@ my $max_recursion = 5;
# selinux context labels of key files for different RHEL version # selinux context labels of key files for different RHEL version
my %selinux_contexts = ( '5' => 'sshd_key_t', my %selinux_contexts = ( '5' => 'sshd_key_t',
'6' => 'ssh_home_t', '6' => 'ssh_home_t',
'7' => 'ssh_home_t'); '7' => 'ssh_home_t',
'8' => 'ssh_home_t');
# disallowed paths for home directories for accounts
my @disallowed_homes = ('/', '/etc', '/bin', '/sbin', '/usr/bin', '/usr/sbin');
# disallowed login shells for @accounts
my @disallowed_shells = ('/bin/nologin','/bin/false','/sbin/nologin','/sbin/false');
# ------------------------- CONFIGURATION ends here --------------------------- # ------------------------- CONFIGURATION ends here ---------------------------
# initialize variables # initialize variables
my ($debug, $verbose, $preview, $remove, $global, $use_fqdn) = (0,0,0,0,0,0); my ($debug, $verbose, $preview, $remove, $global, $use_fqdn) = (0,0,0,0,0,0);
my (@config_files, @zombie_files, $access_dir, $blacklist_file); my (@config_files, @zombie_files, $access_dir, $key_location, $blacklist_file);
my (%options, @uname, @pwgetent, @accounts, %aliases, %keys, %access, @blacklist); my (%options, @uname, @pwgetent, @accounts, %aliases, %keys, %access, @blacklist);
my ($os, $hostname, $run_dir); my ($os, $hostname, $run_dir, $authorizedkeys_option);
my ($selinux_status, $selinux_context, $linux_version, $has_selinux, $recursion_count) = ("","","",0,1); my ($selinux_status, $selinux_context, $linux_version, $has_selinux, $recursion_count) = ("","","",0,1);
$|++; $|++;
@ -89,7 +94,7 @@ sub parse_config_file {
my $config_file = shift; my $config_file = shift;
unless (open (CONF_FD, "<", $config_file)) { unless (open (CONF_FD, "<", $config_file)) {
do_log ("ERROR: failed to open the configuration file ${config_file} [$! $hostname]") do_log ("ERROR: failed to open the configuration file ${config_file} [$!/$hostname]")
and exit (1); and exit (1);
} }
while (<CONF_FD>) { while (<CONF_FD>) {
@ -98,7 +103,7 @@ sub parse_config_file {
if (/^\s*$/ || /^#/) { if (/^\s*$/ || /^#/) {
next; next;
} else { } else {
if (/^\s*use_fqdn\s*=\s*([0-9]+)\s*$/) { if (/^\s*use_fqdn\s*=\s*(0|1)\s*$/) {
$use_fqdn = $1; $use_fqdn = $1;
do_log ("DEBUG: picking up setting: use_fqdn=${use_fqdn}"); do_log ("DEBUG: picking up setting: use_fqdn=${use_fqdn}");
} }
@ -106,6 +111,15 @@ sub parse_config_file {
$access_dir = $1; $access_dir = $1;
do_log ("DEBUG: picking up setting: access_dir=${access_dir}"); do_log ("DEBUG: picking up setting: access_dir=${access_dir}");
} }
if (/^\s*key_location\s*=\s*(use_controls|use_sshd)\s*/) {
$key_location = $1;
do_log ("DEBUG: picking up setting: key_location=${key_location}");
if ($key_location eq 'use_sshd') {
do_log ("DEBUG: applied setting: key_location=${key_location}");
} else {
do_log ("DEBUG: applied default setting: key_location=${key_location}");
}
}
if (/^\s*blacklist_file\s*=\s*([0-9A-Za-z_\-\.\/~]+)\s*$/) { if (/^\s*blacklist_file\s*=\s*([0-9A-Za-z_\-\.\/~]+)\s*$/) {
$blacklist_file = $1; $blacklist_file = $1;
# support tilde (~) expansion for ~root # support tilde (~) expansion for ~root
@ -151,10 +165,10 @@ sub set_file {
my ($file, $perm, $uid, $gid) = @_; my ($file, $perm, $uid, $gid) = @_;
chmod ($perm, "$file") chmod ($perm, "$file")
or do_log ("ERROR: cannot set permissions on $file [$! $hostname]") or do_log ("ERROR: cannot set permissions on $file [$!/$hostname]")
and exit (1); and exit (1);
chown ($uid, $gid, "$file") chown ($uid, $gid, "$file")
or do_log ("ERROR: cannot set ownerships on $file [$! $hostname]") or do_log ("ERROR: cannot set ownerships on $file [$!/$hostname]")
and exit (1); and exit (1);
return (1); return (1);
@ -228,7 +242,7 @@ $verbose = 1 if ($options{'verbose'});
# where am I? (1/2) # where am I? (1/2)
$0 =~ /^(.+[\\\/])[^\\\/]+[\\\/]*$/; $0 =~ /^(.+[\\\/])[^\\\/]+[\\\/]*$/;
my $run_dir = $1 || "."; $run_dir = $1 || ".";
$run_dir =~ s#/$##; # remove trailing slash $run_dir =~ s#/$##; # remove trailing slash
# don't do anything without configuration file(s) # don't do anything without configuration file(s)
@ -245,14 +259,19 @@ foreach my $config_file (@config_files) {
parse_config_file ($config_file); parse_config_file ($config_file);
} }
# is the target directory for keys present? (not for global preview) # is the target directory for keys present? (not for global preview and
unless ($preview and $global) { # not when $key_location is use_sshd)
do_log ("INFO: checking for SSH control mode ..."); unless (($preview and $global) or $key_location eq 'use_sshd') {
do_log ("INFO: checking for SSH controls mode ...");
if (-d $access_dir) { if (-d $access_dir) {
do_log ("INFO: host is under SSH control via $access_dir"); do_log ("INFO: host is under SSH controls via $access_dir");
} else { } else {
do_log ("ERROR: host is not under SSH keys only control [$hostname]") if ($key_location eq 'use_sshd') {
and exit (1); do_log ("INFO: skipped check since public key location is determined by sshd [$hostname]")
} else {
do_log ("ERROR: host is not under SSH keys only control [$hostname]")
and exit (1);
}
} }
} }
@ -261,7 +280,7 @@ unless ($preview and $global) {
do_log ("INFO: checking for keys blacklist file ..."); do_log ("INFO: checking for keys blacklist file ...");
if (-f $blacklist_file) { if (-f $blacklist_file) {
open (BLACKLIST, "<", $blacklist_file) or \ open (BLACKLIST, "<", $blacklist_file) or \
do_log ("ERROR: cannot read keys blacklist file [$! $hostname]") do_log ("ERROR: cannot read keys blacklist file [$!/$hostname]")
and exit (1); and exit (1);
@blacklist = <BLACKLIST>; @blacklist = <BLACKLIST>;
close (BLACKLIST); close (BLACKLIST);
@ -291,6 +310,28 @@ if ($use_fqdn) {
do_log ("INFO: runtime info: ".getpwuid ($<)."; ${hostname}\@${run_dir}; Perl v$]"); do_log ("INFO: runtime info: ".getpwuid ($<)."; ${hostname}\@${run_dir}; Perl v$]");
# -----------------------------------------------------------------------------
# resolve and check key location
# -----------------------------------------------------------------------------
if ($key_location eq 'use_sshd') {
# get sshd setting but only take 1st path into account
$authorizedkeys_option = qx#sshd -T | grep "authorizedkeysfile" 2>/dev/null | cut -f2 -d' '#;
chomp ($authorizedkeys_option);
if (defined ($authorizedkeys_option)) {
do_log ("INFO: AuthorizedkeysFile resolves to $authorizedkeys_option [$hostname]");
} else {
do_log ("ERROR: unable to get AuthorizedkeysFile value from sshd [$hostname]")
and exit (1);
}
} else {
# for SSH controls native logic we require an absolute path
if ($authorizedkeys_option =~ /^\//) {
do_log ("ERROR: option \$access_dir requires and absolute path [$hostname]")
and exit (1);
}
}
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# collect user accounts via getpwent() # collect user accounts via getpwent()
# result: @accounts # result: @accounts
@ -319,7 +360,7 @@ print Dumper (\@accounts) if $debug;
do_log ("INFO: reading 'alias' file ..."); do_log ("INFO: reading 'alias' file ...");
open (ALIASES, "<", "${run_dir}/alias") open (ALIASES, "<", "${run_dir}/alias")
or do_log ("ERROR: cannot read 'alias' file [$! $hostname]") and exit (1); or do_log ("ERROR: cannot read 'alias' file [$!/$hostname]") and exit (1);
while (<ALIASES>) { while (<ALIASES>) {
my ($key, $value, @values); my ($key, $value, @values);
@ -398,7 +439,7 @@ if (-d "${run_dir}/keys.d" && -f "${run_dir}/keys") {
if (-d "${run_dir}/keys.d") { if (-d "${run_dir}/keys.d") {
do_log ("INFO: local 'keys' are stored in a DIRECTORY on $hostname"); do_log ("INFO: local 'keys' are stored in a DIRECTORY on $hostname");
opendir (KEYS_DIR, "${run_dir}/keys.d") opendir (KEYS_DIR, "${run_dir}/keys.d")
or do_log ("ERROR: cannot open 'keys.d' directory [$! $hostname]") or do_log ("ERROR: cannot open 'keys.d' directory [$!/$hostname]")
and exit (1); and exit (1);
while (my $key_file = readdir (KEYS_DIR)) { while (my $key_file = readdir (KEYS_DIR)) {
next if ($key_file =~ /^\./); next if ($key_file =~ /^\./);
@ -416,7 +457,7 @@ if (-d "${run_dir}/keys.d") {
# process 'keys' files # process 'keys' files
foreach my $key_file (@key_files) { foreach my $key_file (@key_files) {
open (KEYS, "<", $key_file) open (KEYS, "<", $key_file)
or do_log ("ERROR: cannot read 'keys' file [$! $hostname]") and exit (1); or do_log ("ERROR: cannot read 'keys' file [$!/$hostname]") and exit (1);
do_log ("INFO: reading public keys from file: $key_file"); do_log ("INFO: reading public keys from file: $key_file");
while (<KEYS>) { while (<KEYS>) {
@ -454,7 +495,7 @@ print Dumper(\%keys) if $debug;
do_log ("INFO: reading 'access' file ..."); do_log ("INFO: reading 'access' file ...");
open (ACCESS, "<", "${run_dir}/access") open (ACCESS, "<", "${run_dir}/access")
or do_log ("ERROR: cannot read 'access' file [$! $hostname]") and exit (1); or do_log ("ERROR: cannot read 'access' file [$!/$hostname]") and exit (1);
while (<ACCESS>) { while (<ACCESS>) {
my ($who, $where, $what, @who, @where, @what); my ($who, $where, $what, @who, @where, @what);
@ -507,7 +548,7 @@ if ($preview && $global) {
do_log ("INFO: display GLOBAL configuration ...."); do_log ("INFO: display GLOBAL configuration ....");
open (ACCESS, "<", "${run_dir}/access") open (ACCESS, "<", "${run_dir}/access")
or do_log ("ERROR: cannot read 'access' file [$! $hostname]") and exit (1); or do_log ("ERROR: cannot read 'access' file [$!/$hostname]") and exit (1);
while (<ACCESS>) { while (<ACCESS>) {
my ($who, $where, $what, @who, @where, @what); my ($who, $where, $what, @who, @where, @what);
@ -542,7 +583,8 @@ if ($preview && $global) {
} }
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# distribute keys into authorized_keys files in $access_dir # distribute keys into authorized_keys files
# (defined by $key_location and/or $access_dir)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
do_log ("INFO: applying SSH access rules ...."); do_log ("INFO: applying SSH access rules ....");
@ -578,6 +620,10 @@ unless ($preview) {
$linux_version = 7; $linux_version = 7;
last SWITCH_RELEASE; last SWITCH_RELEASE;
}; };
$release_string =~ m/release 8/i && do {
$linux_version = 8;
last SWITCH_RELEASE;
};
} }
} }
# use fall back in case we cannot determine the version # use fall back in case we cannot determine the version
@ -599,17 +645,50 @@ unless ($preview) {
# only add authorized_keys for existing accounts, # only add authorized_keys for existing accounts,
# otherwise revoke access if needed # otherwise revoke access if needed
foreach my $account (sort (@accounts)) { SET_KEY: foreach my $account (sort (@accounts)) {
my $access_file = "$access_dir/$account"; my ($access_file, $authorizedkeys_file, $uid, $gid, $home_dir, $login_shell) = (undef, undef, undef, undef, undef, undef);
# set $access_file when using SSH controls logic
if ($key_location eq 'use_sshd' and defined ($authorizedkeys_option)) {
# use sshd logic (replacing %u,%h, %%)
$authorizedkeys_file = $authorizedkeys_option;
$authorizedkeys_file =~ s/%u/$account/g;
$authorizedkeys_file =~ s/%h/$hostname/g;
$authorizedkeys_file =~ s/%%/%/g;
# check relative path (assume $HOME needs to be added)
if ($authorizedkeys_file !~ /^\//) {
($uid, $gid, $home_dir, $login_shell) = (getpwnam($account))[2,3,7,8];
# do not accept invalid $HOME or shells
if (defined ($home_dir)) {
if (grep( /^$home_dir$/, @disallowed_homes) or grep( /^$login_shell/, @disallowed_shells)) {
do_log ("DEBUG: invalid HOME or SHELL for $account [$hostname]");
next SET_KEY;
} else {
$authorizedkeys_file = $home_dir."/".$authorizedkeys_file;
do_log ("DEBUG: adding $home_dir to public key path for $account [$hostname]");
}
} else {
do_log ("ERROR: unable to get HOME for $account [$hostname]");
next SET_KEY;
}
}
$access_file = $authorizedkeys_file;
} else {
# use native SSH controls logic
$access_file = "$access_dir/$account";
}
do_log ("DEBUG: public key location for $account resolves to $authorizedkeys_file [$hostname]");
# only add authorised_keys if there are access definitions # only add authorised_keys if there are access definitions
if ($access{$account}) { if ($access{$account}) {
unless ($preview) { unless ($preview) {
# do not create root or intermediate paths in $access_file;
# e.g. if $HOME/.ssh/authorized_keys is the public key path, then $HOME/.ssh must already exist
open (KEYFILE, "+>", $access_file) open (KEYFILE, "+>", $access_file)
or do_log ("ERROR: cannot open file for writing in $access_dir [$! $hostname]") or do_log ("ERROR: cannot open file for writing at $access_file [$!/$hostname]")
and exit (1); and next SET_KEY;
} }
foreach my $person (sort (@{$access{$account}})) { foreach my $person (sort (@{$access{$account}})) {
my $real_name = $person; my $real_name = $person;
@ -626,9 +705,13 @@ foreach my $account (sort (@accounts)) {
} }
close (KEYFILE) unless $preview; close (KEYFILE) unless $preview;
# set permissions to world readable and check for SELinux context # set ownerships/permissions on public key file and check for SELinux context
unless ($preview) { unless ($preview) {
set_file ($access_file, 0644, 0, 0); if ($key_location eq 'use_controls') {
set_file ($access_file, 0644, 0, 0);
} else {
set_file ($access_file, 0600, $uid, $gid);
}
# selinux labels # selinux labels
SWITCH: { SWITCH: {
$os eq "Linux" && do { $os eq "Linux" && do {
@ -645,7 +728,7 @@ foreach my $account (sort (@accounts)) {
if (-f $access_file) { if (-f $access_file) {
unless ($preview) { unless ($preview) {
unlink ($access_file) unlink ($access_file)
or do_log ("ERROR: cannot remove obsolete access file(s) [$! $hostname]") or do_log ("ERROR: cannot remove obsolete access file $access_file [$!/$hostname]")
and exit (1); and exit (1);
} else { } else {
do_log ("INFO: removing obsolete access $access_file on $hostname"); do_log ("INFO: removing obsolete access $access_file on $hostname");
@ -655,32 +738,35 @@ foreach my $account (sort (@accounts)) {
} }
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# alert on/remove extraneous authorized_keys files # alert on/remove extraneous authorized_keys files (SSH controls logic only)
# (access files for which no longer a valid UNIX account exists) # (access files for which no longer a valid UNIX account exists)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
do_log ("INFO: checking for extraneous access files ...."); if ($key_location eq 'use_controls') {
opendir (ACCESS_DIR, $access_dir) do_log ("INFO: checking for extraneous access files ....");
or do_log ("ERROR: cannot open directory $access_dir [$! $hostname]")
opendir (ACCESS_DIR, $access_dir)
or do_log ("ERROR: cannot open directory $access_dir [$!/$hostname]")
and exit (1); and exit (1);
while (my $access_file = readdir (ACCESS_DIR)) { while (my $access_file = readdir (ACCESS_DIR)) {
next if ($access_file =~ /^\./); next if ($access_file =~ /^\./);
unless (grep (/$access_file/, @accounts)) { unless (grep (/$access_file/, @accounts)) {
do_log ("WARN: found extraneous access file in $access_dir/$access_file [$hostname]"); do_log ("WARN: found extraneous access file in $access_dir/$access_file [$hostname]");
push (@zombie_files, "$access_dir/$access_file"); push (@zombie_files, "$access_dir/$access_file");
}
} }
} closedir (ACCESS_DIR);
closedir (ACCESS_DIR); do_log ("INFO: ".scalar (@zombie_files)." extraneous access file(s) found on $hostname");
do_log ("INFO: ".scalar (@zombie_files)." extraneous access file(s) found on $hostname"); print Dumper (\@zombie_files) if $debug;
print Dumper (\@zombie_files) if $debug;
# remove if requested and needed # remove if requested and needed
if ($remove && @zombie_files) { if ($remove && @zombie_files) {
my $count = unlink (@zombie_files) my $count = unlink (@zombie_files)
or do_log ("ERROR: cannot remove extraneous access file(s) [$! $hostname]") or do_log ("ERROR: cannot remove extraneous access file(s) [$!/$hostname]")
and exit (1); and exit (1);
do_log ("INFO: $count extraneous access files removed $hostname"); do_log ("INFO: $count extraneous access files removed $hostname");
}
} }
exit (0); exit (0);
@ -711,9 +797,11 @@ update_ssh.pl - distributes SSH public keys in a desired state model.
=head1 DESCRIPTION =head1 DESCRIPTION
B<update_ssh.pl> distributes SSH keys to the appropriate files (.e. 'authorized_keys') into the C<$access_dir> repository based on the F<access>, F<alias> and F<keys> files. B<update_ssh.pl> distributes SSH keys to the appropriate files (.e. 'authorized_keys') into the C<$access_dir> repository based on the F<access>, F<alias> and F<keys> files.
Alternatively B<update_ssh.pl> can distribute public keys to the location specified in the AuthorizedkeysFile setting of F<sshd_config> (allowing public keys to be distributed
to the traditional location in a user's HOME directory). See C<key_location> setting in F<update_ssh.conf[.local]>for more information.
This script should be run on each host where SSH key authentication is the exclusive method of (remote) authentication. This script should be run on each host where SSH key authentication is the exclusive method of (remote) authentication.
For update SSH public keys must be stored in a generic F<keys> file within the same directory as B<update_ssh.pl> script. Orginally SSH public keys must be stored in a generic F<keys> file within the same directory as B<update_ssh.pl> script.
Alternatively key files may be stored as set of individual key files within a called sub-directory called F<keys.d>. Alternatively key files may be stored as set of individual key files within a called sub-directory called F<keys.d>.
Both methods are mutually exclusive and the latter always take precedence. Both methods are mutually exclusive and the latter always take precedence.
@ -739,6 +827,8 @@ Following settings must be configured:
=item * B<access_dir> : target directory for allowed SSH public key files =item * B<access_dir> : target directory for allowed SSH public key files
=item * B<key_location> : whether or not to use AuthorizedkeysFile setting in sshd_config for overriding $access_dir
=item * B<blacklist_file> : location of the file with blacklisted SSH public keys =item * B<blacklist_file> : location of the file with blacklisted SSH public keys
=back =back
@ -746,7 +836,7 @@ Following settings must be configured:
=head1 BLACKLISTING =head1 BLACKLISTING
Key blacklisting can be performed by adding a public key definition in its entirety to the blacklist keys file. When a blacklisted key is Key blacklisting can be performed by adding a public key definition in its entirety to the blacklist keys file. When a blacklisted key is
found in the available F<keys> file(s) during SSH control updates, an alert will be shown on STDOUT and the key will be ignored for the rest. found in the available F<keys> file(s) during SSH controls updates, an alert will be shown on STDOUT and the key will be ignored for the rest.
Examples: Examples: