summaryrefslogtreecommitdiff
path: root/keymanage
blob: 141db15cb508e7e32436b407923a788f0ea2f56c (plain)
    1 #!/usr/bin/env bash
    2 #
    3 # Keymanage provides functionality to push, remove, and swap/refresh ssh keys
    4 # on a list of systems for the specified users
    5 #
    6 # Copyright (C) 2014  Aaron Ball <nullspoon@iohq.net>
    7 # 
    8 # This program is free software: you can redistribute it and/or modify
    9 # it under the terms of the GNU General Public License as published by
   10 # the Free Software Foundation, either version 3 of the License, or
   11 # (at your option) any later version.
   12 # 
   13 # This program is distributed in the hope that it will be useful,
   14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
   15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   16 # GNU General Public License for more details.
   17 # 
   18 # You should have received a copy of the GNU General Public License
   19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
   20 # 
   21 manifest=''
   22 key=''
   23 action=''
   24 id=''
   25 user=''
   26 
   27 #
   28 # Backups by a specific ssh key to <date_modified>.<key_name>
   29 #
   30 # @param ssh_base string Path to where the ssh keys and configs are stored
   31 # @param key string Name of the key to backup
   32 #
   33 # @return string The filename of the key backup
   34 #
   35 function backup_keys {
   36 	local ssh_base=$1
   37 	local key=$2
   38 	moved=0;
   39 	date=""
   40 	priv_ls=$(ls -l --time-style='+%Y%m%d%H%M%S' ${ssh_base}/${key})
   41 	date=$(echo "${priv_ls}" | tr -s ' ' | cut -d ' ' -f 6);
   42 	# Rename the old key
   43 	if [[ -e "${ssh_base}/${key}" ]]; then
   44 		mv ${ssh_base}/${key} ${ssh_base}/${date}.${key}
   45 		moved=1;
   46 	fi
   47 	# Rename the old public key
   48 	if [[ -e "${ssh_base}/${key}.pub" ]]; then
   49 		mv ${ssh_base}/${key}.pub ${ssh_base}/${date}.${key}.pub
   50 		moved=1;
   51 	fi
   52 	if [[ ${moved} == 0 ]]; then
   53 		echo ''
   54 	else
   55 		chmod 700 ${ssh_base}/*
   56 		echo "${ssh_base}/${date}.${key}"
   57 	fi
   58 }
   59 
   60 #
   61 # Pushes specific public key to remote user's authorized_keys
   62 #
   63 # @param user string User owning the authorized_keys file to be modified
   64 # @param server string Server the user's authorized_keys file is on
   65 # @param old_key string The key to use for authentication
   66 # @param new_key string The key, public or private, to be pushed
   67 #
   68 function push_key {
   69 	local conn=$1
   70 	local old_key=$2
   71 	local new_key=$3
   72 	if [[ ${#new_key} -lt '4' ]]; then
   73 		echo "Key to be pushed is not a public key."
   74 		exit
   75 	fi
   76 
   77 	ispub=$(key_is_public ${new_key})
   78 	if [[ ${ispub} == 0 ]]; then
   79 		# Append .pub because a public key wasn't specified
   80 		new_key="${new_key}.pub"
   81 	fi
   82 
   83 	local cmd="if [[ ! -d ~/.ssh/ ]]; then mkdir ~/.ssh/; fi"
   84 	cmd="${cmd} && echo '$(cat ${new_key})' >> ~/.ssh/authorized_keys"
   85 
   86 	# Unset our identity file if it doesn't exist
   87 	local id_file="-i ${old_key}"
   88 	if [[ ${old_key} == '' ]]; then
   89 		id_file=''
   90 	fi
   91 	contents=$(cat ${new_key})
   92 	ssh -q ${id_file} ${conn} "${cmd}"
   93 }
   94 
   95 #
   96 # Removes the specified public key from a remote user's authorized_keys file
   97 #
   98 # @param user string User owning the authorized_keys file to be modified
   99 # @param server string Server the user's authorized_keys file is on
  100 # @param key string The key to use for authentication which is to be removed
  101 #
  102 function remove_remote_key {
  103 	local conn=$1
  104 	local key=$2
  105 	pub_key=''
  106 	priv_key=''
  107 	ispub=$(key_is_public ${key})
  108 	if [[ ${ispub} == 0 ]]; then
  109 		priv_key="${key}"
  110 		pub_key="${key}.pub"
  111 	else
  112 		priv_key="${key:0:-4}"
  113 		pub_key="${key}"
  114 	fi
  115 	contents=$(cat "${pub_key}")
  116 	local cmd="if [[ ! -d ~/.ssh/ ]]; then mkdir ~/.ssh/; fi"
  117 	cmd="${cmd} && cat ~/.ssh/authorized_keys | grep -v '${contents}' "
  118 	cmd="${cmd} > ~/.ssh/auth_keys"
  119 	cmd="${cmd} && mv ~/.ssh/auth_keys ~/.ssh/authorized_keys"
  120 	if [[ ${id} != '' ]]; then
  121 		ssh -q -i ${id} ${conn} "${cmd}"
  122 	else
  123 		ssh -q ${conn} "${cmd}"
  124 	fi
  125 }
  126 
  127 #
  128 # Determines if the specified key is public (or not which would be private).
  129 #
  130 # @param key string Path to the key to check
  131 #
  132 # @return int Whether or not the key is public
  133 #
  134 function key_is_public {
  135 	key=$1
  136 	if [[ ${#key} -lt '4' ]]; then
  137 		echo 0;
  138 	fi
  139 	# Check the extension
  140 	ext=${key:$((${#key}-4)):${#key}}
  141 	if [[ ${ext} == '.pub' ]]; then
  142 		echo 1;
  143 	fi
  144 	echo 0
  145 }
  146 
  147 #
  148 # Generates a new ssh key of the length 4096
  149 #
  150 # @param filepath string Path to where the new ssh key will be written
  151 # @param bits int Number of bits in the new key (eg: 2048, 4096, 8192, etc.)
  152 #
  153 function gen_key {
  154 	local filepath=$1
  155 	local bits=$2
  156 	ssh-keygen -b ${bits} -f "${filepath}" -N ''
  157 }
  158 
  159 #
  160 # Prints the help text
  161 #
  162 function get_help {
  163 	echo "
  164 Manages ssh keys en masse. Designed to perform pushes, removals, and swaps
  165 of ssh keys on a llist of servers for the current or specified user.
  166 
  167 Usage: keymanage.sh action --manifest systems.list
  168 
  169 Actions: push, update, remove
  170 
  171 Arguments:
  172  -m,--manifest   Text file containing a list of systems, delimited by new
  173                  lines.
  174  -k,--key        Path to a key to perform an action (push or remove) with.
  175  -i,--id         Key to use for automated logins. Not used when performing an
  176                  update.
  177  -u,--user       Username on remote systems to work on (defaults to root).
  178  
  179 "
  180 }
  181 
  182 function parse_args {
  183 	argv=(${@})
  184 	# Parse the arguments
  185 	for(( i=0; i<${#argv[*]}; i++ )); do
  186 		if [[ ${argv[$i]} == '-h' || ${argv[$i]} == '--help' ]]; then
  187 			echo "$(get_help)"
  188 			exit
  189 		elif [[ ${argv[$i]} == '-m' || ${argv[$i]} == '--manifest' ]]; then
  190 			manifest=${argv[$i+1]}
  191 			i=$(( ${i} + 1 ))
  192 		elif [[ ${argv[$i]} == '-k' || ${argv[$i]} == '--key' ]]; then
  193 			key=${argv[$i+1]}
  194 			i=$(( ${i} + 1 ))
  195 		elif [[ ${argv[$i]} == '-i' || ${argv[$i]} == '--id' ]]; then
  196 			id=${argv[$i+1]}
  197 			i=$(( ${i} + 1 ))
  198 		elif [[ ${argv[$i]} == '-u' || ${argv[$i]} == '--user' ]]; then
  199 			user=${argv[$i+1]}
  200 			i=$(( ${i} + 1 ))
  201 		else
  202 			action=${argv[$i]}
  203 		fi
  204 	done
  205 
  206 	# Enforce some business rules
  207 	echo
  208 	exit=0;
  209 	if [[ ${action} == '' ]]; then
  210 		echo "Please specify an action.";
  211 		echo "  Available actions: push, remove, update."
  212 		echo
  213 		exit=1;
  214 	fi
  215 	if [[ ${manifest} == '' ]]; then
  216 		echo "Please specify a manifest file."
  217 		echo "  Example: keymanage.sh action [-m|--manifest] ./systems.txt"
  218 		echo
  219 		exit=1;
  220 	fi
  221 	if [[ ${exit} == 1 ]]; then
  222 		exit
  223 	fi
  224 }
  225 
  226 #
  227 # Determines the path to the parent directory containing a file.
  228 #
  229 # @param filepath string Path to the file to get the parent directory for
  230 #
  231 # @return string Path to the file's parent directory
  232 #
  233 function get_file_path {
  234 	filepath=$1
  235 	filename=$(basename ${filepath})
  236 	echo ${filepath} | sed "s/\(.*\)${filename}/\1/"
  237 }
  238 
  239 #
  240 # Push main function. One param because the rest are global
  241 #
  242 function key_push {
  243 	argv=( ${@} )
  244 	if [[ ${id} == '' ]]; then
  245 		echo "No identity file specified (-i). This will likely be painful."
  246 	fi
  247 	for (( i=0; i<${#argv[*]}; i++ )); do
  248 		dest=${argv[$i]}
  249 		if [[ ${id} == '' ]]; then
  250 			push_key "${dest}" '' ${key} 
  251 		else
  252 			push_key "${dest}" ${id} ${key} 
  253 		fi
  254 		echo "Key ${key} added for ${dest}."
  255 	done
  256 }
  257 
  258 #
  259 # Update main function. One param because the rest are global
  260 #
  261 function key_update {
  262 	argv=( ${@} )
  263 	ssh_base=$(get_file_path ${key})
  264 	filename=$(basename ${key})
  265 	# Backup our old key
  266 	backup_key="$(backup_keys ${ssh_base} ${filename})"
  267 
  268 	# Let's get to work on that new key
  269 	gen_key "${key}" 4096
  270 
  271 	for (( i=0; i<${#argv[*]}; i++ )); do
  272 		dest=${argv[$i]}
  273 		if [[ ${backup_key} == '' ]]; then
  274 			echo "No current key exists."
  275 			echo "Skipping backup and removal from remote."
  276 			# Push the new key
  277 			push_key "${dest}" '' ${key} 
  278 		else
  279 			# Push the new key
  280 			push_key "${dest}" ${backup_key} ${key} 
  281 			# Clean up the old key from our remote
  282 			remove_remote_key "${dest}" "${backup_key}"
  283 		fi
  284 		echo "Key ${key} updated for ${dest}."
  285 	done
  286 }
  287 
  288 #
  289 # Remove main function. One param because the rest are global
  290 #
  291 function key_remove {
  292 	argv=( ${@} )
  293 	for (( i=0; i<${#argv[*]}; i++ )); do
  294 		dest=${argv[$i]}
  295 		remove_remote_key "${dest}" "${key}"
  296 		echo "Key ${key} removed from ${dest}."
  297 	done
  298 }
  299 
  300 #
  301 # The main function
  302 #
  303 function main {
  304 	# Parse our script args
  305 	# Believe me, this is a lot better than the alternatives
  306 	parse_args ${@}
  307 	
  308 	destinations=( $(cat ${manifest}) )
  309 	# Key required
  310 	if [[ ${key} == '' ]]; then
  311 		echo -n "Please specify a key (-k) to ${action}."
  312 		echo
  313 		exit
  314 	fi
  315 
  316 	# Let's start doing stuff
  317 	if [[ ${action} == 'push' ]]; then
  318 		key_push ${destinations[*]}
  319 	elif [[ ${action} == 'update' ]]; then
  320 		key_update ${destinations[*]}
  321 	elif [[ ${action} == 'remove' ]]; then
  322 		key_remove ${destinations[*]}
  323 	fi
  324 }
  325 
  326 main ${@}

Generated by cgit