.\" -*- mode: troff; coding: utf-8 -*- .\" Automatically generated by Pod::Man 5.01 (Pod::Simple 3.43) .\" .\" Standard preamble: .\" ======================================================================== .de Sp \" Vertical space (when we can't use .PP) .if t .sp .5v .if n .sp .. .de Vb \" Begin verbatim text .ft CW .nf .ne \\$1 .. .de Ve \" End verbatim text .ft R .fi .. .\" \*(C` and \*(C' are quotes in nroff, nothing in troff, for use with C<>. .ie n \{\ . ds C` "" . ds C' "" 'br\} .el\{\ . ds C` . ds C' 'br\} .\" .\" Escape single quotes in literal strings from groff's Unicode transform. .ie \n(.g .ds Aq \(aq .el .ds Aq ' .\" .\" If the F register is >0, we'll generate index entries on stderr for .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index .\" entries marked with X<> in POD. Of course, you'll have to process the .\" output yourself in some meaningful fashion. .\" .\" Avoid warning from groff about undefined register 'F'. .de IX .. .nr rF 0 .if \n(.g .if rF .nr rF 1 .if (\n(rF:(\n(.g==0)) \{\ . if \nF \{\ . de IX . tm Index:\\$1\t\\n%\t"\\$2" .. . if !\nF==2 \{\ . nr % 0 . nr F 2 . \} . \} .\} .rr rF .\" ======================================================================== .\" .IX Title "SSH-LAST 1" .TH SSH-LAST 1 2024-09-26 SSH-TOOLS "User Commands" .\" For nroff, turn off justification. Always turn off hyphenation; it makes .\" way too many mistakes in technical documents. .if n .ad l .nh .SH NAME ssh\-last \- list last SSH sessions .SH SYNOPSIS .IX Header "SYNOPSIS" .Vb 2 \& ssh\-last [OPTIONS] \& ssh_logs | ssh\-last [OPTIONS] .Ve .SS Options .IX Subsection "Options" .Vb 11 \& \-a show all sessions (show data which is hidden by the \*(Aqignored\*(Aq file) \& \-c colored output (highlight active SSH sessions) \& \-d debug \& \-f force showing fingerprints (no mapping from \*(Aqknown\*(Aq file) \& \-h show this help message \& \-i force showing certificate ids (no mapping from \*(Aqknown\*(Aq file, not together with \-f) \& \-l try to use logfiles instead of journalctl (may be even faster on some systems) \& \-n show host/ip in cleartext (no mapping from \*(Aqknown\*(Aq file) \& \-w show only active SSH sessions \& \-? show complete manual with more detailed information \& (usually needs perl\-doc installed to work properly) \& \& \-\-version show version information .Ve .SS Examples .IX Subsection "Examples" .Vb 4 \& ssh\-last \& ssh\-last \-c | more \& ssh\-last \-c | less \-R # keeps colored output in less \& ssh\-last \-cw \& \& # Logs from yesterday \& LC_TIME=C journalctl _COMM=sshd \-g \*(AqAccepted|Disconnected\*(Aq \-\-since yesterday | ssh\-last \& \& # Logs from three days ago \& LC_TIME=C journalctl _COMM=sshd \-g \*(AqAccepted|Disconnected\*(Aq \-\-since \-3d \-\-until \-2d | ssh\-last \& \& # Logs from the last hour \& LC_TIME=C journalctl _COMM=sshd \-g \*(AqAccepted|Disconnected\*(Aq \-\-since \-1h | ssh\-last \& \& # Logs until a specific date \& LC_TIME=C journalctl _COMM=sshd \-g \*(AqAccepted|Disconnected\*(Aq \-\-until "2022\-03\-12 07:00:00" | ssh\-last \& \& # From logfiles (order must be from oldest to newest) \& zgrep \-hE \*(AqAccepted|Disconnected\*(Aq auth.log.2.gz auth.log.1 auth.log | ssh\-last \& zgrep \-hE \*(AqAccepted|Disconnected\*(Aq $(ls /var/log/auth.log* \-\-sort=time \-\-reverse) | ssh\-last \& zgrep \-hE \*(AqAccepted|Disconnected\*(Aq $(ls /var/log/messages* \-\-sort=time \-\-reverse) | ssh\-last \& zgrep \-hE \*(AqAccepted|Disconnected\*(Aq $(ls /var/log/secure* \-\-sort=time \-\-reverse) | ssh\-last .Ve .SH DESCRIPTION .IX Header "DESCRIPTION" ssh-last is like last but for SSH sessions .SS "Output Flags" .IX Subsection "Output Flags" .Vb 9 \& +\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-+ \& | | \& | AUTH_ID | \& | | \& | (C) sshd authorized login via (c)ertificate | \& | (K) sshd authorized login via public (k)ey | \& | (?) sshd authorized login via some other type (password, pam) | \& | | \& +\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-+ .Ve .SS Algorithm .IX Subsection "Algorithm" .Vb 1 \& Milling through sshd logs in chronological order: \& \& 1) Finding login (Accepted) and logout (Disconnected) lines. \& 2) Storing info from the lines like username, auth_type, fingerprint, ... \& 3) Using the used network port to check for active sessions \& and piecing together old sessions by remembering logged network ports \& 4) Using mainly /etc/os\-release to adapt for different systems \& which differ in logfile names, logging patterns, etc... .Ve .SH FILES .IX Header "FILES" .SS Ignored .IX Subsection "Ignored" .Vb 3 \& /etc/ssh\-tools/ssh\-last/ignored \& ~/.config/ssh\-tools/ssh\-last/ignored \& ./ignored \& \& These data will be hidden in output unless forced with \-a option \& \& +\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-+ \& |# Fingerprints | \& | | \& |SHA256:ElgyEn5xPe4VlK5jJkqauRdAKNRHdh2tGHfo0m9/IwW Jenkins | \& |SHA256:5xPe4JkqaElKNRHGHfxPe4RdAKdh2tlK5AKNRHn5xK5 foo # comment | \& |SHA256:nmKL5s7/fs45312nvjhFSRTREa44r2hfgJHJG54353R bar@gmx.de | \& | | \& |# Hosts | \& | | \& |127.0.0.1 localhost # local ssh logins | \& |192.168.1.50 nas # more comments | \& |webserver # alias from the \*(Aqknown\*(Aq file | \& | | \& |# Cert IDs | \& | | \& |user1@company.com | \& |user2@company.com with some info | \& |user3@company.com with some info # and a comment | \& | | \& |# Users | \& | | \& |git # gitlab | \& +\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-+ .Ve .SS Known .IX Subsection "Known" .Vb 3 \& /etc/ssh\-tools/ssh\-last/known \& ~/.config/ssh\-tools/ssh\-last/known \& ./known \& \& For these keys the mapped value will be shown instead of its key, \& unless forced with \-f (fingerprints) and \-n (hosts) \& or \-i (certificate ids) option \& \& +\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-+ \& |# Fingerprints | \& | | \& |SHA256:WwI/9m0ofHGt2hdHRNKAdRuaqkJj5KlV4ePx5nEyglE Sven Wick | \& |SHA256:xyk5ZZZWZKnmKL5mYdk8Poy5eds7/CD/JEwqykMnlQQ root@n40l # comment | \& |SHA256:G7h9i5+NDU72Ae40gCkxyvDz/8BH+KETw7sXHCYr5w0 sven.wick@gmx.de | \& | | \& |# Hosts | \& | | \& |127.0.0.1 localhost # local ssh logins | \& |192.168.1.50 nas # more comments | \& |192.168.50.100 webserver | \& | | \& |# Cert IDs | \& | | \& |user1@company.com vaporup | \& +\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-+ .Ve .SH "BUGS AND LIMITATIONS" .IX Header "BUGS AND LIMITATIONS" .SS JumpHosts .IX Subsection "JumpHosts" .Vb 3 \& Using a JumpHost with ProxyCommand oder ProxyJump, \& may often result in an unclean disconnect with nothing logged, \& so LOGOUT and DURATION can not be displayed. .Ve .SS "Unprivileged users" .IX Subsection "Unprivileged users" .Vb 1 \& If possible, run ssh\-last as root or via sudo \& \& 1) Logfiles and systemd\*(Aqs journal usually can\*(Aqt be read by a normal user \& 2) ssh\-last \-w works only reliably as root, \& since ss and netstat do not show process info when invoked as normal user \& 3) ssh\-last tries to map the fingerprint from a user\*(Aqs authorized_keys file \& but users usually are not allowed to look into each others files .Ve .SS "OS Upgrades" .IX Subsection "OS Upgrades" .Vb 5 \& If you do an in\-place upgrade like dist\-upgrade on Debian/Ubuntu, \& depending on the version difference, \& it can happen that sshd logs differently from that point on \& and you may have a mix of logs in new and old format \& which results in ssh\-last showing only the latest ones correctly .Ve .SS "Log inconsistency" .IX Subsection "Log inconsistency" .Vb 5 \& I have seen cases where some sshd "Disconnect" log messages \& were missing in systemd\*(Aqs journal but existed in /var/log/auth.log. \& So, if ssh\-last is not showing a logout and duration \& but the log lines exist in the logfile, check if the log message \& really reached systemd\*(Aqs journal since ssh\-last defaults to journald .Ve .SH NOTES .IX Header "NOTES" .SS "Helper Scripts" .IX Subsection "Helper Scripts" .Vb 2 \& For convenience you can create little wrapper scripts like the following \& which avoids parsing too many logs by limiting the data only to the last week \& \& my\-ssh\-last \& +\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-+ \& | #!/usr/bin/env bash | \& | | \& | LC_TIME=C journalctl _COMM=sshd \-\-since \-1week \e | \& | | grep \-E \*(AqAccepted|Disconnected\*(Aq \e | \& | | ssh\-last "$@" | \& | | \& +\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-+ .Ve .SH "SEE ALSO" .IX Header "SEE ALSO" \&\fBssh\-keyinfo\fR\|(1), \fBssh\-certinfo\fR\|(1) .SH AUTHOR .IX Header "AUTHOR" Sven Wick