diff options
Diffstat (limited to 'src/Updating_SSH_Keys_Across_an_Environment.ascii')
-rw-r--r-- | src/Updating_SSH_Keys_Across_an_Environment.ascii | 347 |
1 files changed, 347 insertions, 0 deletions
diff --git a/src/Updating_SSH_Keys_Across_an_Environment.ascii b/src/Updating_SSH_Keys_Across_an_Environment.ascii new file mode 100644 index 0000000..becdea2 --- /dev/null +++ b/src/Updating_SSH_Keys_Across_an_Environment.ascii @@ -0,0 +1,347 @@ +Updating SSH Keys Across an Environment +======================================= +:author: Aaron Ball +:email: nullspoon@iohq.net + + +== {doctitle} + +Most Linux environments with any number of servers uses keys to perform tasks +from simple manual administration to gathering manifests, backing up config +files across an environment, and really any kind of automation. Why? Because +passwords are terrible things (how's that for indignant). Seriously though, +despite the risks passwords present with minimum constraints not being +appropriately set or enforced, at least passwords make authentication and +semi-secure security accessible to people. Of course, keys are preferable, but +not reachable for the general public. Enough about my philosophical ramblings +about security though. I have several servers that I run (including this one) +that all use keys almost exclusively for logins. Like passwords, keys should be +cycled through frequently as well and if you have things set up right, that +should be completely painless. Here's the script I wrote to bulk change +passwords across my entire environment. + +---- +#!/usr/bin/env bash +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# @author Nullspoon <nullspoon@iohq.net> +# + +manifest='' +key='' +action='' +id='' +user='' + +# +# Backups by a specific ssh key to <date_modified>.<key_name> +# +# @param ssh_base string Path to where the ssh keys and configs are stored +# @param key string Name of the key to backup +# +# @return string The filename of the key backup +# +function backupKeys { + local ssh_base=$1 + local key=$2 + moved=0; + date="" + priv_ls=$(ls -l --time-style='+%Y%m%d%H%M%S' ${ssh_base}/${key}) + date=$(echo "${priv_ls}" | tr -s ' ' | cut -d ' ' -f 6); + # Rename the old key + if [[ -e "${ssh_base}/${key}" ]]; then + mv ${ssh_base}/${key} ${ssh_base}/${date}.${key} + moved=1; + fi + # Rename the old public key + if [[ -e "${ssh_base}/${key}.pub" ]]; then + mv ${ssh_base}/${key}.pub ${ssh_base}/${date}.${key}.pub + moved=1; + fi + if [[ ${moved} == 0 ]]; then + echo '' + else + chmod 700 ${ssh_base}/* + echo "${ssh_base}/${date}.${key}" + fi +} + +# +# Pushes specific public key to remote user's authorized_keys +# +# @param user string User owning the authorized_keys file to be modified +# @param server string Server the user's authorized_keys file is on +# @param old_key string The key to use for authentication +# @param new_key string The key, public or private, to be pushed +# +function pushKey { + local conn=$1 + local old_key=$2 + local new_key=$3 + if [[ ${#new_key} -lt '4' ]]; then + echo "Key to be pushed is not a public key." + exit + fi + + ispub=$(keyIsPublic ${new_key}) + if [[ ${ispub} == 0 ]]; then + # Append .pub because a public key wasn't specified + new_key="${new_key}.pub" + fi + + local cmd="if [[ ! -d ~/.ssh/ ]]; then mkdir ~/.ssh/; fi" + cmd="${cmd} && echo '$(cat ${new_key})' >> ~/.ssh/authorized_keys" + + # Unset our identity file if it doesn't exist + local id_file="-i ${old_key}" + if [[ ${old_key} == '' ]]; then + id_file='' + fi + contents=$(cat ${new_key}) + ssh -q ${id_file} ${conn} "${cmd}" +} + +# +# Removes the specified public key from a remote user's authorized_keys file +# +# @param user string User owning the authorized_keys file to be modified +# @param server string Server the user's authorized_keys file is on +# @param key string The key to use for authentication which is to be removed +# +function removeRemoteKey { + local conn=$1 + local key=$2 + pub_key='' + priv_key='' + ispub=$(keyIsPublic ${key}) + if [[ ${ispub} == 0 ]]; then + priv_key="${key}" + pub_key="${key}.pub" + else + priv_key="${key:0:-4}" + pub_key="${key}" + fi + contents=$(cat "${pub_key}") + local cmd="if [[ ! -d ~/.ssh/ ]]; then mkdir ~/.ssh/; fi" + cmd="${cmd} && cat ~/.ssh/authorized_keys | grep -v '${contents}' " + cmd="${cmd} > ~/.ssh/auth_keys" + cmd="${cmd} && mv ~/.ssh/auth_keys ~/.ssh/authorized_keys" + ssh -q -i ${priv_key} ${conn} "${cmd}" +} + +# +# Determines if the specified key is public (or not which would be private). +# +# @param key string Path to the key to check +# +# @return int Whether or not the key is public +# +function keyIsPublic { + key=$1 + if [[ ${#key} -lt '4' ]]; then + echo 0; + fi + # Check the extension + ext=${key:$((${#key}-4)):${#key}} + if [[ ${ext} == '.pub' ]]; then + echo 1; + fi + echo 0 +} + +# +# Generates a new ssh key of the length 4096 +# +# @param filepath string Path to where the new ssh key will be written +# @param bits int Number of bits in the new key (eg: 2048, 4096, 8192, etc.) +# +function genKey { + local filepath=$1 + local bits=$2 + ssh-keygen -b ${bits} -f "${filepath}" -N '' +} + +# +# Prints the help text +# +function getHelp { + echo + echo -n "Manages ssh keys en masse. Designed to perform pushes, " + echo " removals, and creations of ssh keys on lists of servers." + echo + echo "Usage: keymanage.sh action --manifest systems.list" + echo -n " -m, --manifest Text file containing a list of systems, " + echo "delimited by new lines." + echo -n " [-k, --key] Path to a key to perform an action " + echo "(push or remove) with." + echo -n " [-i, --id] Key to use for automated logins. Not " + echo "used when performing an update." + echo -n " [-u, --user] Username on remote systems to work on " + echo "(defaults to root)." + echo +} + +function parseArgs { + argv=(${@}) + # Parse the arguments + for(( i=0; i<${#argv[*]}; i++ )); do + if [[ ${argv[$i]} == '-h' || ${argv[$i]} == '--help' ]]; then + getHelp + exit + elif [[ ${argv[$i]} == '-m' || ${argv[$i]} == '--manifest' ]]; then + manifest=${argv[$i+1]} + i=$(( ${i} + 1 )) + elif [[ ${argv[$i]} == '-k' || ${argv[$i]} == '--key' ]]; then + key=${argv[$i+1]} + i=$(( ${i} + 1 )) + elif [[ ${argv[$i]} == '-i' || ${argv[$i]} == '--id' ]]; then + id=${argv[$i+1]} + i=$(( ${i} + 1 )) + elif [[ ${argv[$i]} == '-u' || ${argv[$i]} == '--user' ]]; then + user=${argv[$i+1]} + i=$(( ${i} + 1 )) + else + action=${argv[$i]} + fi + done + + # Enforce some business rules + echo + exit=0; + if [[ ${action} == '' ]]; then + echo "Please specify an action."; + echo " Available actions: push, remove, update." + echo + exit=1; + fi + if [[ ${manifest} == '' ]]; then + echo "Please specify a manifest file." + echo " Example: keymanage.sh action [-m|--manifest] ./systems.txt" + echo + exit=1; + fi + if [[ ${exit} == 1 ]]; then + exit + fi +} + +# +# Determines the path to the parent directory containing a file. +# +# @param filepath string Path to the file to get the parent directory for +# +# @return string Path to the file's parent directory +# +function getFilePath { + filepath=$1 + filename=$(basename ${filepath}) + echo ${filepath} | sed "s/\(.*\)${filename}/\1/" +} + +# +# Push main function. One param because the rest are global +# +function keyPush { + argv=( ${@} ) + if [[ ${id} == '' ]]; then + echo "No identity file specified (-i). This will likely be painful." + fi + for (( i=0; i<${#argv[*]}; i++ )); do + dest=${argv[$i]} + if [[ ${id} == '' ]]; then + pushKey "${dest}" '' ${key} + else + pushKey "${dest}" ${id} ${key} + fi + echo "Key ${key} added for ${dest}." + done +} + +# +# Update main function. One param because the rest are global +# +function keyUpdate { + argv=( ${@} ) + ssh_base=$(getFilePath ${key}) + filename=$(basename ${key}) + # Backup our old key + backup_key="$(backupKeys ${ssh_base} ${filename})" + + # Let's get to work on that new key + genKey "${key}" 4096 + + for (( i=0; i<${#argv[*]}; i++ )); do + dest=${argv[$i]} + if [[ ${backup_key} == '' ]]; then + echo "No current key exists." + echo "Skipping backup and removal from remote." + # Push the new key + pushKey "${dest}" '' ${key} + else + # Push the new key + pushKey "${dest}" ${backup_key} ${key} + # Clean up the old key from our remote + removeRemoteKey "${dest}" "${backup_key}" + fi + echo "Key ${key} updated for ${dest}." + done +} + +# +# Remove main function. One param because the rest are global +# +function keyRemove { + argv=( ${@} ) + for (( i=0; i<${#argv[*]}; i++ )); do + dest=${argv[$i]} + removeRemoteKey "${dest}" "${key}" + echo "Key ${key} removed from ${dest}." + done +} + +# +# The main function +# +function main { + # Parse our script args + # Believe me, this is a lot better than the alternatives + parseArgs ${@} + + destinations=( $(cat ${manifest}) ) + # Key required + if [[ ${key} == '' ]]; then + echo -n "Please specify a key (-k) to ${action}." + echo + exit + fi + + # Let's start doing stuff + if [[ ${action} == 'push' ]]; then + keyPush ${destinations[*]} + elif [[ ${action} == 'update' ]]; then + keyUpdate ${destinations[*]} + elif [[ ${action} == 'remove' ]]; then + keyRemove ${destinations[*]} + fi +} + +main ${@} +---- + + +Category:Linux + + +// vim: set syntax=asciidoc: |