diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/ReadMe.md b/ReadMe.md index e68c837..7c41d69 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -1,4 +1,4 @@ -# Rotate all admin keys on servers +# Rotate all ssh keys on servers for certain users - create new key with name scheme - copy to server to diff --git a/bin/remove-old-ssh-keys.sh b/bin/remove-old-ssh-keys.sh new file mode 100644 index 0000000..6c4de1a --- /dev/null +++ b/bin/remove-old-ssh-keys.sh @@ -0,0 +1,29 @@ +#!/usr/bin/env bash + +# base folder for all data +BASE_FOLDER=$(dirname "$(readlink -f "$0")")"/"; +# config folder +CONFIG_BASE="${BASE_FOLDER}../config/"; +# previous public key +SSH_PUBLIC_KEYS_PREVIOUS="${BASE_FOLDER}../ssh-public-keys/previous/"; + +# load config +if [ -f "${CONFIG_BASE}settings.ini" ]; then + source <(grep = "${CONFIG_BASE}settings.ini" | sed 's/ *= */=/g') +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 + +# find last public in remote server and remove it +for line in `cat "${CONFIG_BASE}${server_list}" | sed 1d`; do + hostname=$(echo "${line}" | cut -d "," -f 1) + # flags are current "M" for multi key, has other users public key in too + flags=$(echo "${line}" | cut -d "," -f 2) + echo "Remove previous key for: ${hostname}"; + # find in master key and $admin user +done + +# __END__ diff --git a/bin/rotate-ssh-keys.sh b/bin/rotate-ssh-keys.sh new file mode 100755 index 0000000..e200e5b --- /dev/null +++ b/bin/rotate-ssh-keys.sh @@ -0,0 +1,213 @@ +#!/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__ diff --git a/config/.gitignore b/config/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/config/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/last-rotate/.gitignore b/last-rotate/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/last-rotate/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/ssh-keys/.gitignore b/ssh-keys/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/ssh-keys/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/ssh-public-keys/current/.gitignore b/ssh-public-keys/current/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/ssh-public-keys/current/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/ssh-public-keys/previous/.gitignore b/ssh-public-keys/previous/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/ssh-public-keys/previous/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore