Bash – Local, timestamped logging of all ssh commands

bashcommand lineopensshshell

How can I keep a local, timestamped record of all remote commands I use in ssh (command-line openssh client started through bash)?

Requirements:

  • Essential:

    • 100% client-side without relying on server logging
    • Configured or installed per-user with logs stored in the user's home directory.
    • Support for distinguishing between multiple simultaneous sessions with various users and hosts.
    • Non-intrusive (no need to activate it each time and does not interfere significantly with using ssh)
  • High priority:

    • Either output is not logged or filtered out as much as possible
    • Either password entries are not logged or the file is encrypted
    • Indicates the actual commands used (after tab/ history completion, backspaces, CTRL+C, etc… have been processed)
  • Nice to have:

    • Also logs commands in chained sessions (commands entered during remote ssh or su <user> sessions)
    • Session start and end should be logged
    • A simple bash-based, non-root solution would be best (perhaps an alias or bash wrapper script for the ssh command?)

My skill level:

  • I am not new to programming, but am still learning bash and the "Linux way", so code samples with brief explanations would be most appreciated.

Possible strategies

  • keylogger — Problem: logs passwords, does not log tab/ history completion (see glenn's answer)
  • screen with scrollback dumping once per second and diff between them to find new scrollback lines — Problem: how can this be implemented in an useful automated way?
  • ssh "$@" | tee >(some_cleaner_function >> $logfile) — Problem: cannot handle multiline commands or history in chained sessions, careful cleanup needed (see my answer)
  • A combination of some of the above

An example

The following SSH session:

user@local:~$ ssh user@remote
Last login: Tue Jun 17 16:34:23 2014 from local
user@remote:~$ cd test
user@remote:~/test$ ls
a  b
user@remote:~/test$ exit

Might result in a log at ~/logs/ssh.log such as:

2014-06-17 16:34:50   [user@remote - start]
2014-06-17 16:34:51   [user@remote] cd test
2014-06-17 16:34:52   [user@remote] ls
2014-06-17 16:34:53   [user@remote] exit
2014-06-17 16:34:53   [user@remote - end]

Or, perhaps a separate log will be created for each session with the command line used to start the session at the top of the file.

Best Answer

I was intrigued by your question. I wasn't originally going to give an answer but I got hooked.

This uses expect and it's really a key logger.

#!/usr/bin/expect -f

proc log {msg} {
    puts $::fh "[timestamp -format {%Y-%m-%d %H:%M:%S}]: $msg"
}

set ssh_host [lindex $argv 0]
set ::fh [open "sshlog.$ssh_host" a]

log "{session starts}"

spawn ssh $ssh_host

interact {
    -re "(.)" {
        set char $interact_out(1,string)
        if {$char eq "\r"} {
            log $keystrokes
            set keystrokes ""
        } else {
            append keystrokes $char
        }
        send -- $char
    }
    eof
}

log "{session ends}"

Notes:

  • it appends to a file with the ssh destination in the name
  • it is a key logger: if you have not set up ssh keys, you get the user's password in the log file
  • it is foiled by tab completion: if the user types uptTab (for the uptime command), you'll get "upt\t" in the log file, not "uptime"
  • it grabs characters in "raw" mode: if the user is a bad typist, you'll get lots of ^? (backspace characters) in the log file.
Related Question