Gatekeeper Script for SSH

The gatekeeper pattern adds a post-authentication challenge to SSH sessions using ForceCommand. The concept was inspired by a scene in a movie where a system required answering riddles before granting access. While not a substitute for proper multi-factor authentication, it adds a lightweight additional verification step.

If you have already set up SSH key authentication with a passphrase on your key, you have a decent form of multi-factor authentication already. The gatekeeper script layers on one more check by prompting the user with a challenge after they have authenticated.

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 will want to implement your own logic for the questions and answers. I highly recommend using logic that changes periodically but can be deduced remotely by the client.

 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 its permissions to 755 with the owner being root. To make the SSH server pass off control to the Gatekeeper script once its done authenticating a user, use the ForceCommand directive in the SSHd 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 you are done authenticating it will ask for the token. Putting in 'muffins' should get you to your shell, while anything else will kill the connection.