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

Generated by cgit