Logging sftp commands

Updated -

Using sftp to store data on a file server became a popular and secure way. In the past, there were problems with logging user activity in chrooted environment because of missing files to do so. This was finally solved in RHEL 6 using file descriptor passing and in RHEL 7 this feature is achieved using a privileged monitor. On the next lines, I would like to elaborate on the possibilities.

Logging without chroot

Single file

Basically, if we don't use chroot, we can rely on the default configuration and the only thing needed is to allow logging from sftp-server by adding command-line arguments to the Subsystem sftp line in /etc/ssh/sshd_config:

Subsystem   sftp    /usr/libexec/openssh/sftp-server -l VERBOSE

After restarting sshd and performing sftp session, these lines will appear in /var/log/messages:

Mar  9 09:39:07 localhost sftp-server[1829]: received client version 3
Mar  9 09:39:07 localhost sftp-server[1829]: realpath "."
Mar  9 09:39:09 localhost sftp-server[1829]: lstat name "/root"

Specific file

If you want to achieve logging into a different file, you have to configure rsyslog to direct messages into the other file, for example using the log_facility option, in /etc/ssh/sshd_config:

Subsystem   sftp    /usr/libexec/openssh/sftp-server -l VERBOSE -f LOCAL3

And in /etc/rsyslog.conf:

local3.*                                                /var/log/sftp.log

After restarting sshd and rsyslog, you will find the following logs in /var/log/sftp.log:

Mar  9 09:49:02 localhost sftp-server[1947]: received client version 3
Mar  9 09:49:02 localhost sftp-server[1947]: realpath "."
Mar  9 09:49:04 localhost sftp-server[1947]: lstat name "/root"
  • If you'd like to log the sftp transactions in one specific only then edit the /etc/rsyslog.conf file to have the following lines :-
..snip..
local3.*                        /var/log/sftp.log
&~
*.info;mail.none;authpriv.none;cron.none                /var/log/messages
..snip..

and then restart rsyslog service.

The messages are now logged to /var/log/sftp.log and owing to the presence of '&~' they would be limited to /var/log/sftp.log only.

Logging in chroot

When using chroot, there are basically two possibilities. One is chroot without any support files, which requires logging through a privileged monitor. This is useful if there are many users and no way to have a separate /dev/log socket in every chroot. The other case is to have a /dev/log socket configured, which should be honoured by sshd and has higher priority over logging through the monitor. However, both options require to use the internal-sftp subsystem instead of the executable path like above, because there is no such file in chroot.

RHEL 6 (since RHEL 6.7)

Via monitor

The base release of openssh doesn't have the ability to log from a chrooted environment, if there is no available and configured socket located in /dev/log. In order to enable chroot, we need to modify /etc/ssh/sshd_config in this way:

Subsystem   sftp    internal-sftp -l VERBOSE
Match Group sftponly
    ChrootDirectory /chroots/%u

After restarting sshd and performing sftp session with user from group sftponly, we will get these lines in /var/log/secure (note the process name sshd instead of sftp-server, because the file descriptor is owned by the privileged monitor, not the sftp-server) :

Mar  9 10:04:35 localhost sshd[2159]: received client version 3
Mar  9 10:04:35 localhost sshd[2159]: realpath "."
Mar  9 10:04:36 localhost sshd[2159]: lstat name "/"

This use case doesn't allow us to log into different files, because the log_facility option is honoured only for newly open log descriptors, but this is skipped in this case by the sftp-server.

Via socket in chroot

If we want to log through the /dev/log socket in chroot, we set up /etc/ssh/sshd_config in the same way like in previous point, but we specify rsyslog configuration in /etc/rsyslog.d/sftp.conf, which creates the required socket automatically. The following example is for rsyslog v7 (rsyslog7 package):

input(type="imuxsock" HostName="user" Socket="/chroots/user/dev/log" CreatePath="on")
if $fromhost == 'user' then /var/log/sftp.log
& stop

After restarting sshd and rsyslog, we can get these log entries in /var/log/sftp.log:

Mar  9 10:53:39 user internal-sftp[2475]: received client version 3
Mar  9 10:53:39 user internal-sftp[2475]: realpath "."
Mar  9 10:53:40 user internal-sftp[2475]: lstat name "/"

This setup requires more configuration but gives clean results. Important is to set up SELinux rules for rsyslog, so that it has access to this socket, SELinux rules for sshd (internal-sftp), so that it has access to the /chroots/user/ directory, and SELinux and Linux ACL for the socket directory /chroots/user/dev/ (default is 0700 root owned).

Via socket in chroot, filtering by log facility

Or we can again use log_facility in /etc/ssh/sshd_config to achieve the same results:

Subsystem   sftp    internal-sftp -l VERBOSE -f LOCAL3
Match Group sftponly
    ChrootDirectory /chroots/%u

And in /etc/rsyslog.conf:

input(type="imuxsock" Socket="/chroots/user/dev/log" CreatePath="on")
local3.*                                                /var/log/sftp.log

The logs will end up in /var/log/sftp.log:

Mar  9 12:28:46 rhel6 internal-sftp[4008]: received client version 3
Mar  9 12:28:46 rhel6 internal-sftp[4008]: realpath "."
Mar  9 12:28:47 rhel6 internal-sftp[4008]: lstat name "/"

RHEL 7 (since RHEL 7.1)

Via monitor

Base openssh release in this version is more recent and provides ability to log via a privileged monitor. Simply enough if you just set up internal-sftp and chroot like in the previous example:

Subsystem   sftp    internal-sftp -l VERBOSE
Match Group sftponly
    ChrootDirectory /chroots/%u

And the results will show up in /var/log/secure again:

Mar  9 11:24:04 rhel7 sshd[20327]: received client version 3 [postauth]
Mar  9 11:24:04 rhel7 sshd[20327]: realpath "." [postauth]
Mar  9 11:24:05 rhel7 sshd[20327]: opendir "/" [postauth]

Via socket in chroot

If we want to log into a different file, we can use the same sshd configuration, but we need to adjust /etc/rsyslog.conf:

input(type="imuxsock" HostName="user" Socket="/chroots/user/dev/log" CreatePath="on")
if $fromhost == 'user' then /var/log/sftp.log
& stop

After restarting sshd and rsyslog, sftp messages are logged into /var/log/sftp.log (currently /var/log/messages because of bug #1184402 in rsyslog)

Mar  9 13:04:49 rhel7 internal-sftp[12699]: received client version 3
Mar  9 13:04:49 rhel7 internal-sftp[12699]: realpath "."
Mar  9 13:04:50 rhel7 internal-sftp[12699]: opendir "/"

Via socket in chroot, filtering using log facility

We can also use log_facility in /etc/ssh/sshd_config like in the previous cases:

Subsystem   sftp    internal-sftp -l VERBOSE -f LOCAL3
Match Group sftponly
    ChrootDirectory /chroots/%u

Also we need a socket in chroot, otherwise we can't change the facility for the sftp-server. This is done in /etc/rsyslog.conf:

input(type="imuxsock" Socket="/chroots/user/dev/log" CreatePath="on")
local3.*                                                /var/log/sftp.log

Provided that we take care of the SELinux configuration and rsyslog is able to create a socket and log using it, we will get the logs in /var/log/sftp.log:

Mar 12 09:40:06 rhel7 internal-sftp[2472]: received client version 3
Mar 12 09:40:06 rhel7 internal-sftp[2472]: realpath "."
Mar 12 09:40:07 rhel7 internal-sftp[2472]: opendir "/"

Chroot comments

  • Minimal configuration of access rights for the chroot directory structure can be defined in the following way (where user user is a member of group sftponly):
drwxr-xr-x. 3 root root 17 Jan 20 16:17 /chroots/
drwxr-xr-x. 4 root sftponly 27 Mar 11 17:55 /chroots/user/
drwxr-xr-x. 2 user sftponly  6 Mar  9 11:05 /chroots/user/home
drwxr-xr-x. 2 root root     16 Mar 12 09:39 /chroots/user/dev
srw-rw-rw-. 1 root root 0 Mar 12 09:39 /chroots/user/dev/log
  • To make sure that the user has only sftp access, it is recommended to use ForceCommand internal-sftp with the same arguments as Subsystem sftp option above.
  • RHEL 7 has a bug in rsyslog that prevents socket creation with missing parent directories (you need to create the dev directory in the user's chroot). (bug #1184410)
  • RHEL 7 has a bug in rsyslog that prevents the change of hostname parameter that is used for filtering into a different directory in one example. (bug #1184402)
  • Without SELinux configuration on RHEL 6, the user is not able to list directories in the sftp chroot, because it is forbidden by SELinux.
  • Without SELinux configuration on RHEL 7, rsyslog is unable to create a socket in chroot.

Comments