Secure,
Automated File Distribution
Tim Maletic
There are countless ways to send a file from one host to another.
But what if you want your systems to do it without your intervention?
There are still many answers: use the Berkeley notion of trust (implemented
via the r-commands and hosts.equiv or .rhosts files),
embed clear-text passwords into scripts or pre-load them into memory,
or use anonymous authentication. But what if you want to do it securely?
And what if you want to do it efficiently?
I recently ran into this problem when I wanted to push a set of
files to all the UNIX systems at my site. Sys Admin has presented
solutions to this type of problem before, but none have sufficiently
tackled the security issues. Jim McKinstry is rather explicit about
the security shortcomings of his file replication strategy, which
includes use of SUID programs, embedded clear-text passwords, and
the r-commands (Sys Admin, February 1998), while Robert Blader's
techniques focus on physically separated networks (Sys Admin,
October 1999). Michael Watson recently presented a brief introduction
to using SSH for automated file distribution (Sys Admin,
February 2001). However, Watson neglects the possibility of using
of public-key authentication, leaving his strategy open to the age-old
problems of automated password authentication (see Libes).
In my situation, the files to distribute were the master-configuration
files for a UID 0 process that runs on every host -- the solution
had to be highly paranoid. To prevent a rogue server from masquerading
as the true master server, I decided that some form of cryptographic,
host-to-host authentication was required. Symmetric key cryptography,
with a shared secret key on each host, is risky because the compromise
of one host breaks the entire system. An asymmetric, public key
system would alleviate this threat. The Secure Shell protocol was
a good fit -- it is widely ported and resides in user space
(as opposed to most IPSec implementations). I didn't want to
get into kernel modifications for my entire site. SSH, however,
turned out to be only part of the solution.
The Ingredients
I considered a number of options, including rdist, NFS,
Coda, ftp, SSL, IPSec, and stunnel, but none of them
satisfied all of my requirements. The solution I settled on uses
Rsync (http://rsync.samba.org) to connect to a chrooted,
unprivileged account via OpenSSH (http://www.openssh.com).
An Rsync daemon can serve files from a chrooted directory, but it
can't do strong host-to-host authentication. scp, part
of the SSH suite, can do strong authentication, but its performance
doesn't scale because it has no built-in mechanism for copying
only the differences between source and target files. Rsync can
use SSH for its transport layer, but SSH requires a shell account
on the SSH server. My breakthrough came when I finally read the
contrib/README file in the OpenSSH distribution -- Ricardo
Cerqueira has contributed a patch for chrooting SSH accounts. I
have since learned that this functionality is built into the SSH
product from SSH Communications Security, Inc. (http://www.ssh.com).
With these ingredients, we can create a highly secure architecture
for automated file distribution. Master copies of the files live
under the home directory of a specially-created, unprivileged account
on a SSH server. I'll call this user "ssync". Slave
copies of the files can reside on any other SSH-capable system that
can hit port 22 (the registered SSH port) of the server, even if
it crosses an untrusted network. Client systems run a regularly
scheduled Rsync command to update the slave files. For the following,
let's assume the client-side Rsync runs as root, because we
want to preserve the ownership of arbitrary master files (but it
can run as any user that can write the slave files to their destination
on the client). These Rsync sessions connect over SSH to the server
as the ssync user, and the SSH daemon is configured to chroot()
to ssync's home directory. The chroot() imprisons the
ssync user in his home directory, effectively changing that directory
into the root directory, and is an extra precaution in the event
that a client system becomes compromised. (Several Sys Admin
articles have covered chroot in various contexts. See "Securing
Apache" by Kyle Dent, May 1999, for an introduction to chroot.)
Public keys are distributed during the initial installation to allow
for password-less logins. And since Rsync only transfers the differences
of changed files, these sessions can run frequently, relative to
the amount of data to sync and its rate of change.
Mix Well
I will later analyze the security of this model in some detail.
In the meantime, let's look at the practical side of putting
these pieces together. We'll build a network consisting of
the hosts "sol", "mercury", "venus",
..., "pluto". sol will run sshd and host the master
copies of the files under the home directory of the ssync user.
mercury, venus, and the rest of the planets will run Rsync over
SSH as root to connect to the ssync account on sol.
You'll need SSH and Rsync on all the planets, while sol will
need an SSH server, and statically linked versions of both Rsync
and a shell for the chrooted environment. Our examples will use
OpenSSH v2.3.0, and Rsync v2.4.6. OpenSSH requires OpenSSL (http://www.openssl.org)
and Zlib (http://www.info-zip.org/pub/infozip/zlib) for their
cryptographic and compression libraries, respectively.
OpenSSH, OpenSSL, and Zlib build easily on a wide variety of platforms.
See their documentation for details. (I've had a hard time
with other applications finding OpenSSL if it is installed in a
custom location, so you may save some frustration by letting it
use its preferred /usr/local/ssl.) If you are new to the
Secure Shell, familiarize yourself with the client's and server's
copious options and get a copy of SSH, The Secure Shell: The
Definitive Guide, by Barrett and Silverman. The default client
and server configurations in OpenSSH are sufficient for our purposes,
but I recommend setting:
Protocol 2
PermitRootLogin no
in sol's sshd_config file. The first option disables support
for any clients running less than SSH Protocol Version 2. SSH Protocol
2 improves upon its earlier incarnations in several respects, and
should be required where possible. The second option modifies the
default PermitRootLogin behavior, which may bypass your /etc/securetty
configuration (especially if you use OpenSSH's src/contrib/sshd.pam.generic,
which leaves out a reference to pam_securetty.so). After all,
you should never log in directly as root anyway. I also recommend
modifying each client's ssh_config to enable StrictHostKeyChecking.
This will prevent an ssh client from connecting to a host whose
private key has changed, unless the client explicitly overrides this
safety feature at the command line.
Fire up sshd on sol and test logging in with the ssh
client from the planets. If you've enabled StrictHostKeyChecking
by default, you'll have to manually exchange keys, or temporarily
urn it off with the -o StrictHostKeyChecking=no option to
the ssh client. If you run into problems, remember to try
running sshd in debug mode (-d) and the client in
verbose mode (-v). Once the installation is complete, you'll
need to create key pairs for root on each of the clients:
root@venus# ssh-keygen -d -P ""
This will create a DSA key pair with an unencrypted private key in
the default locations ~/.ssh/id_dsa and id_dsa.pub.
An encrypted private key requires a passphrase for each use. This
is obviously more secure, but hard to automate. We'll have to
rely on filesystem protections for our private keys -- more on
this later.
Create the ssync user on sol. Its /etc/passwd entry should
look something like:
ssync:x:111:99::/usr/local/ssync:/bin/sh
For our example, the master copies of the distribution files will
live under /usr/local/ssync on sol, so this becomes ssync's
home directory. Create that directory, as well as /usr/local/ssync/.ssh/.
Now concatenate each planet's id_dsa.pub file and place
the result in ssync's ~/.ssh/authorized_keys2 file on
sol. This should be sufficient for passwordless logins to the ssync
account. Test it from a planet; you should see something like:
root@jupiter# ssh ssync@sol "uname -n; whoami"
sol
ssync
root@jupiter#
You can lock this new account now; you won't be using the password
anyway.
The final twist for the SSH configuration is forcing sshd
to chroot() the ssync user. Apparently, this can be done
through the use of the "ChrootUser" configuration directive
in SCS, Inc.'s SSH implementation. With OpenSSH, you need to
apply a contributed patch to src/session.c. The patch, unfortunately,
is slightly out of date, but it is simple enough that you'll
find it easy to insert the changes into the newer session.c.
After fixing session.c, rerun the make and copy the
resulting sshd into its production directory. Stop and restart
sshd.
Your new sshd will chroot() a user when it encounters
the magic token "/./" in the sixth field of their
/etc/passwd entry (the home directory). So modify the ssync account
on sol with a password entry such as:
ssync:x:111:99::/usr/local/share/./:/.ssh/ash.static
The directory to the left of the "." in the sixth
field is ssync's real home directory, and the directory to the
right of the "." is ssync's home directory relative
to the chroot. We'll try to keep the special configuration
files as contained as possible by keeping ssync's statically
linked shell in its ~/.ssh directory, along with its authorized_keys2
and environment files. From the perspective of the real root, our
directory should look like:
root@sol: /usr/local/ssync >ls -al
total 28
drwxr-xr-x 7 root root 4096 Nov 29 09:20 .
drwxr-xr-x 19 root root 4096 Feb 20 12:59 ..
dr-x------ 2 ssync root 4096 Jan 17 10:58 .ssh
drwxr-xr-x 2 root root 4096 Feb 9 09:42 bin
drwxr-xr-x 4 root root 4096 Jan 13 09:29 etc
drwxr-xr-x 2 root root 4096 Nov 6 16:04 lib
drwxr-xr-x 4 root root 4096 Nov 29 09:20 platform
root@sol: /usr/local/ssync >ls -al .ssh/
total 2064
dr-x------ 2 ssync root 4096 Jan 17 10:58 .
drwxr-xr-x 7 root root 4096 Nov 29 09:20 ..
-r-xr-xr-x 1 root root 275556 Nov 8 13:08 ash.static
-rw-r--r-- 1 root root 12044 Jan 17 10:58 authorized_keys2
-rw-r--r-- 1 root root 11 Nov 8 13:08 environment
-r-xr-xr-x 1 root root 1801225 Nov 8 13:08 rsync
root@sol: /usr/local/ssync >cat .ssh/environment
PATH=/.ssh
root@sol: /usr/local/ssync >
We use the environment file (documented in the ssh(1) man pages)
to set ssync's $PATH so that it can find its shell, a
statically linked version of the simple ASH shell that I found installed,
by default, on my Red Hat 6.2 system.
To get the above to work, I discovered that sshd and ssync
must agree as to the full path to ssync's shell. Because I
didn't want a copy of ash.static getting mixed up with
the bulk of my distribution directory, I made the change at the
real root: I created a .ssh directory under the real /,
and copied ash.static there.
With that cheap hack out of the way, we're now ready for
testing:
root@neptune: / >ssh ssync@sol
Last login: Sat Feb 01 20:01:49 2001 from uranus.example.net
$ pwd
Cannot exec /bin/pwd
$ ls
ls: not found
Oh yeah, all we have to work with is the static ash shell. What can
we do with shell built-ins?
$ echo *
bin etc lib platform
$ echo .*
. .. .ssh
$ cd ..
$ echo *
bin etc lib platform
Great! We can't cd out of /usr/local/ssync.
$ cd bin
$ echo foo > test
cannot create test: permission denied
And our file modes and owners forbid write-access to the ssync user.
$ exit
Connection to sol closed.
root@neptune: / >
Now that is a limited environment. But it is enough for Rsync.
Fetch the Rsync sources from rsync.samba.org. Build and
install on the planets, as per the traditional:
user@saturn$ cd /usr/local/src/rsync-2.4.6
user@saturn$ ./configure ; make
user@saturn$ /bin/su
root@saturn# make install
(See the src/README file for details.) For sol, however, skip
the make install, and modify the configuration step for static
linking:
user@sol$ LDFLAGS="-static" ./configure ; make
Then copy the resulting Rsync binary to ssync's ~/.ssh
directory.
Serve with Lime Twist
We're finally ready for a real test. On the planets, create
the destination directory. Let's make it the same as the master
copy: /usr/local/ssync. Now we should be able to run Rsync
to retrieve the first batch of files -- the contents of /usr/local/ssync/.ssh.
root@mars# rsync -avz --delete --rsh=ssh ssync@sol:/.ssh /usr/local/ssync
receiving file list ... done
.ssh/
.ssh/ash.static
.ssh/authorized_keys2
.ssh/environment
.ssh/rsync
.ssh/
wrote 80 bytes read 678888 bytes 271587.20 bytes/sec
total size is 2088836 speedup is 3.08
root@mars#
(If you run into problems at this stage, try removing the chroot
restriction from the ssync account to isolate the problem.) See the
rsync(1) man pages to become familiar with its many options.
Above, we're using the following options:
-a -- Archive mode (recurse, preserve modes, owners,
etc.)
-v -- Verbose (just for testing)
-z -- Compress
--delete -- Delete files on target that aren't on source
--rsh -- Path to ssh client
The "-a" and "--delete" options
will ensure that the slave directories will exactly replicate the
master. This kind of configuration is useful when you want the master
to be completely authoritative for the content of the slaves. For
example, this could be used to simulate a push of configuration
files to all of your hosts. It's only a simulated push, because
really each slave system would be regularly Rsyncing to the master.
I've had success using cron to schedule an Rsync script that
runs every five minutes. To prevent hitting the SSH server all at
once, the script sleeps for a pseudo-random amount of time between
1 and 180 seconds before launching the Rsync.
This technique also has applications to static Web content distribution.
First, public Web sites could pull their static content from a trusted
intranet system. This would save Web authors publishing to or authoring
on vulnerable, external hosts. It would also provide an automated
update of the site from a trusted copy in the event of defacement
(unless they root your box, in which case they'll most likely
disable your cron scripts!). Second, such an arrangement fits naturally
into Web-clustering strategies, where content synchronization is
already a problem. We could sync a 10-node cluster as easily as
a single host.
Don't Drink and Drive
The proposed file distribution strategy demands a high price in
initial configuration, but it pays big dividends on security. Let's
think about some possible attack scenarios.
First of all, anonymous users on separate systems (i.e., neither
sol nor one of the planets) will have no access to our files, because
they can't authenticate to the SSH server. Because we've
locked the ssync account, password authentication isn't an
option. Authenticating, therefore, requires a copy of a private
DSA key that corresponds to one of the public keys in ssync's
authorized_keys2 file, and those should only be readable
by root@[planet] (or by anyone with access to your backup
tapes, which is a good reason to regularly rotate your SSH keys).
For users with local access to one of the planets, root's
private key is protected by the mode 700 .ssh directory.
(OpenSSH creates that directory mode 700 -- make sure it stays
that way!) While users won't be able to access sol's ssync
account, they may very well be able to access the files you are
distributing. Set their access modes and owners appropriately on
sol, and Rsync's "-a" option will preserve
those settings on the planets.
Version 2 of the SSH protocol will thwart all but the most determined
packet-level attacks. There are currently no known vulnerabilities
(though there are several in protocols 1.x). Passive packet sniffing
will yield no passwords or other data, and active attacks such as
session hijacking are prevented as well. Both DNS and the more difficult
IP spoofing are blocked by SSH's host authentication. If a
rogue server IP spoofs as sol, the planet's SSH clients will,
at the very least, complain loudly that sol's host key has
changed. If this is a concern, configure your SSH clients to refuse
such a connection with the StrictHostKeyChecking option,
and manually initialize your clients' known_hosts2 files.
If an attacker gains root access to one of the planets, they'll
have one of the golden private keys, and will be able to access
the ssync account on sol. However, we've chrooted that account,
and (take another look at the file permissions listed above) the
ssync user has no write-access to any file or directory. All they'll
be able to do with the ssync account is update your file set. (Of
course, the client system is hosed, and you've got serious
problems, but there is no threat to the distribution system as a
whole.)
You may be wondering whether this public-key authentication really
buys us any extra security when we're leaving the private key
unencrypted. How is this more secure than embedded clear-text passwords,
when in either case, the game is over when an attacker gets the
right file? The answer is that public-key thereby restricts access
to SSH clients (assuming we haven't done anything silly, like
allowing .rhosts files). We can also take additional steps,
such as configuring the SSH server to only accept connections from
a fixed set of client IP addresses. Then an attacker won't
be able to authenticate from arbitrary points on the network. (You'll
also get the illusory feeling of safety from knowing how much more
difficult it is to shoulder-surf a public DSA key than a clear-text
password.)
The real risk is a root-level compromise of the master host. The
severity of this risk depends on what you're distributing.
If it is Web content, your Web content is corruptible and no longer
trustworthy. If it is host configuration files, your hosts are corruptible
and no longer trustworthy. In the latter case, you must protect
the master at all costs. Like a Kerberos key server, it should run
the fewest services possible, and those should be secured.
Hangover
The major weakness of the above strategy is the management complexity
when scaling beyond a handful of distribution filesets. Each new
directory structure to replicate will need to be analyzed to determine
whether or not it can use the ssync account on sol. Perhaps the
master files will need to reside on another host, or their security
requirements or filesystem location may dictate using another unprivileged
account. So, each new fileset to distribute may require the configuration
of sshd and the unprivileged account on a new system. Its
scalability within a single fileset, on the other hand, is another
strength of our solution. It should perform well as the number of
files and the number of clients rise.
References
Barrett, Daniel J. and Richard Silverman. SSH, The Secure Shell:
The Definitive Guide.O'Reilly & Associates.
Blader, Robert. "File Transfer and Verification Between Non-Connected
Networks". Sys Admin, October 1999.
Libes, D. "Handling Passwords with Security and Reliability
in Background Processes" Proceedings of the Eighth USENIX System
Administration Conference (LISA VIII), pp. 57-64, San Diego, CA,
September 19-23, 1994, http://www.nist.gov/msidlibrary/doc/ \
libes94d.ps).
McKinstry, Jim. "File Replication". Sys Admin,
February 1998.
Watson, Michael. "Replacing rdist and ftp with scp and Associated
Utilities". Sys Admin, February 2001
Tim Maletic was a doctoral candidate in Philosophy before he
started down the path of true enlightenment. He is now a Senior
UNIX System Administrator for Priority Health, specializing in Information
Security. He can be reached at: tmaletic@alumni.indiana.edu.
|