SSH with chroot and only working “sftp”, “rsync” (both)

chrootpermissionssftpsshUbuntu

I have two users and one shared folder in my Ubuntu server:

  1. User writer, which has write access to /var/shared. It's an application regularly making file changes in this folder from remote, with an SSH key.

  2. User reader is used by multiple clients with an SSH key, a key they can get without my permission, that's why I need to restrict commands available in this shell.

Question:

I need to restrict commands accessible for the reader user so it can use only sftp and rsync protocols (no standard commands like mkdir, ls, top, …). 
Only directory /var/shared must be readable, and must be a root path,
e.g., no need to cd into it, it's already / in sftp or rsync.

How do I write a shell script so I can apply it with usermod -s for user reader that will give such behavior? I cannot find any samples. How do I make writer also remain "jailed" to /var/share, so paths are same?

Notes:

  1. I have tried sshd_config's Match, ForceCommand internal-sftp and ChrootDirectory directives already. This requires the ChrootDirectory to be owned by root and non-writable (755 or less), and does not support rsync.

  2. I have tried rssh, but it simply doesn't work for directories outside the home directory for the logged in user. So I couldn't chroot users to the same directory with different permissions.

  3. I tried to use command=".." ssh-rsa.... in the authorized_keys file, but didn't get how can I enable behavior which I need, I only check rrsync script from rsync's docs. This method has no chroot feature I need.

Can I have a sample at least for such shells? Is this achievable with scripts?

Bash and C++ (if needed) are welcome. Output of ldd /bin/bash:

linux-vdso.so.1 =>  (0x00007fff7e9d1000)
libtinfo.so.5 => /lib/x86_64-linux-gnu/libtinfo.so.5 (0x00007f79dfd8b000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f79dfb87000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f79df7bd000)
/lib64/ld-linux-x86-64.so.2 (0x000055bd0767c000)

Best Answer

First of all ChrootDirectory must be owned by root and not writable by other users. Thus /var/shared in your case cannot be ChrootDirectory value.

I would recommend to create a directory which would be writable by root only and make /var/shared accessible inside this dir either via Linux bind-mounting or some kind of symlinks workarounds.

If you need restrict sftp or rsync, you need to check SSH_ORIGINAL_COMMAND environment variable on the server with help of a "wrapper" enforced either via ForceCommand or via command in ssh public key, this variable is populated after user is authenticated and contains info what kind of connection clients is going to establish. For sftp it would have sftp-server inside, for rsync it would have rsync, for just ssh session it would have, IIRC, the variable empty, for ssh date it would be date. SSH_ORIGINAL_COMMAND is run under authenticated user on the server!

This could be your start of the wrapper:

#!/usr/bin/env bash

set -eu
set -o pipefail

[[ -z "${SSH_ORIGINAL_COMMAND:-}" ]] && exit 1

case "${SSH_ORIGINAL_COMMAND}" in
    "/usr/libexec/openssh/sftp-server")
        exec /usr/libexec/openssh/sftp-server
        ;;
    "rsync --server"*)
        exec ${SSH_ORIGINAL_COMMAND}
        ;;
    *)
        exit 1
        ;;
esac

As you can see, ssh -v gives you info about what command would be executed by the server. Thus you can change /tmp to something else in your wrapper as well.

$ rsync -a -e "ssh -v" bin localhost:/tmp/ 2>&1 | grep '^debug.*Sending command:'
debug1: Sending command: rsync --server -logDtpre.iLsfxC . /tmp/
Related Question