How I automated Chrooted SFTP user logging

Ian Moroney
3 min readOct 15, 2020

So, I had a challenge.
How do you make a fully hands-off idempotent jailed/chrooted SFTP server, AND get logging of user commands (file uploads, downloads, deletions, ls, dir… that sort of thing) out of the jail… when your users are dynamic virtual users coming from ldaps, and you don’t know the usernames ahead of time (AND you don’t want to manually modify your rsyslog.d configuration file every time you get a new user).

Phew!
Maybe this is a niche solution that nobody wants.
Maybe everyone wants it and doesn’t know it yet?

Are you sick of logging onto your chrooted sftp server and creating a socket for your user just so you can monitor the commands that are being run, and say you have logging in place?

Have you followed a guide such as https://www.the-art-of-web.com/system/sftp-logging-chroot/ which describes in great detail how you set up logging for chrooted environments, only the users are static, and you want dynamic automation?

This article is from me, to you.

The solution is simple, but the protection layers had to be complex.

/etc/rsyslog.d/sftp.conf# log internal-sftp activity to sftp.log
if $programname == 'internal-sftp' then /logs/sftp/user.log
& stop
input(type="imuxsock" Socket="/data/usera/dev/log" CreatePath="on")
input(type="imuxsock" Socket="/data/userb/dev/log" CreatePath="on")

If you’re reading this, the above should be familiar.
Below is how you automate the insertion of those sockets.

First, I needed a way of “detecting” the users in the system. In my case, these are virtual users, so they don’t necessarily end up in /etc/passwd.

I figured that because each user leaves its mark in the form of a home directory inside my jail, i could just list the directory and i’d have my list of users, right?

$ lslost+found  supervisord.log  supervisord.pid usera userb userc

This wasn’t going to be very straightforward.

find . -maxdepth 1 -type d.
./lost+found
./usera
./userb
./userc

Oh dear.

find . -maxdepth 1 -type d -and -not -name 'lost*' -and -not -name '.' | sed "s|./||"usera
userb
userc

That’s more like it.

So now that I have my user list, and i’m storing it in a variable, I want to do a foreach loop to add each user’s socket entry to the rsyslog.d/sftp.conf file.

Once I had that in place, I realised… If I keep running this script, it’s going to add multiple entries for these users over and over again.

Not good.

So, I added an if else statement to my foreach loop.
Bet you’re regretting doing this already.

Once i’d got all that sorted, I wanted to restart the rsyslog service so it would pick up the new logging for that user.
Except that I put it inside the foreach loop, and it restarted after each user got added.

So, now i’m having to set a variable to be true if a user has been added to the rsyslog conf, and if so, restart the service.

I now have this running as a cron job every 5 minutes.

So, to recap, we:

  • Get our list of users
  • For each user we get, Add the user’s logging socket to rsyslog.d/sftp.conf
  • Set a variable to be true, so we can let the service restart statement know that we’ve added a user
  • Restart the service, but only if we’ve added a user.
  • Have this script running every 5 minutes via cron so your user logging needs are fully automated.

Below is what I currently think is an elegant solution to this problem.

#!/bin/bashcd /data
userlist=$(find . -maxdepth 1 -type d -and -not -name 'lost*' -and -not -name '.' | sed "s|./||")
jailpath=/data
file=/etc/rsyslog.d/sftp.conf
restartservice=false
for user in $userlist
do
if grep -q $user "$file";
then echo "File already contains user, skipping"
else
echo "Adding user $user to rsyslog"
echo "input(type=\"imuxsock\" Socket=\"$jailpath/$user/dev/log\" CreatePath=\"on\")" >> $file
eval restartservice=true
fi
done
if $restartservice
then
echo "restarting rsyslog service"
service rsyslog restart
else
echo "service doesn't need restarting"
fi

--

--