System-wide SSH login notifications with Pushbullet
Dec 21, 2014I recently found about Pushbullet, a service and set of applications which allows you to share notifications and other text messages between your devices. They also provide a convenient API, making it easy to send notifications from scripts or other programs.
In a quest to secure my personal server, I wanted to get a notification for each
SSH login. I found some solutions online, but they weren’t
waterproof (relying on per-user .profile
modifications) or were a bit too
hackish for my taste (actively tail
ing auth.log
).
Solution
It’s essential that any user logging in cannot externally prevent the notification from being sent. The best solution I found is to modify the SSH server configuration, and force execution of a wrapper script which will generate the notification. The ForceCommand option does exactly that:
Forces the execution of the command specified by ForceCommand, ignoring any command supplied by the client and ~/.ssh/rc if present.
Within the wrapper script, we can use the (relatively undocumented)
SSH_CONNECTION
environment variable to get a hold of some details about the
current connection. We will use that to make our notification a bit more
interesting. Finally, we hand control back to the user, spawning the command
asked for (saved in SSH_ORIGINAL_COMMAND
), or the user’s default shell:
#!/bin/sh
SHELL=$(getent passwd $USER | cut -d: -f7)
IP=$(echo $SSH_CONNECTION | cut -d " " -f 1)
HOSTNAME=$(dig -x $IP +short)
if [ -z "${HOSTNAME}" ]; then
HOSTNAME=$IP
fi
LOCALHOST=$(hostname)
/usr/local/bin/pushmessage \
"SSH login on $LOCALHOST" \
"User ${USER} has logged in from ${HOSTNAME}"
${SSH_ORIGINAL_COMMAND-$SHELL}
I saved this script in /usr/local/sbin/ssh-wrapper
, made it executable, and
activated it by adding ForceCommand /usr/local/sbin/ssh-wrapper
to
/etc/ssh/sshd_config
.
Note that my wrapper script makes use of pushmessage
, a small script which
delivers the actual notification:
#!/bin/sh
if [ "$#" != 2 ]; then
echo "Usage: $0 TITLE MESSAGE" >&2
exit 1
fi
APIKEY="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
IDEN="XXXXXXXXXXXXXXXXXXXXXX"
curl https://api.pushbullet.com/api/pushes -u ${APIKEY}: \
--output /dev/null --silent --max-time 5 \
-X POST -d device_iden=${IDEN} \
-d type=note -d title="$1" \
-d body="$2"
The APIKEY
and IDEN
variables are personalised, and contain respectively
your Pushbullet access token and the device identification. The former you can
find on your Pushbullet account page, and
in order to get a suitable device identifier you’ll need to inquire the API:
$ curl --silent -u ${APIKEY}: \
https://api.pushbullet.com/api/devices | python -m json.tool
{
"devices": [
{
...,
"iden": "XXXXXXXXXXXXXXXXXXXXXX"
}
],
"shared_devices": []
}
Limitations
- Slowdown: on a normal day, a
pushmessage
invocation takes about 500 ms. This is a significant slowdown of the — already pretty slow — SSH login. Backgrounding the API call solves this, but can get you spammed. - Too many invocations: the wrapper script is called on each login, even if the
SSH connection was already established (ie. when multiplexing connections
using
ControlMaster
). This is especially noticeable when using tab completion for thescp
command: every<TAB>
will trigger a notification. - Cluttered process tree: the
ssh-wrapper
script keeps on existing in the process hierarchy because it spawns the user shell. It should be possible to avoid this (eg. byexecve
ing the shell instead).