Logging OpenSSH SFTP Transactions

I just came across a really handy post on David Busby‘s blog: Enable logging in the SFTP subsystem – Oneiroi. From OpenSSH 4.4 on, you can pass arguments to Subsystem calls, and the sftp subsystem supports logging to an aribtrary syslog facility and priority. Simply adding a line like:

Subsystem       sftp    /usr/libexec/openssh/sftp-server -f LOCAL5 -l INFO

and the appropriate lines to your syslog config will give you a handy transfer log like:

Jul 16 09:22:25 hostname sftp-server[2058]: session opened for local user jantman from [A.B.C.D]
Jul 16 09:22:26 hostname sftp-server[2058]: open "/home/jantman/temp/sftp_test" flags WRITE,CREATE,TRUNCATE mode 0666
Jul 16 09:22:45 hostname sftp-server[2058]: close "/home/jantman/temp/sftp_test" bytes read 0 written 1464813
Jul 16 09:23:08 hostname sftp-server[2058]: session closed for local user jantman from [A.B.C.D]
Jul 16 09:27:50 hostname sftp-server[2309]: session opened for local user jantman from [A.B.C.D]
Jul 16 09:27:50 hostname sftp-server[2309]: open "/home/jantman/temp/sftp_test" flags READ mode 0666
Jul 16 09:27:54 hostname sftp-server[2309]: close "/home/jantman/temp/sftp_test" bytes read 1464813 written 0
Jul 16 09:27:54 hostname sftp-server[2309]: session closed for local user jantman from [A.B.C.D]

If you have syslog write these logs to their own file, remember to setup log rotation for them.

Unfortunately, I’m not aware of any way to log SCP file transfers.

Secure rsnapshot backups over the WAN via SSH

Since I moved all of my WAN-facing stuff (mail, web, this blog, svn etc.) to a virtual server with Linode, and just have a desktop at home, it’s no longer practical to use Bacula for backups. Linode manages daily and weekly backups through their backup service, but they’ll only restore a full filesystem at a time. I wanted something that would keep daily and weekly incremental backups long enough that I could find a file changed (or accidentally deleted) a few days or weeks ago. Since I’d be backing up to my desktop at home (which is on a residential dynamic IP connection), the logical solution was something using rsync. Even better than that is the rsnapshot tool, which builds upon rsync and hard links to manage incremental backups with as little disk usage as possible (though I’d certainly recommend excluding log files).

I’m pretty strict about security. Since my home connection has a dynamic IP, things are a bit more complicated – I can’t push from the server, I can’t ACL or firewall the server to just my home IP, and an IPsec VPN would be difficult to accomplish (not to mention add a lot of overhead to big file transfers). So, I opted for a solution that uses SSH key-based authentication, forced comands, and a C wrapper.

The configuration of rsync and rsnapshot is mostly out of the scope of this post. There are plenty of good resources for that, so I’ll just cover the things that won’t be found in most tutorials. Also, I’ll be referring to the remote machine to be backed up as the “remote host” and the local machine which triggers the backup and stores the data as the “local host”.

Local Host Setup – Part I

  1. Choose and create a directory to store your backups in. I have a 1TB external disk mounted at /mnt/backup/, so I chose /mnt/backup/rsnapshot/.
  2. Generate two sets of password-less SSH keys using the ssh-keygen program. One will be used to run the rsync command on the remote host, the other will be used to trigger your pre- and post-backup scripts. Name them accordingly (i.e. “remoteHostname_remoteBackupUsername_cmd” and “remoteHostname_remoteBackupUsername_rsync”). Now, get (scp) the public key for each pair to the remote host.

Remote Host Setup

  1. Ensure that rsync is installed on the host.
  2. Create a user to run the backups. I called this user “rsyncuser”. Create a home directory, and a group for the user. Do not set a password (you don’t want password logins).
  3. Copy the public key files you created above to the user’s ~/.ssh/ directory.
  4. Cat the “remoteHostname_remoteBackupUsername_cmd” public key into the user’s ~/.ssh/authorized_keys file.
  5. Now comes the first fun part. Let’s assume that your pre- and post-backup scripts are /root/bin/rsnapshot-pre.sh and /root/bin/rsnapshot-post.sh, respectively. As root, grab a copy of cmd-wrapper.c (from subversion or at the bottom of this post). Modify for your use – the only thing likely to change is line 38, which ensures it will only run for a member of GID 502. Change this to rsyncuser’s GID. Compile the wrapper with gcc -o cmd-wrapper cmd-wrapper.c. Copy it to rsyncuser’s home directory (/home/rsyncuser), chown root:rsyncuser and chmod 4750. Yes, this sets the SUID bit. The program will now be owned by root, and runnable as root by rsyncuser (or, more specifically, any member of the rsyncuser group).
  6. Open rsyncuser’s .ssh/authorized_keys file in a text editor. At the beginning of the “remoteHostname_remoteBackupUsername_cmd” key line, prepend command="/home/rsyncuser/cmd-wrapper". This sets up SSH forced command (there’s a good overview in O’Reilly’s SSH: The Definitive Guide) so that when this key is used to login, it will directly execute /home/rsyncuser/cmd-wrapper and then exit, without allowing access to anything else.
  7. Add rsyncuser to AllowUsers in /etc/ssh/sshd_config (you do limit user access via SSH, right?) and then reload sshd.
  8. Now, if you SSH to rsyncuser@remoteHost from the local host, using the “_cmd” ssh key and a command of “pre” (i.e. ssh -i /path/to/remoteHostname_remoteBackupUsername_cmd rsyncuser@remoteHost pre), it should execute /root/bin/rsnapshot-pre.sh ad root, and you should see the output locally.
  9. Repeat the above step for the post-backup script (replacing “pre” above with “post”). You should now have your pre- and post-backup scripts working, and triggered remotely. (Note: these steps, and some of the other setup here, is a bit more complex so that it will work better with rsnapshot backups of multiple remote hosts.)
  10. Cat the “remoteHostname_remoteBackupUsername_rsync” public key into the backup user’s ~/.ssh/authorized_keys file.
  11. As root, grab a copy of rsync-wrapper.c (from subversion or at the bottom of this post). Modify for your use – the only thing likely to change is line 38, which ensures it will only run for a member of GID 502 (change this to rsyncuser’s GID), and perhaps the path of or arguments passed to rsync (the wrapper will call /usr/bin/rsync --server --sender -vlogDtprRe.iLsf --numeric-ids . /). Compile the wrapper with gcc -o rsync-wrapper rsync-wrapper.c. Copy it to rsyncuser’s home directory (/home/rsyncuser), chown root:rsyncuser and chmod 4750.
  12. Open rsyncuser’s .ssh/authorized_keys file in a text editor. At the beginning of the “remoteHostname_remoteBackupUsername_rsync” key line, prepend command="/home/rsyncuser/rsync-wrapper". This will run rsync with the arguments specified in rsync-wrapper.c every time this key is used to login.

Local Host Setup – Part II

I use totally separate configs for each host that I backup, to keep things clean and to let me enable, disable, or tweak one remote backup without affecting the others.

  1. Create host-specific pre- and post-backup scripts. I put them in /etc/rsnapshot.d/.
    /etc/rsnapshot.d/pre-remoteHostName.sh:

    #!/bin/bash
     
    # do anything else needed on the local system before a backup
    ssh -i /path/to/remoteHostname_remoteBackupUsername_cmd rsyncuser@remoteHost pre

    /etc/rsnapshot.d/post-remoteHostName.sh:

    #!/bin/bash
     
    # do anything else needed on the local system after a backup
    ssh -i /path/to/remoteHostname_remoteBackupUsername_cmd rsyncuser@remoteHost post
  2. Setup a set of rsync include and exclude files (see man rsync(1), --include-from= and --exclude-from=). I put mine at /etc/rsnapshot.d/rsync-include-remoteHostName.txt and /etc/rsnapshot.d/rsync-exclude-remoteHostName.txt, respectively. (Examples included at the bottom of this post).
  3. Configure rsnapshot. I use a separate config file for each remote host. Copy the default /etc/rsnapshot.conf to /etc/rsnapshot-remoteHostName.conf. The important items are rsync_short_args, rsync_long_args, ssh_args, cmd_preexec, cmd_postexec and backup. Here’s an example of my config file, with comments and blank lines removed:
    config_version  1.2
    snapshot_root   /mnt/backup/rsnapshot/
    cmd_cp          /bin/cp
    cmd_rm          /bin/rm
    cmd_rsync       /usr/bin/rsync
    cmd_ssh         /usr/bin/ssh
    cmd_logger      /bin/logger
    cmd_du          /usr/bin/du
    cmd_rsnapshot_diff      /usr/bin/rsnapshot-diff
    interval        daily   14 # save 14 daily backups
    interval        weekly  6 # save 6 weekly backups
    verbose         2
    loglevel        3
    logfile /var/log/rsnapshot-remoteHostName.log
    lockfile        /var/run/rsnapshot-remoteHostName.pid
    rsync_short_args        -a
    rsync_long_args --delete --numeric-ids --relative --delete-excluded
    ssh_args        -i /path/to/remoteHostname_remoteBackupUsername_rsync
    exclude_file    /etc/rsnapshot.d/rsync-exclude-remoteHostName.txt
    include_file    /etc/rsnapshot.d/rsync-include-remoteHostName.txt
    link_dest       1
    use_lazy_deletes        1
    cmd_preexec     /etc/rsnapshot.d/pre-remoteHostName.sh
    cmd_postexec    /etc/rsnapshot.d/post-remoteHostName.sh
    backup  rsyncuser@remoteHostName:/      remoteHostName/

    The backup line is what tells rsync what to back up (/ on remoteHostName, logging in as rsyncuser), and where to back up to (snapshot_root/remoteHostName/).

  4. Create two scripts that will actually trigger the backups, which I’ll call /root/bin/rsnapshot-daily.sh and /root/bin/rsnapshot-weekly.sh:
    /root/bin/rsnapshot-daily.sh:

    #!/bin/bash
     
    /usr/bin/rsnapshot -c /etc/rsnapshot-remoteHostName.conf daily
    # add other hosts here; note, they'll run in series

    /root/bin/rsnapshot-weekly.sh:

    #!/bin/bash
     
    /usr/bin/rsnapshot -c /etc/rsnapshot-remoteHostName.conf weekly
    # add other hosts here; note, they'll run in series
  5. Add two entries to root’s croontab to run the rsnapshot backups. Adjust the following days and times to your liking:
    0 1 * * Mon /root/bin/rsnapshot-weekly.sh # run the weekly backups every Monday at 01:00
    30 2 * * * /root/bin/rsnapshot-daily.sh # run the daily backups every day at 02:30, which *should* be after the weekly finished on Monday morning
  6. Check, after the next scheduled runs, that everything appears to have run correctly. If you want, you can manually trigger the daily script and watch what happens. If you do this more than once, you should delete the directories it creates, or else rotation will be messed up. If you have issues with rsync, aside from the usual troubleshooting, check that rsync-wrapper.c is calling rsync with the same arguments that rsnapshot is sending. It may be useful to use my print-cmd.sh script in place of the “rsync-wrapper” forced command. This script will simply log the command rsnapshot calls via SSH.

Assuming all of this worked, you should now have a fairly secure SSH-based remotely-triggered backup system. In a follow-up post I provide my Nagios Check Plugin for Rsnapshot Backups.

The referenced scripts, config files, etc. are below:

cmd-wrapper.c:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <strings.h>
#include <stdlib.h>
 
/********************************************
 * Wrapper - Secure Yourself                
 *                                          
 * 2007 - Mike Golvach - eggi@comcast.net   
 * Modified 2012 by Jason Antman <jason@jasonantman.com> <http://www.jasonantman.com>
 *  - configured for use as pre- and post-backup script wrapper
 *                                          
 * USAGE: cmd-wrapper [pre|post]
 *
 * $HeadURL: http://svn.jasonantman.com/misc-scripts/cmd-wrapper.c $
 * $LastChangedRevision: 26 $
 *                                          
 ********************************************/
 
/* Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License */
 
/* Define global variables */
 
int gid;
 
/* main(int argc, char **argv) - main process loop */
 
int main(int argc, char **argv, char **envp)
{
  char *origcmd;
 
  origcmd = getenv("SSH_ORIGINAL_COMMAND");
 
  /* printf ("Original Command:%s\n", origcmd); */
 
  /* Set euid and egid to actual user */
 
  gid = getgid();
  setegid(getgid());
  seteuid(getuid());
 
  /* Confirm user is in GROUP(502) group */
 
  if ( gid != 502 ) {
    printf("User Not Authorized! Exiting...\n");
    exit(1);
  }
 
  /* Check argc count only at this point */
 
  if ( argc != 1 ) {
    printf("Usage: cmd-wrapper [pre|post]\n");
    exit(1);
  }
 
  /* Set uid, gid, euid and egid to root */
 
  setegid(0);
  seteuid(0);
  setgid(0);
  setuid(0);
 
  /* Check argv for proper arguments and run
   * the corresponding script, if invoked.
   */
 
  if ( strncmp(origcmd, "pre", 3) == 0 ) {
    if (execl("/root/bin/rsnapshot-pre.sh", "rsnapshot-pre.sh", NULL) < 0) {
      perror("Execl:");
    }
  } else if ( strncmp(origcmd, "post", 4) == 0 ) {
    if (execl("/root/bin/rsnapshot-post.sh", "rsnapshot-post.sh", NULL) < 0) {
      perror("Execl:");
    }
  } else {
    printf("ERROR: Invalid command: %s\n", origcmd);
    printf("Usage: COMMAND [pre|post]\n");
    exit(1);
  }
  exit(0);
}

rsync-wrapper.c:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <strings.h>
 
/********************************************
 * Wrapper - Secure Yourself                
 *                                          
 * 2007 - Mike Golvach - eggi@comcast.net   
 * Modified 2012 by Jason Antman <jason@jasonantman.com> <http://www.jasonantman.com>
 *  - configured for use as rsync wrapper
 *                                          
 * $HeadURL: http://svn.jasonantman.com/misc-scripts/rsync-wrapper.c $
 * $LastChangedRevision: 26 $
 *                                          
 ********************************************/
 
/* Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License */
 
/* Define global variables */
 
int gid;
 
/* main(int argc, char **argv) - main process loop */
 
int main(int argc, char **argv)
{
 
  /* Set euid and egid to actual user */
 
  gid = getgid();
  setegid(getgid());
  seteuid(getuid());
 
  /* Confirm user is in GROUP(502) group */
 
  if ( gid != 502 ) {
    printf("User Not Authorized! Exiting...\n");
    exit(1);
  }
 
  /* Check argc count only at this point */
 
  if ( argc != 1 ) {
    printf("Usage: rsync-wrapper\n");
    exit(1);
  }
 
  /* Set uid, gid, euid and egid to root */
 
  setegid(0);
  seteuid(0);
  setgid(0);
  setuid(0);
 
  /* Check argv for proper arguments and run
   * the corresponding script, if invoked.
   */
  if (execl("/usr/bin/rsync", "rsync", "--server", "--sender", "-vlogDtprRe.iLsf", "--numeric-ids", ".", "/", NULL) < 0) {
    perror("Execl:");
  }
  exit(0);
}

/etc/rsnapshot.d/rsync-include-remoteHostName.txt:

# Include
+ /dev/console
+ /dev/initctl
+ /dev/null
+ /dev/zero
+ /usr/local/*

/etc/rsnapshot.d/rsync-exclude-remoteHostName.txt:

# Exclude
- /cgroup/*
- /dev/*
- /lib/*
- lost+found/
- /proc/*
- /sys/
- /tmp/
- /var/log/*

HP Prolaint iLO SSH Problems

There’s a known issue with the SSH implementation in the iLO firmware for HP Proliant servers (specifically G2 and G3) and OpenSSH 5.1p1. There was a thread on the OpenSSH developers list that referenced this problem and suggested a solution, but it doesn’t seem to be a sure fix.

This problem is present on my DL360 G2′s which are running the 1.84 2006-05-05 version of the iLO firmware (iLO 1.84 pass9) with the P26 2004.05.01 version of the system firmware. I also see the issue on a DL380G3 running iLO 1.92 2008.04.24 and system firmware P29 2004.09.15. The only way that I can reliably get into the iLO is by SSHing from a box with an older version of SSH, such as 4.2p1.

Most of the things that I could find online referenced unsetting the LANG environment variable:

unset LANG

and then SSHing with agent forwarding disabled:

ssh -a hostname-ilo

Unfortunately this combination doesn’t seem to do it for me.

I happened to stumble by this post to the debian-ssh mailing list, which suggested that shortening the new OpenSSH version string fixed the problem.

I was able to confirm that the version string is, in fact, the sole problem. I downloaded the source of OpenSSH 5.2p1 and, with the following small patch to version.h, managed to get SSH working to the iLO perfectly:

--- openssh-patched/version.h   2009-06-12 00:35:48.000000000 -0400
+++ openssh-5.2p1/version.h     2009-02-22 19:09:26.000000000 -0500
@@ -1,6 +1,6 @@
 /* $OpenBSD: version.h,v 1.55 2009/02/23 00:06:15 djm Exp $ */
 
-#define SSH_VERSION    "OpenSSH"
+#define SSH_VERSION    "OpenSSH_5.2"
 
-#define SSH_PORTABLE   ""
+#define SSH_PORTABLE   "p1"
 #define SSH_RELEASE    SSH_VERSION SSH_PORTABLE

I patched version.h, ran `./configure`, `make`, and then copied the compiled ssh binary to /usr/bin/ilossh, so that my original ssh binary would be intact, and the ilossh binary would be left alone by RPM upgrades.

Centralized Storage via SFTP

For quite a while, I’ve been planning on centralizing a lot of my personal storage (documents, miscellaneous stuff) on one machine at home. The biggest problem that I have is that, while a VPN would be a good solution for my apartment (if I could get IPcop to do VPN between two dynamic IPs), it doesn’t really work for my mobile life. My laptop is often connected to untrusted wireless, and unknown firewall configurations, so VPN isn’t always the best (and definitely not the easiest) option. Given road warrior use, NFS is obviously out of the question.

After a little searching, I found the SSHFS module for FUSE, which allows userspace mounting of a SFTP filesystem. Despite some initial hiccups, I managed to get it setup on two machines – my laptop and a desktop in the apartment. This week I’ll finish working on the rest of the machines – and eventually replace my aging SSH gateway machine (currently a 10-year-old Gateway mini tower) with a Soekris box.

The setup was pretty easy:

  1. Make sure you have public key authentication setup for ssh between the machines, using RSA keys.
  2. Make sure fuse, libfuse, and the related packages are installed.
  3. Install the sshfs package.
  4. Make sure your user is added to the “trusted” group (for OpenSuSE).

After that, just give it a spin, as the user that you want to mount the filesystem as:

sshfs hostname:/path/to/mount /path/to/local/mountpoint

Once that worked pefectly, I added the following to my .bash_profile:

# this handles SSHFS mount of the central-home dir
if [ -a
/path/to/local/mountpoint ]; then
echo "HOSTNAME home is mounted at
/path/to/local/mountpoint"
else
echo "Mounting HOSTNAME home at
/path/to/local/mountpoint..."
hostname:/path/to/mount /path/to/local/mountpoint
fi

Rainy Day Link Updates

Well, today happens to be my 21st birthday. Now, not only can I vote or die for my country, but I can finally buy a drink when a president I didn’t vote for sends me off to war! All kidding aside, almost every year I can remember, it’s either snowed on my birthday, or been a rainy, slushy mess. This year appears to be the latter – an utterly disgusting mix of rain, slushy snow, and little chunks of ice from last night.

I haven’t had much time this week to do anything interesting – it’s been a busy week for class-related stuff, and a bunch of work-related stuff too. So, I guess I’ll just post some interesting links for the past few days…

Sun Microsystems has announced that we’ll be acquiring Innotek, and therefore VirtualBox. This means that Sun’s virtualization products will now reach to the desktop – and that I should give VirtualBox a shot for running OpenSolaris on my laptop. (aside: why is virtualization not in my Firefox spell check yet???)

Researchers at the Pittsburgh Supercomputing Center have come out with a version of SSH / SCP that uses multi-threading, for use in high-bandwidth applications on multi-core systems.

An ITnews article on why Open Source needs better PR.

What? Microsoft sued again? Ars Technica ran an article on the Vista Capable fiasco, with some comments from MS insiders. How is it that such a big company, with such a ubiquitous product, can constantly be sued, have MAJOR screwups, screw over their customers, and still people come back for more?

BBC News – EU Competition Regulators raid Intel offices in Munich.

A CNet article (linked from John M. Willis’ ESM Blog) on the US Treasury upping its content management budget to $28.2 million – no wonder why ww have so much debt, nobody told them about Drupal. From the article – “tens of millions on a $1 million problem”.

Roger Rustad’s Nagios Wiki, liked in a Groundwork blog entry.