Skip to content

Instantly share code, notes, and snippets.

@csarn
Last active May 19, 2024 20:21
Show Gist options
  • Save csarn/3084913ba3e78da6c4d511df131c4d5f to your computer and use it in GitHub Desktop.
Save csarn/3084913ba3e78da6c4d511df131c4d5f to your computer and use it in GitHub Desktop.
ZFS pull backup with minimal permissions

Usage

on system that should be backed up

  • put "restrict_commands.sh" in /usr/local/bin and make it executable
  • install ts, lzop and optionally mbuffer
useradd zfsbackup --create-home --system
mkdir /home/zfsbackup/.ssh
zfs allow -u zfsbackup send,hold tank/dataset
echo 'restrict,command="restrict_commands.sh" ssh-ed25519 ...' > /home/zfsbackup/.ssh/authorized_keys
chown zfsbackup:zfsbackup /home/zfsbackup/.ssh -R

on backup server

run cronjob with:

syncoid --no-sync-snap --no-privilege-elevation --sendoptions=Rw zfsbackup@target:tank/dataset tank/dataset
#!/bin/bash
export PATH=/usr/sbin:$PATH
_RE_DATASET=$'[\"\']+[a-z0-9/_]+[\"\']+'
_RE_SNAPSHOT=$'[\"\']+[a-z0-9/_]+[\"\']*@[\"\']*[a-z0-9/_:-]+[\"\']+'
_RE_SIZE=$'[0-9]+[kMG]?'
_WHITELIST=(
-e "exit"
-e "echo -n"
-e "zpool get -o value -H feature@extensible_dataset $_RE_DATASET"
-e "zfs get -H syncoid:sync $_RE_DATASET"
-e "zfs get -Hpd 1 -t snapshot guid,creation $_RE_DATASET"
-e " *zfs send -R -w -nv?P $_RE_SNAPSHOT"
-e " *zfs send -R -w +$_RE_SNAPSHOT \| +lzop *(\| mbuffer +-q -s $_RE_SIZE -m $_RE_SIZE 2>/dev/null)?"
-e " *zfs send -R -w -nv?P +-I $_RE_SNAPSHOT $_RE_SNAPSHOT"
-e " *zfs send -R -w +-I $_RE_SNAPSHOT $_RE_SNAPSHOT \| lzop *(\| mbuffer +-q -s $_RE_SIZE -m $_RE_SIZE 2>/dev/null)?"
-e "command -v (lzop|mbuffer)"
)
## LOG non-whitelisted commands, execute whitelisted
echo "$SSH_ORIGINAL_COMMAND" |\
tee >(egrep -x -v "${_WHITELIST[@]}" \
| ts "non-whitelisted command issued: (client $SSH_CLIENT)" \
| logger -p local0.crit \
) |\
egrep -x "${_WHITELIST[@]}" | bash
@lediur
Copy link

lediur commented Apr 27, 2024

I also encountered the above mysterious lzop: <stdin>: not a lzop file error. I initially tried to work around it by specifying --compress=none, but that resulted in a different cannot receive: failed to read from stream. Turns out this is a bit of a red herring.

/var/log/syslog on the remote host (source of the dataset) revealed that it was catching multiple "non-whitelisted" commands. It turns out that the two _RE_DATASET and _RE_SNAPSHOT regex in the restrict_commands.sh above don't account for capital letters in either the dataset or snapshot, and syncoid was trying to send snapshots that started with TEMP_.

Changing those two lines to:

_RE_DATASET=$'[\"\']+[A-Za-z0-9/_]+[\"\']+'
_RE_SNAPSHOT=$'[\"\']+[A-Za-z0-9/_]+[\"\']*@[\"\']*[A-Za-z0-9/_:-]+[\"\']+'

resolved my issue. I'm now happily pulling snapshots from my primary server to my backup server. @Mikesco3 harder to tell if this resolves your issue specifically, but @alembiq I do see a capital GMT in your log output so you might give it a shot.

@alembiq
Copy link

alembiq commented May 16, 2024

me bad, i never used the script above, I got this error from using syncoid from default package, but I'm getting the error above

should have read the thread more in detail before posting my question :( sorry

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment