This repository has been archived by the owner. It is now read-only.
Permalink
Cannot retrieve contributors at this time
Fetching contributors…
| #!/usr/bin/env bash | |
| VERSION="1.7" | |
| AUTHY_URL="https://api.authy.com" | |
| APP_ROOT=`dirname $0` | |
| CONFIG_FILE="$APP_ROOT/authy-ssh.conf" | |
| UPSTREAM_URL="https://raw.githubusercontent.com/authy/authy-ssh/master/authy-ssh" | |
| READ_TIMEOUT=60 | |
| MIN_API_KEY_SIZE=12 | |
| OK=0 | |
| FAIL=1 | |
| SEQ="seq" | |
| export TERM="xterm-256color" | |
| NORMAL=$(tput sgr0) | |
| GREEN=$(tput setaf 2; tput bold) | |
| YELLOW=$(tput setaf 3) | |
| RED=$(tput setaf 1) | |
| function red() { | |
| echo -e "$RED$*$NORMAL" | |
| } | |
| function green() { | |
| echo -e "$GREEN$*$NORMAL" | |
| } | |
| function yellow() { | |
| echo -e "$YELLOW$*$NORMAL" | |
| } | |
| function debug() { | |
| if [[ $DEBUG_AUTHY ]] | |
| then | |
| echo ">>> $*" | |
| fi | |
| } | |
| function check_dependencies() { | |
| if ! type "seq" > /dev/null 2>&1; | |
| then | |
| if ! type "jot" > /dev/null 2>&1; | |
| then | |
| red "You need installed on your system either 'seq' or 'jot' command" | |
| exit $FAIL | |
| else | |
| SEQ="jot" | |
| fi | |
| fi | |
| } | |
| function escape_input() { | |
| sed "s/[;\`\"\$\' ]//g" <<<$* | |
| } | |
| function escape_number() { | |
| sed 's/[^0-9]*//g' <<< $* | |
| } | |
| function os_version() { | |
| echo `uname -srm` | |
| } | |
| function read_input() { | |
| read -t "$READ_TIMEOUT" input | |
| echo "$(escape_input $input)" | |
| } | |
| function read_number() { | |
| read -t "$READ_TIMEOUT" number | |
| echo "$(escape_number $number)" | |
| } | |
| function require_root() { | |
| debug "Checking if user is root" | |
| find_sshd_config | |
| if [[ ! -w $SSHD_CONFIG ]] | |
| then | |
| red "root permissions are required to run this command. try again using sudo" | |
| exit $FAIL | |
| fi | |
| } | |
| function require_curl() { | |
| which curl 2>&1 > /dev/null | |
| if [ $? -eq 0 ] | |
| then | |
| return $OK | |
| fi | |
| # if `which` is not installed this check is ran | |
| curl --help 2>&1 > /dev/null | |
| return $FAIL | |
| } | |
| function user_agent() { | |
| os="$(os_version)" | |
| echo "User-Agent: AuthySSH/${VERSION} (${os})" | |
| } | |
| function find_sshd_config() { | |
| debug "Trying to find sshd_config file" | |
| if [[ -f /etc/sshd_config ]] | |
| then | |
| SSHD_CONFIG="/etc/sshd_config" | |
| elif [[ -f /etc/ssh/sshd_config ]] | |
| then | |
| SSHD_CONFIG="/etc/ssh/sshd_config" | |
| else | |
| red "Cannot find sshd_config in your server. Authy SSH will be enabled when you add the ForceCommand to it" | |
| fi | |
| } | |
| function add_force_command() { | |
| debug "Trying to add force command to $SSHD_CONFIG" | |
| find_sshd_config | |
| authy_ssh_command="$1" | |
| if [[ -w $SSHD_CONFIG ]] | |
| then | |
| yellow "Adding 'ForceCommand ${authy_ssh_command} login' to ${SSHD_CONFIG}" | |
| uninstall_authy "quiet" # remove previous installations | |
| echo -e "\nForceCommand ${authy_ssh_command} login" >> ${SSHD_CONFIG} | |
| echo "" | |
| check_sshd_config_file | |
| red " MAKE SURE YOU DO NOT MOVE/REMOVE ${authy_ssh_command} BEFORE UNINSTALLING AUTHY SSH" | |
| sleep 5 | |
| fi | |
| } | |
| function check_sshd_config_file() { | |
| yellow "Checking the validity of $SSHD_CONFIG file..." | |
| sshd -t | |
| if [ $? -ne 0 ] | |
| then | |
| red "sshd_config file is invalid. MAKE SURE YOU DO NOT RESTART THE SSH SERVER UNTIL YOU FIX IT." | |
| exit $FAIL | |
| fi | |
| } | |
| function install_authy() { | |
| source="$1" | |
| dest="$2/authy-ssh" | |
| if [[ ! $2 ]] | |
| then | |
| dest="/usr/local/bin/authy-ssh" # defaults to /usr/local/bin | |
| fi | |
| config_file="${dest}.conf" | |
| # | |
| # Ensure our target directory is present | |
| # | |
| set -e | |
| mkdir -p `dirname "${dest}"` | |
| set +e | |
| if [[ ! -r `dirname "${dest}"` ]] | |
| then | |
| red "${dest} is not writable. Try again using sudo" | |
| return $FAIL | |
| fi | |
| yellow "Copying ${source} to ${dest}..." | |
| cp "${source}" "${dest}" | |
| yellow "Setting up permissions..." | |
| chmod 755 $dest | |
| if [[ ! -f ${config_file} ]] | |
| then | |
| echo -n "Enter the Authy API key: " | |
| read authy_api_key | |
| if [ ${#authy_api_key} -lt ${MIN_API_KEY_SIZE} ] | |
| then | |
| red "you have entered a wrong API key" | |
| return $FAIL | |
| fi | |
| echo "Default action when api.authy.com cannot be contacted: " | |
| echo "" | |
| echo " 1. Disable two factor authentication until api.authy.com is back" | |
| echo " 2. Don't allow logins until api.authy.com is back" | |
| echo "" | |
| echo -n "type 1 or 2 to select the option: " | |
| read default_verify_action | |
| case $default_verify_action in | |
| 1) | |
| default_verify_action="disable" | |
| ;; | |
| 2) | |
| default_verify_action="enforce" | |
| ;; | |
| *) | |
| red "you have entered an invalid option" | |
| return $FAIL | |
| ;; | |
| esac | |
| yellow "Generating initial config on ${config_file}..." | |
| echo "banner=Good job! You've securely logged in with Authy." > "${config_file}" | |
| echo "api_key=${authy_api_key}" >> "${config_file}" | |
| echo "default_verify_action=${default_verify_action}" >> "${config_file}" | |
| else | |
| red "A config file was found on ${config_file}. Edit it manually if you want to change the API key" | |
| fi | |
| chmod 644 ${config_file} | |
| add_force_command "${dest}" | |
| echo "" | |
| echo "To enable two-factor authentication on your account type the following command: " | |
| echo "" | |
| if [[ $SUDO_USER ]] | |
| then | |
| green " sudo ${dest} enable $SUDO_USER <your-email> <your-numeric-country-code> <your-cellphone>" | |
| green " Example: sudo $0 enable $SUDO_USER myuser@example.com 1 401-390-9987" | |
| else | |
| green " sudo ${dest} enable $USER <your-email> <your-numeric-country-code> <your-cellphone>" | |
| green " Example: sudo $0 enable $USER myuser@example.com 1 401-390-9987" | |
| fi | |
| echo "" | |
| echo "To enable two-factor authentication on user account type: " | |
| echo "" | |
| green " sudo ${dest} enable <local-username> <user-email> <user-cellphone-country-code> <user-cellphone>" | |
| echo "" | |
| echo "To uninstall Authy SSH type:" | |
| echo "" | |
| green " sudo ${dest} uninstall" | |
| echo "" | |
| yellow " Restart the SSH server to apply changes" | |
| echo "" | |
| } | |
| function uninstall_authy() { | |
| find_sshd_config | |
| if [[ $1 != "quiet" ]] | |
| then | |
| yellow "Uninstalling Authy SSH from $SSHD_CONFIG..." | |
| fi | |
| if [[ -w $SSHD_CONFIG ]] | |
| then | |
| sed -ie '/^ForceCommand.*authy-ssh.*/d' $SSHD_CONFIG | |
| fi | |
| if [[ $1 != "quiet" ]] | |
| then | |
| green "Authy SSH was uninstalled." | |
| yellow "Now restart the ssh server to apply changes and then remove ${APP_ROOT}/authy-ssh and $CONFIG_FILE" | |
| fi | |
| } | |
| function check_config_file() { | |
| debug "Checking config file at $CONFIG_FILE" | |
| dir=`dirname ${CONFIG_FILE}` | |
| if [[ ! -r $dir ]] | |
| then | |
| red "ERROR: ${dir} cannot be written by $USER" | |
| return $FAIL | |
| fi | |
| if [[ ! -f $CONFIG_FILE ]] | |
| then | |
| red "Authy ssh has not been configured" # FIXME: add more info | |
| return $FAIL | |
| fi | |
| if [[ $1 == "writable" && ! -w $CONFIG_FILE ]] | |
| then | |
| red "$CONFIG_FILE is not writable. Please try again using sudo" | |
| exit $FAIL | |
| fi | |
| chmod 644 "$CONFIG_FILE" 2>/dev/null | |
| return $OK | |
| } | |
| # Checks if the API KEY is valid. This function receives one argument which can be: | |
| # - run: runs a shell even if the test fails. | |
| # - anything else: exits the command | |
| function check_api_key() { | |
| debug "Checking api key" | |
| if [ ${#AUTHY_API_KEY} -lt ${MIN_API_KEY_SIZE} ] | |
| then | |
| red "Cannot find a valid api key" | |
| run_shell | |
| fi | |
| return $OK | |
| } | |
| # Usage: $(read_config banner) | |
| function read_config() { | |
| key="$1" | |
| if [[ -f $CONFIG_FILE ]] | |
| then | |
| KEYFOUND=$FAIL | |
| while IFS='=' read -r ckey value | |
| do | |
| if [[ $ckey == $key ]] | |
| then | |
| echo $value # don't stop the loop so we can read repeated keys | |
| KEYFOUND=$OK | |
| fi | |
| done < $CONFIG_FILE | |
| return $KEYFOUND | |
| fi | |
| red "ERROR: $config_file couldn't be found" | |
| return $FAIL | |
| } | |
| function protect_user() { | |
| local_user="$(escape_input "$1")" | |
| if [[ $local_user == "" ]] | |
| then | |
| local_user="$USER" | |
| fi | |
| eval home_path="~$local_user" | |
| auth_keys="${home_path}/.ssh/authorized_keys" | |
| debug "Protecting user: $local_user with auth keys: $auth_keys" | |
| if [[ ! -w $home_path ]] | |
| then | |
| red "You don't have access to $auth_keys. Please try again as root." | |
| exit $FAIL | |
| fi | |
| echo -n "Enter your public ssh key: " | |
| read ssh_key | |
| grep "$ssh_key" "$auth_keys" 2>&1 >/dev/null | |
| grep_exit_code=$? | |
| if [[ $grep_exit_code -eq 0 ]] | |
| then | |
| yellow "The ssh key is already present in $auth_keys. Please remove it and try again." | |
| exit $FAIL | |
| fi | |
| register_user_on_authy "$local_user" | |
| if [[ $? -ne 0 || ! $authy_user_id ]] | |
| then | |
| exit $FAIL | |
| fi | |
| echo "command=\"$COMMAND login $authy_user_id\" $ssh_key" >> "${auth_keys}" | |
| if [[ $(whoami) == "root" ]] | |
| then | |
| chown "$local_user" "$auth_keys" | |
| fi | |
| green "The user is now protected with authy" | |
| } | |
| # usage: register_user "local_user" "<email>" "<country-code>" "<cellphone>" | |
| function register_user() { | |
| register_user_on_authy $* | |
| if [[ $? -ne 0 ]] | |
| then | |
| exit $FAIL | |
| fi | |
| if [[ $authy_user_id ]] | |
| then | |
| echo "user=$local_user:$authy_user_id" >> $CONFIG_FILE | |
| green "User was registered" | |
| else | |
| red "Cannot register user: $response" | |
| fi | |
| } | |
| function register_user_on_authy() { | |
| local_user="$(escape_input $1)" | |
| email="$(escape_input $2)" | |
| country_code="$(escape_number $3)" | |
| cellphone="$(escape_number $4)" | |
| if [[ $local_user == "" ]] | |
| then | |
| echo -n "Username: " | |
| local_user="$(read_input)" | |
| fi | |
| if [[ $country_code == "" ]] | |
| then | |
| echo -n "Your country code: " | |
| country_code="$(read_number)" | |
| fi | |
| if [[ $cellphone == "" ]] | |
| then | |
| echo -n "Your cellphone: " | |
| cellphone="$(read_number)" | |
| fi | |
| if [[ $email == "" ]] | |
| then | |
| echo -n "Your email: " | |
| email="$(read_input)" | |
| fi | |
| echo -e " | |
| Username:\t${local_user} | |
| Cellphone:\t(+${country_code}) ${cellphone} | |
| Email:\t${email} | |
| " | |
| echo -n "Do you want to enable this user? (y/n) " | |
| read should_continue | |
| if [[ "$should_continue" != y* ]] | |
| then | |
| return $FAIL | |
| fi | |
| url="$AUTHY_URL/protected/json/users/new?api_key=${AUTHY_API_KEY}" | |
| response=`id "${local_user}" 2>/dev/null` | |
| if [[ $? -ne 0 ]] | |
| then | |
| red "$local_user was not found in your system" | |
| return $FAIL | |
| fi | |
| useragent="$(user_agent)" | |
| response=`curl --connect-timeout 10 "${url}" -A "${useragent}" -d user[email]="${email}" -d user[country_code]="${country_code}" -d user[cellphone]="${cellphone}" -s 2>/dev/null` | |
| ok=true | |
| debug "[register-user] url: $url | response: $response | curl exit stats: $?" | |
| if [[ $response == *cellphone* ]] | |
| then | |
| yellow "Cellphone is invalid" | |
| ok=false | |
| fi | |
| if [[ $response == *email* ]] | |
| then | |
| yellow "Email is invalid" | |
| ok=false | |
| fi | |
| if [[ $ok == false ]] | |
| then | |
| return $FAIL | |
| fi | |
| if [[ $response == *user*id* ]] | |
| then | |
| authy_user_id=`echo $response | grep -o '[0-9]\{1,\}'` # match the authy id | |
| return $OK | |
| elif [[ $response == "invalid key" ]] | |
| then | |
| yellow "The api_key value in $CONFIG_FILE is not valid" | |
| else | |
| red "Unknown response: $response" | |
| fi | |
| return $FAIL | |
| } | |
| function run_shell() { | |
| if [[ "$SSH_ORIGINAL_COMMAND" != "" ]] # when user runs: ssh server <command> | |
| then | |
| debug "running command: $SSH_ORIGINAL_COMMAND" | |
| exec /bin/bash -c "${SSH_ORIGINAL_COMMAND}" | |
| elif [ $SHELL ] # when user runs: ssh server | |
| then | |
| debug "running shell: $SHELL" | |
| exec -l $SHELL | |
| fi | |
| exit $? | |
| } | |
| function find_authy_id() { | |
| for user in `read_config user` | |
| do | |
| IFS=":"; declare -a authy_user=($user) | |
| if [[ ${authy_user[0]} == $USER ]] | |
| then | |
| echo $(escape_number ${authy_user[1]}) | |
| return $OK | |
| fi | |
| done | |
| return $FAIL | |
| } | |
| # login <test|login> "token" "authy-id" | |
| function login() { | |
| mode="$(escape_input $1)" | |
| authy_token="$(escape_number $2)" | |
| authy_id="$(escape_number $3)" | |
| if [[ $authy_id == "" ]] | |
| then | |
| authy_id="$(find_authy_id)" | |
| fi | |
| debug "Logging $authy_id with $authy_token in $mode mode." | |
| if [[ $authy_token == "" ]] | |
| then | |
| red "You have to enter only digits." | |
| return $FAIL | |
| fi | |
| size=${#authy_token} | |
| if [[ $size -lt 6 || $size -gt 10 ]] | |
| then | |
| red "You have to enter a valid token." | |
| return $FAIL | |
| fi | |
| useragent="$(user_agent)" | |
| url="$AUTHY_URL/protected/json/verify/${authy_token}/${authy_id}?api_key=${AUTHY_API_KEY}&force=true" | |
| response=`curl --connect-timeout 30 -sL -w "|%{http_code}" -A "${useragent}" "${url}"` | |
| curl_exit_code=$? | |
| IFS='|' response_body=($response) # convert to array | |
| debug "[verify-token] url: $url | response: $response | curl exit status: $curl_exit_code" | |
| if [ $curl_exit_code -ne 0 ] # something went wrong when running the command, let it pass | |
| then | |
| red "Error running curl" | |
| fi | |
| default_verify_action="$(read_config default_verify_action)" | |
| if [[ $default_verify_action == "disable" ]] | |
| then | |
| debug "Checking if authy service is up." | |
| check_response=`curl --connect-timeout 10 -s "${AUTHY_URL}" -A "${useragent}" -o /dev/null` | |
| check_exit_code=$? | |
| if [[ $check_exit_code == 7 || $check_exit_code == 28 ]] | |
| then | |
| red "Letting user pass through. api.authy.com could not be reached." | |
| run_shell | |
| fi | |
| fi | |
| if [[ "${response_body[1]}" == "200" ]] && [[ "${response_body[0]}" == *\"token\":\"is*valid\"* ]] | |
| then | |
| debug "Two-factor token was accepted." | |
| if [[ ! $AUTHY_TOKEN ]] | |
| then | |
| banner_text=$(read_config banner) | |
| if [[ $? == $OK ]]; | |
| then | |
| green $banner_text | |
| fi | |
| fi | |
| if [[ $mode != "test" ]] | |
| then | |
| run_shell | |
| else | |
| debug "Test was successful" | |
| exit $OK | |
| fi | |
| else | |
| red "Invalid token. try again" | |
| fi | |
| return $FAIL # fail by default | |
| } | |
| function request_sms() { | |
| authy_id="$(escape_number $1)" | |
| url="$AUTHY_URL/protected/json/sms/${authy_id}?api_key=${AUTHY_API_KEY}&force=true" | |
| useragent="$(user_agent)" | |
| response=`curl --connect-timeout 10 -A "${useragent}" "${url}" 2>/dev/null` | |
| debug "[request sms] url: $url | response: $response | curl exit stats: $?" | |
| if [[ $response == *success*sent* ]] | |
| then | |
| green "SMS message was sent" | |
| elif [[ $response == *not*enabled* ]] | |
| then | |
| yellow "SMS is not enabled on Sandbox accounts. You need to upgrade to a Starter account at least." | |
| else | |
| red "Message couldn't be sent: $response" | |
| fi | |
| } | |
| # if this methods returns $OK the user is logged in. | |
| function ask_token_and_log_user_in() { | |
| mode="$(escape_input $1)" | |
| authy_id="$(escape_number $2)" | |
| if [[ $authy_id == "" ]] | |
| then | |
| authy_id="$(find_authy_id)" | |
| fi | |
| if [[ $authy_id == "" && $mode == "test" ]] #user is not using authy, let it go | |
| then | |
| red "Cannot find authy id for $USER in $CONFIG_FILE" | |
| red "You have to enable it using 'authy-ssh enable'" | |
| exit $FAIL | |
| fi | |
| if [[ $authy_id == "" ]] | |
| then | |
| debug "User is not protected with authy." | |
| run_shell | |
| fi | |
| times=3 | |
| if [[ $AUTHY_TOKEN ]] # env var | |
| then | |
| times=1 | |
| fi | |
| for i in `$SEQ 1 $times` | |
| do | |
| authy_token="$(escape_number $AUTHY_TOKEN)" | |
| if [[ ! $AUTHY_TOKEN ]] | |
| then | |
| echo -n "Authy Token (type 'sms' to request a SMS token): " | |
| authy_token="$(read_input)" | |
| fi | |
| if [ $? -ne 0 ] | |
| then | |
| debug "Timeout on Authy Token read." | |
| exit $? | |
| fi | |
| case $authy_token in | |
| sms) request_sms "${authy_id}" ;; | |
| *) | |
| authy_token="$(escape_number $authy_token)" | |
| login "${mode}" "${authy_token}" "${authy_id}" | |
| ;; | |
| esac | |
| done | |
| return $FAIL | |
| } | |
| function update_authy() { | |
| temp_file="$(mktemp -t tmpXXXXXXXXX)" | |
| curl "${UPSTREAM_URL}" -o "$temp_file" | |
| chmod 755 "$temp_file" | |
| echo -n "Do you want to overwrite ${COMMAND} (y/n)? " | |
| read opt | |
| if [[ "$opt" == "y" ]] | |
| then | |
| mv "${temp_file}" "${COMMAND}" | |
| green "Now type authy-ssh test to verify everything is working." | |
| else | |
| rm "${temp_file}" | |
| yellow "Authy SSH was not updated." | |
| fi | |
| } | |
| require_curl | |
| # get the absolute path to the command | |
| cd `dirname $0` | |
| COMMAND="$PWD/`basename $0`" | |
| cd - >/dev/null | |
| case $1 in | |
| install) | |
| check_dependencies | |
| install_authy "$0" "$2" | |
| ;; | |
| update) | |
| check_dependencies | |
| require_root | |
| update_authy | |
| ;; | |
| uninstall) | |
| require_root | |
| uninstall_authy | |
| ;; | |
| test|check) | |
| check_config_file | |
| check_dependencies | |
| AUTHY_API_KEY="$(read_config api_key)" | |
| check_api_key || exit | |
| ask_token_and_log_user_in "test" | |
| ;; | |
| enable|register) | |
| check_dependencies | |
| require_root | |
| check_config_file "writable" | |
| AUTHY_API_KEY="$(read_config api_key)" | |
| check_api_key | |
| register_user "$2" "$3" "$4" "$5" | |
| ;; | |
| login) | |
| check_config_file | |
| check_dependencies | |
| AUTHY_API_KEY="$(read_config api_key)" | |
| check_api_key | |
| ask_token_and_log_user_in "login" "$2" | |
| ;; | |
| protect) | |
| check_config_file | |
| check_dependencies | |
| AUTHY_API_KEY="$(read_config api_key)" | |
| protect_user "$2" | |
| ;; | |
| version) | |
| echo "Authy SSH v${VERSION}" | |
| exit 0 | |
| ;; | |
| *) | |
| cat <<__EOF__ | |
| Usage: authy-ssh <command> <arguments> | |
| VERSION $VERSION | |
| Available commands: | |
| install | |
| installs Authy SSH in the given directory. This command needs sudo if the directory is not writable. | |
| sudo $0 install /usr/local/bin | |
| update | |
| updates Authy SSH using the main authy-ssh script: | |
| ${UPSTREAM_URL} | |
| uninstall | |
| uninstalls Authy SSH from sshd_config | |
| sudo $0 uninstall | |
| test | |
| tests if the Authy SSH is working correctly | |
| enable | |
| receives a list of arguments needed to register a user. usage: | |
| sudo $0 enable [local-user] [email] [numeric country code] [cellphone] | |
| Example: sudo $0 enable myuser myuser@example.com 1 401-390-9987 | |
| protect | |
| installs authy-ssh for the given user | |
| $0 protect [user] | |
| login | |
| ask a token to the user if it is already registered. | |
| version | |
| prints the Authy SSH version | |
| __EOF__ | |
| ;; | |
| esac |