Skip to content

Instantly share code, notes, and snippets.

@dansimau
Last active July 31, 2022 01:22
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dansimau/8803227 to your computer and use it in GitHub Desktop.
Save dansimau/8803227 to your computer and use it in GitHub Desktop.
Tool for logging SSH sessions.

What is sshlog.sh

sshlog.sh logs all your SSH sessions to the specified destination directory so you can search/recall them later.

How to use

  • Copy sshlog.sh to somewhere on your system and make it executable. eg:

     cp sshlog.sh /usr/local/bin/
     chmod +x /usr/local/bin/sshlog.sh
    
  • Edit the SSH_LOG_DEST variable at the top of the script to define where your SSH log files go.

  • Create an alias for the ssh command. Add this to your ~/.bash_profile or ~/.bashrc:

     alias ssh='/usr/local/bin/sshlog.sh'
    
  • You're done.

Notes

  • sshlog.sh works out the hostname of the destination host by reading the parameters you send the SSH command. You can SSH in a variety of ways:

     ssh -o ForwardAgent=yes fred@foo.com
    

    or

     ssh foo -lfred -v
    

    It should be able to cope with this just fine.

  • sshlog.sh gzips the log files when your SSH session ends, to save disk space.

Bugs

Send to: dan@dans.im

#!/bin/bash
#
# Script to automatically record all SSH sessions.
#
# Created 2009-08-06
# Updated 2020-07-06
# dan@dans.im
#
set -euo pipefail
declare -r SSH_LOG_CMD_SSH=${SSH_LOG_CMD_SSH:-"$(which ssh)"}
declare -r SSH_LOG_DEST=${SSH_LOG_DEST:-"$HOME/sshlogs"}
#
# Parses the arguments and outputs the target host, if detected. Otherwise,
# outputs nothing and returns an error.
#
parse_ssh_hostname_from_args() {
local -a args=("$@")
local host
for (( i=0; i<${#args[@]}; i++)); do
case "${args[$i]:0:2}" in
# Known SSH flags
-4|-6|-A|-a|-C|-f|-G|-g|-K|-k|-M|-N|-n|-q|-s|-T|-t|-V|-v*|-X|-x|-Y|-y)
continue
;;
# Known SSH options
-B|-b|-c|-D|-E|-e|-F|-I|-i|-J|-L|-l|-m|-O|-o|-p|-Q|-R|-S|-W|-w)
# Check if the option and value are separated by a space. If
# yes, we can skip the next argument, as it is the option
# value.
if [ -z ${args[$i]:2:1} ]; then
i=$((i+1))
fi
continue
;;
# Unknown flag or option
-*)
return 1
;;
# The first non-flag/non-option value should be the SSH host
*)
host="${args[$i]}"
# Strip the <user>@ component
host="${host#*@}"
echo "$host"
return 0
;;
esac
done
return 1
}
main() {
local -i exitcode
local ssh_host
local ssh_logfile
if [ ! -w "$SSH_LOG_DEST" ]; then
echo "ERROR: $SSH_LOG_DEST is not writable." >&2
exit 1
fi
ssh_host=$(parse_ssh_hostname_from_args "$@") || ssh_host=unknown
ssh_logfile="$SSH_LOG_DEST/$(date '+%Y-%m-%d_%H:%M:%S')_$ssh_host.log"
:> "$ssh_logfile"
$SSH_LOG_CMD_SSH "$@" > >(tee -a "$ssh_logfile") 2> >(tee -a "$ssh_logfile" >&2) \
&& exitcode=$? || exitcode=$?
gzip "$ssh_logfile"
return $exitcode
}
if [ "$0" == "$BASH_SOURCE" ]; then
main "$@"
fi
. "${0%/*}/sshlog.sh"
test_parse_ssh_hostname_from_args() {
[ "$(parse_ssh_hostname_from_args foo -ldan)" == "foo" ] || return 1
[ "$(parse_ssh_hostname_from_args -ldan -v foo)" == "foo" ] || return 1
[ "$(parse_ssh_hostname_from_args -l dan foo -v)" == "foo" ] || return 1
[ "$(parse_ssh_hostname_from_args foo -l dan -v)" == "foo" ] || return 1
[ "$(parse_ssh_hostname_from_args -l dan -v foo)" == "foo" ] || return 1
[ "$(parse_ssh_hostname_from_args dan@foo echo OK)" == "foo" ] || return 1
}
#
# Run specified tests.
#
tests() {
local ret=0
local test_log="/tmp/${0##*/}.test.log"
if [ $# -ne 0 ]; then
tests="$@"
else
tests=$(compgen -A function | grep "test_")
fi
for test in "$tests"; do
:>"$test_log"
echo -n "$test: "
if (set -x; $test) 2>"$test_log"; then
echo -e "\033[1;32mOK\033[0m"
else
echo -e "\033[1;31mFAILED\033[0m"
ret=1
{
echo
echo "Trace:"
echo -e "\033[33m"
cat "$test_log"
echo -e "\033[0m"
} >&2
fi
done
return $ret
}
tests "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment