#!/usr/bin/env bash # Rotate and deploy admin keys # List of servers with info # ssh-keygen -t ed25519 -N "" -C "" -f # store old key names, current key names # base folder for all data BASE_FOLDER=$(dirname "$(readlink -f "$0")")"/"; # config folder CONFIG_BASE="${BASE_FOLDER}../config/"; # timestamps of last rotate per user/host LAST_ROTATE="${BASE_FOLDER}../last-rotate/"; # ssh-keys temp holder SSH_PRIVATE_KEYS="${BASE_FOLDER}../ssh-keys/"; # ssh public keys from current and last SSH_PUBLIC_KEYS_PREVIOUS="${BASE_FOLDER}../ssh-public-keys/previous/"; SSH_PUBLIC_KEYS_CURRENT="${BASE_FOLDER}../ssh-public-keys/current/"; # PEM folder for server PEM_SERVER="${HOME}/.ssh/PEMS/SERVER-ADMIN/"; # PEM archive folder PEM_ARCHIVE="${HOME}/.ssh/ARCHIVED_PEMs/SERVER-ADMIN/$(date +%F)/"; DRYRUN=0; FORCE=0; HOST_ONLY=""; USER_ONLY=""; # while getopts ":h:u:n" opt; do case "${opt}" in h|hostname) HOST_ONLY="${OPTARG}"; ;; u|username) USER_ONLY="${OPTARG}"; ;; n|dryrun) DRYRUN=1; ;; f|force) FORCE=1; ;; \?) echo -e "\n Option does not exist: ${OPTARG}\n"; echo "-h override single host name"; echo "-n dry run"; echo "" exit 1; ;; esac; done; if [ ! -d "${SSH_PRIVATE_KEYS}" ]; then echo "Missing ssh private keys folder: ${SSH_PRIVATE_KEYS}"; exit; fi if [ ! -d "${SSH_PUBLIC_KEYS_CURRENT}" ]; then echo "Missing ssh public keys current folder: ${SSH_PUBLIC_KEYS_CURRENT}"; exit; fi if [ ! -d "${SSH_PUBLIC_KEYS_PREVIOUS}" ]; then echo "Missing ssh public keys previous folder: ${SSH_PUBLIC_KEYS_PREVIOUS}"; exit; fi # load config if [ -f "${CONFIG_BASE}settings.ini" ]; then source <(grep = "${CONFIG_BASE}settings.ini" | sed 's/ *= */=/g') fi if [ -z "${key_age}" ]; then echo "A minimnum key age in days must be set"; exit; fi # we must have "server_list" set and file must be in config folder if [ ! -f "${CONFIG_BASE}${server_list}" ]; then echo "Cannot find ${server_list} file in the config folder"; exit; fi # default ssh command SSH="ssh -a -x"; add_ssh_key() { AUTH_KEY_FILE="${1}"; PUB_KEY_DATA="${2}"; RMV_CHATTR_I="chattr -i " ADD_CHATTR_I="chattr +i " RMV_CHMOD_UW="chamod u-w " ADD_CHMOD_UW="chmod u+w " # { [ -z \`tail -1c ${AUTH_KEY_FILE} 2>/dev/null\` ] || # echo >> "${AUTH_KEY_FILE}" || exit 1; } && # cat >> "${AUTH_KEY_FILE}" || exit 1; # check if same key already exists, if yes, skip # the -z `tail ...` checks for a trailing newline. The echo adds one if was missing (from ssh-copy-id) # if [ -f "${AUTH_KEY_FILE}" ] && ! cat >> grep "${AUTH_KEY_FILE}"; then # ${RMV_CHATTR_I} ${AUTH_KEY_FILE} # ${ADD_CHMOD_UW} ${AUTH_KEY_FILE} # { [ -z \`tail -1c ${AUTH_KEY_FILE} 2>/dev/null\` ] || # echo >> "${AUTH_KEY_FILE}" || exit 1; } && # cat >> "${AUTH_KEY_FILE}" # ${RMV_CHMOD_UW} ${AUTH_KEY_FILE} # ${ADD_CHATTR_I} ${AUTH_KEY_FILE} # fi; INSTALLKEYS_SH=$(tr '\t\n' ' ' <<-EOF if [ -f "~/TEMP_SSH_PUB.pem.pub" ] && ! cat >> grep "~/TEMP_SSH_PUB.pem.pub"; then ${RMV_CHATTR_I} ~/TEMP_SSH_PUB.pem.pub ${ADD_CHMOD_UW} ~/TEMP_SSH_PUB.pem.pub { [ -z \`tail -1c ~/TEMP_SSH_PUB.pem.pub 2>/dev/null\` ] || echo >> "~/TEMP_SSH_PUB.pem.pub || exit 1; } && cat >> "~/TEMP_SSH_PUB.pem.pub" ${RMV_CHMOD_UW} ~/TEMP_SSH_PUB.pem.pub ${ADD_CHATTR_I} ~/TEMP_SSH_PUB.pem.pub fi; EOF ) # to defend against quirky remote shells: use 'exec sh -c' to get POSIX; printf "exec sh -c '%s'" "${INSTALLKEYS_SH}" } install_ssh_key() { HOSTNAME="${1}"; USERNAME="${2}"; PUB_KEY="${3}"; AUTH_KEY_FILE="${4}"; # PUB_KEY_DATA=$(printf '%s\n' "$(cat "${PUB_KEY}")") echo "=== FILE ${AUTH_KEY_FILE}"; printf '%s\n' "$(cat "${PUB_KEY}")" | ${SSH} "${USERNAME}"@"${HOSTNAME}" "$(add_ssh_key "${AUTH_KEY_FILE}" "${PUB_KEY_DATA}")" } # Remove all last entries # Move all current to last if [ ${DRYRUN} -eq 0 ]; then echo "Remove all previous ssh public keys"; rm "${SSH_PUBLIC_KEYS_PREVIOUS}"*; echo "Move all current public keys to the previous folder"; mv "${SSH_PUBLIC_KEYS_CURRENT}"* "${SSH_PUBLIC_KEYS_PREVIOUS}"; # create new archive folder local mkdir -p "${PEM_ARCHIVE}"; fi for line in `cat "${CONFIG_BASE}${server_list}" | sed 1d`; do if [[ "${i}" =~ ^\# ]]; then continue; fi # hostname is on pos 1 hostname=$(echo "${line}" | cut -d "," -f 1); # if hostname opt set and not matching skip if [ ! -z "${HOST_ONLY}" ] && [ "${HOST_ONLY}" != "${hostname}" ]; then continue; fi # login user name username=$(echo "${line}" | cut -d "," -f 2); # if username opt set and not matching skip if [ ! -z "${USER_ONLY}" ] && [ "${USER_ONLY}" != "${username}" ]; then continue; fi # check if force or if last rotaet in valid range if [ "${FORCE}" -eq 0 ] && [ -f "${LAST_ROTATE}${hostname}_${username}.last-rotate"]; then # holds unix timestamp, if now - this timestamp is < key_age => skip last_rotate=$(cat "${LAST_ROTATE}${hostname}_${username}.last-rotate"); current_timestamp=$(date +%s) age=$(( ($current_timestamp - $last_rotate) )) days_left=$(( (age)/(3600*24) )) if [ $days_left -le $key_age ]; then echo "Last rotate for ${username}@${hostname} was ${days_left} days ago, minimum is ${key_age}"; continue; fi fi # flags: (not used at the moment) flags=$(echo "${line}" | cut -d "," -f 3); # name for the SSH key files SSH_KEY_FILE="${hostname}_${username}.pem"; SSH_KEY_PUB_FILE="${hostname}_${username}.pem.pub"; # create name echo "Create new key for: ${username}@${hostname} with flags '${flags}'"; # if current exist, skip creation # if pem or pub missing, but not both, alert and skip # else create new if [ -f "${SSH_KEY_FILE}" ] ssh-keygen -t ed25519 -N "" -C "${hostname}: $(date +%F)" -f "${SSH_PRIVATE_KEYS}${SSH_KEY_FILE}" # move the public key to the current folder mv "${SSH_PRIVATE_KEYS}${SSH_KEY_PUB_FILE}" "${SSH_PUBLIC_KEYS_CURRENT}"; # deploy public key to server # - master admin file install_ssh_key "${hostname}" "${username}" "${SSH_PUBLIC_KEYS_CURRENT}${SSH_KEY_PUB_FILE}" "/etc/ssh/authorized_keys--master" # - admin ssh config auth file install_ssh_key "${hostname}" "${username}" "${SSH_PUBLIC_KEYS_CURRENT}${SSH_KEY_PUB_FILE}" "/etc/ssh/authorized_keys/${username}" # - copy local PEM file to archive folder if [ ${DRYRUN} -eq 0 ]; then if [ -f "${PEM_SERVER}${SSH_KEY_FILE}" ]; then cp "${PEM_SERVER}${SSH_KEY_FILE}" "${PEM_ARCHIVE}"; fi # - copy to local ssh folder mv "${SSH_PRIVATE_KEYS}${SSH_KEY_FILE}" "${PEM_SERVER}"; fi # post roate write timestamp into rotate file if [ ${DRYRUN} -eq 0 ]; then echo $(date +%s) > "${LAST_ROTATE}${hostname}_${username}.last-rotate"; fi done # __END__