If you've already implemented the SSH keys for authentication and have a password on your key then you've already achieved multi-factor authentication to a certain degree.

The gatekeeper script is something that I haven't come across on any other systems that other people administer. Perhaps it's too much trouble for them without much gain. I had the idea for this while watching a James Bond movie.

A Russian systems engineer put riddles on one of his machines that you had to go through in order to access the system, while this alone isn't secure, asking the user random questions after authentication couldn't hurt security. I highly recommend using some logic that changes periodically, but the client can remotely deduce.

First we need to make a script to handle the additional authentication. Please note that the following script is just an example, most of the security is in place but you'll want to implement your own logic for the questions and answers.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#!/bin/sh
# gatekeeper.sh - Post-login access question script

# In the event that a user tries to get crafty and Ctrl-C out of the script
# we'll just kill the connection
trap jail INT
jail() {
  kill -9 $PPID
  exit 0
}

# Once a user logs in, check to see if they just wanted a shell.
# SSH_ORIGINAL_COMMAND will be null (-z) if they did
if [ -z "$SSH_ORIGINAL_COMMAND" ]; then
  # The answer the user needs to know, a function or call to another script
  # could be used to generate this answer based on any number of resources
  # available to the system. This could include querying an internal web
  # script to get a daily password user's of a site have access to (and
  # perhaps are supposed to be checking)
  local CORRECT_ANSWER="muffins"

  # Message displayed to the user before being prompted, I'm going to assume
  # the user knows the question in this case what's the admin's favorite
  # breakfast
  echo -n "Gatekeeper token authentication required: "

  # Get the user's answer. If the answer is correct execute the command.
  # If the answer is wrong, log the attempt and kill the connection.
  while read -s inputline; do
    RESPONSE="$inputline"
    echo

    if [ $CORRECT_ANSWER = "${RESPONSE}" ]; then
      echo "Gatekeeper authentication accepted."
      $SHELL -l
      exit 0
    else
      logger "Gatekeeper: $USER login failed from $SSH_CLIENT"
      kill -9 $PPID
      exit 0
    fi
  done
fi

# This command will bypass the gatekeeper script if the user tries to rsync as
# this script will break rsync. It creates a fresh shell just for good measure.
#if [ `echo $SSH_ORIGINAL_COMMAND | awk '{print $1}'` = rsync ]; then
#  $SHELL -c "$SSH_ORIGINAL_COMMAND"
#  exit 0
#fi

# If a user tried to execute something other than an 'approved' command just
# kill the session. This will prevent SCP and SFTP unless they are configured
# to bypass the script.
kill -9 $PPID
exit 0

Put this script in /etc/ssh/gatekeeper.sh and change it's permissions to 755 with the owner being root. To make the SSH server pass off control to the Gatekeeper script once it's done authenticating a user, we'll use the 'ForceCommand' command in SSHd's config file (/etc/ssh/sshd_config). Add the following line to the end of the config and restart the SSH daemon.

ForceCommand /etc/ssh/gatekeeper.sh

Assuming everything went according to plan, when you SSH into the remote server once your done authenticating it'll ask for the token. Putting in 'muffins' should get you to your shell, while anything else will kill the connection.