Home / Alt manpages / rrsync(1)

  • rrsync(1)
  • User command
  • linux

Restrict an SSH Key to rsync with rrsync

Handing someone an SSH key with full shell access just so they can sync one directory is how backup accounts turn into a way in. rrsync fixes that: it forces a key to run rsync only inside a chosen directory, read-only or write-only. The examples use rrsync from rsync 3.2.7, packaged here as 3.2.7-1ubuntu1.5. Allow about fifteen minutes if the SSH key already exists. This changes an account's ~/.ssh/authorized_keys, so keep a separate administrative login open before you test.

1. Check the installed rrsync

Run these ordinary, read-only checks on the server that will receive the SSH connection:

$ command -v rrsync
/usr/bin/rrsync
$ rsync --version | head -1
rsync  version 3.2.7  protocol version 31
$ rrsync -help
usage: rrsync [-ro | -wo] [-munge] [-no-del] [-no-lock] [-help] DIR

Help text can vary slightly with packaging, but the installed command accepts one directory plus the options above. The directory can be relative to the restricted user's home directory or an absolute path; the examples below use a relative one, since that avoids a second set of ownership and traversal assumptions.

Checkpoint

Do not continue until command -v rrsync returns the program you intend to call. Nothing returned? Stop and install or expose the rsync package through your normal system-management process. Never put a guessed path into authorized_keys.

2. Prepare the restricted directory

Choose the account and directory first. In this example the account is backupdrop and the restricted directory is incoming beneath its home directory. Both must already exist before the key is used:

$ getent passwd backupdrop
backupdrop:x:...:/home/backupdrop:/bin/sh
$ ls -ld /home/backupdrop/incoming
drwx------ ... backupdrop backupdrop /home/backupdrop/incoming

The output above is host-specific. What matters is that the account exists, the directory is the one you intended, and the SSH account can read or write it as required. The directory name in the forced command is not a label: incoming means the path beneath the account's home directory, nothing more.

If the directory does not exist, create it with the account's normal administration process. This is state-changing and normally needs elevated privileges:

$ sudo install -d -o backupdrop -g backupdrop -m 0700 /home/backupdrop/incoming

Undo this only when you are certain no data is needed: sudo rmdir /home/backupdrop/incoming removes the directory only if it is empty. Do not reach for a recursive removal as part of this setup.

3. Back up the SSH key file

Before editing the account, make a private backup of its key file, running this as the account owner or with the minimum privilege needed to read it:

$ install -m 0600 /home/backupdrop/.ssh/authorized_keys /home/backupdrop/.ssh/authorized_keys.before-rrsync
$ ls -l /home/backupdrop/.ssh/authorized_keys*

File does not exist yet? Create it following the SSH directory's existing ownership and mode policy rather than copying this command blindly. The backup contains public keys and SSH restrictions: it still reveals access configuration, so keep it readable only by the account owner and administrators.

Checkpoint

Confirm your normal administrator key still appears in the file. A malformed replacement can lock out the restricted account, and a misplaced restriction can hit the wrong key entirely.

4. Add a read-only key

Append one line for the client key that should be allowed to download files. The forced command is the part that invokes rrsync; replace the key material, not the command syntax:

command="rrsync -ro incoming" ssh-ed25519 AAAA_REPLACE_WITH_THE_CLIENT_PUBLIC_KEY client@example

Replace AAAA_REPLACE_WITH_THE_CLIENT_PUBLIC_KEY with the complete public key from the client, including its real type and optional comment. Never paste a private key into this file. -ro permits reading from incoming and implies -no-del and -no-lock, so it also blocks rsync's deletion and removal options.

For a write-only drop location, use a separate key and a separate line:

command="rrsync -wo incoming" ssh-ed25519 AAAA_REPLACE_WITH_A_DIFFERENT_CLIENT_PUBLIC_KEY uploader@example

-wo allows only writing to the directory. Do not assume it is interchangeable with read-only access: test it against the client workflow you actually need. One rrsync key gets one restricted directory. Need several independent directories or module-level configuration? The daemon-over-SSH arrangement in the manpage is a different design for that.

5. Test the forced command through SSH

Test from the client using the matching private key, and use a temporary destination so a successful read test cannot overwrite something useful:

$ mkdir -p /tmp/rrsync-read-test
$ rsync -av -e 'ssh -i /path/to/client_key' [email protected]:incoming/ /tmp/rrsync-read-test/
receiving incremental file list

sent ... bytes  received ... bytes  ... bytes/sec
total size is ...  speedup is ...

The transfer summary is illustrative: filenames and byte counts depend on the directory. What matters is a successful rsync transfer from the intended directory, with no interactive shell along the way. The key's forced command receives rsync's server request, and rrsync validates it before anything runs.

Checkpoint

Verify the client sees only the intended files. A successful transfer does not prove every path spelling is safe, so test a file inside the restricted directory and resist adding a second, broader key while you debug.

6. Verify the write-only boundary

For the -wo key, upload a disposable file to a test directory and check the result on the server:

$ printf '%s\n' 'rrsync write test' > /tmp/rrsync-upload.txt
$ rsync -av -e 'ssh -i /path/to/uploader_key' /tmp/rrsync-upload.txt [email protected]:incoming/
$ ssh -i /path/to/uploader_key [email protected]
rrsync: restricted command rejected
$ rm -f /tmp/rrsync-upload.txt

The final SSH command is expected to fail, because this key is forced to rrsync, not a shell. The exact diagnostic and exit status depend on the SSH and rsync versions. Check the server directory through an administrative account, not by weakening the forced command:

$ sudo ls -l /home/backupdrop/incoming/rrsync-upload.txt
-rw------- ... backupdrop backupdrop ... /home/backupdrop/incoming/rrsync-upload.txt

Warning

Do not use --delete as a test. The read-only configuration already rejects delete and remove options, and destructive synchronisation can remove real data the moment your source selection is wrong.

7. Handle path and shell traps

rrsync validates the paths it receives and rejects --copy-links by default, which stops rsync following a symlink inside the restricted directory out to a file outside it. It also rejects --protect-args (-s) because the server-side script cannot inspect the options that mode hides. Genuinely need protect-args? Use the daemon-over-SSH configuration instead of weakening this restriction.

Do not add -no-lock casually. The normal rrsync lock check stops multiple runs for the same user; the option is mainly useful alongside -munge, which enables rsync's --munge-links on the server side, a deliberate symlink-handling choice rather than a general repair option.

Finally, check the restricted user's login shell. The rrsync manpage warns that bash can run user-controlled startup files before a forced command runs. If that account can edit its own home bashrc files, your forced-command boundary may already be undermined. Switch to a simpler shell such as dash only after checking the account's existing access and your operating system's shell policy; changing a login shell is security-sensitive and can disrupt other access.

8. Recover from a bad key line

If the test fails because of an editing mistake, restore the key file only after confirming the target path and keeping your administrative session open:

$ sudo cp --preserve=mode,ownership,timestamps /home/backupdrop/.ssh/authorized_keys.before-rrsync /home/backupdrop/.ssh/authorized_keys
$ sudo ssh-keygen -lf /home/backupdrop/.ssh/authorized_keys

The fingerprint command may report multiple lines or non-key options, so its output is not a complete configuration test on its own. Reopen the file and compare the restored contents with the backup. Once the corrected line is in place, repeat the read-only transfer test before allowing production traffic.

Done means

  • Versions match. rrsync and rsync are the expected installed 3.2.7 tools.
  • The forced command is precise. One intended directory, one deliberate access mode.
  • The client is boxed in correctly. It can transfer the expected files and cannot open an interactive shell with that key.
  • Both directions are tested. Read-only keys reject deletion; write-only keys are tested with disposable data.
  • Recovery exists. The original authorized_keys file is backed up with a working recovery path.
  • Shell and symlink behaviour are checked. Only then does this count as a real security boundary.