This repository has been archived by the owner. It is now read-only.
Permalink
Switch branches/tags
Nothing to show
Find file
Fetching contributors…
Cannot retrieve contributors at this time
executable file 778 lines (642 sloc) 18.2 KB
#!/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