The way .ssh/authorized_keys is typically used is not secure. Because using it securely is hard, and dumping in passwordless ssh keys is easy. I spent about 5 hours today locking down my authorized_keys.

rsync

The only easy case is a rsync of a single directory. Follow these good instructions.

If you need to rsync multiple separate directories, it's easy to find several documents involving a validate-rsync.sh. Do not use, it is insecure -- it allows rsync to be run with any parameters. Including parameters that allow the remote system to rsync in a new ~/.ssh/authorized_keys. Oops. (You can probably also trick validate-rsync.sh into running other arbitrary commands.) To be secure, you have to check the rsync parameters against some form of whitelist.

git

Locking down git should be easy. Use git-shell.

Unless you also want to be able to log in to an interactive shell on the same account. To do that, you'll need to generate a separate ssh key for git. Both git servers I use are named git.*, so I set it up like this in ~/.ssh/config on the client:

Host git.*
    IdentityFile ~/.ssh/id_rsa.git

Then on the server, I added the key to authorized_keys, prefixed with this:

command="perl -e 'exec qw(git-shell -c), $ENV{SSH_ORIGINAL_COMMAND}'"

(I also tried the simpler command="git-shell -c $SSH_ORIGINAL_COMMAND"; but it didn't work with alioth's old version of openssh, and I didn't want to worry about exposing SSH_ORIGINAL_COMMAND to the shell.)

Before arriving at that, I took a detour to using gitosis. It can make it impressively easy to manage locked down git over ssh, but you have to fit how it does things. Including repository layout, and a dedicated account. So I spent several hours changing my git repo layout. In the end, I decided gitosis was too complicated and limiting for what I needed, but I do recommend checking it out.

svn

Like git, this is fairly easy, since svn provides svnserve. The svnbook documents how to use it in .authroized_keys. Basically, just use

command="svnserve -t"

A similar approach as used for git can be used here if you want to have a dedicated ssh key that causes svnserve to be run.

d-i daily builds

d-i has a d-i-unpack-helper that can be put in authorized_keys.

unison

Can probably be locked down similarly to rsync, but I haven't tried yet.

duplicity

The only way seems to not use duplicity over ssh at all, and instead use a different transport.

too hard

What if you want to allow locked down git, and a rsync, and maybe unison too, all to the same account? You'll end up with some nasty mismash of all of the above, and there are plenty more things using ssh as a transport that need other techniques to lock them down.

Until this becomes easier, a majority will just dump passwordless ssh keys in ~/.ssh/authorized_keys, creating exploitable trust paths that don't need to exist.

discussion

comment 1

(I also tried the simpler command="git-shell -c $SSH_ORIGINAL_COMMAND"; but it didn't work with alioth's old version of openssh, and I didn't want to worry about exposing SSHORIGINALCOMMAND to the shell.)

I found that command="git shell -c \"$SSH_ORIGINAL_COMMAND\"" works.

command="git shell -c $SSH_ORIGINAL_COMMAND" doesn't work because $SSH_ORIGINAL_COMMAND will contain spaces and git-shell will see too many arguments and throw the error: "fatal: What do you think I am? A shell?".

Comment by svend [myopenid.com]
Limiting shell access to multiple

Since git 1.7.4, you can extend git-shell with custom commands by placing executables in ~/git-shell-commands (see the git-shell man page for details). The user must have read and execute permissions on this directory and (as with all commands) execute permission on the executables. According to user mimrock in answer to his "Custom commands with git-shell" question, these commands only work in interactive mode as of 1.7.10. For earlier and later versions of git, you can use a shell script to run commands in git-shell-commands in non-interactive mode:

#!/bin/bash                                                                     

cmdline=($1)
cmd=$(basename "${cmdline[0]}")

if [ -z "$cmd" ] ; then
    exec git-shell
elif [ -n "$cmd" -a -x ~/git-shell-commands/"$cmd" ] ; then
    ~/git-shell-commands/"$cmd" "${cmdline[@]:1}"
else
    exec git-shell -c "$1"
fi

Use this in place of git-shell in the authorize_keys "command" option. Invocation is basically the same as for git-shell. Assuming the script is named "sshsh", and following svend's example, we have:

command="sshsh \"$SSH_ORIGINAL_COMMAND\"" ...

If you'd rather not require $SSH_ORIGINAL_COMMAND to be quoted, use the following script:

#!/bin/bash                                                                     

cmd=$(basename $1)

if [ -z "$cmd" ] ; then
    exec git-shell
elif [ -n "$cmd" -a -x ~/git-shell-commands/"$cmd" ] ; then
    shift
    ~/git-shell-commands/"$cmd" "$@"
else
    exec git-shell -c "$*"
fi

The authorized_key entry then becomes:

command="sshsh $SSH_ORIGINAL_COMMAND" ...

Any other commands you wish to allow through ssh can be created within ~/git-shell-commands as links or scripts. For example, to allow rsync as well as git:

$ ln -s $(which rsync) ~/git-shell-commands/
Comment by Galen