From f01304df35fcfb4501d32caf47ed44ec09af7274 Mon Sep 17 00:00:00 2001 From: Axel Burri Date: Wed, 2 Sep 2015 19:20:58 +0200 Subject: [PATCH] ssh_filter_btrbk: refactoring/hardening: - switched to bash interpreter - enable fine-grained (--source, --target, ...) capabilities by command-line options - added "--restrict_path" command-line option - added sudo flag - added man-page - print SSH_ORIGINAL_COMMAND in error message --- doc/ssh_filter_btrbk.1 | 97 +++++++++++++++++++++++++++ ssh_filter_btrbk.sh | 144 +++++++++++++++++++++++++++++++++-------- 2 files changed, 214 insertions(+), 27 deletions(-) create mode 100644 doc/ssh_filter_btrbk.1 diff --git a/doc/ssh_filter_btrbk.1 b/doc/ssh_filter_btrbk.1 new file mode 100644 index 0000000..6ecd1bb --- /dev/null +++ b/doc/ssh_filter_btrbk.1 @@ -0,0 +1,97 @@ +.TH "ssh_filter_btrbk" "1" "2015-09-03" "btrbk v0.20.0" "" +.\" disable hyphenation +.nh +.\" disable justification (adjust text to left margin only) +.ad l +.SH NAME +ssh_filter_btrbk.sh \- ssh command filter script for btrbk +.SH SYNOPSIS +.nf +\fBssh_filter_btrbk.sh\fR [\-s|\-\-source] [\-t|\-\-target] [\-d|\-\-delete] [\-i|\-\-info] + [\-p|\-\-restrict\-path ] [\-l|\-\-log] [\-\-sudo] +.fi +.SH DESCRIPTION +\fBssh_filter_btrbk.sh\fR restricts SSH commands to \fIbtrfs\fR +commands used by \fIbtrbk\fR. It examines the SSH_ORIGINAL_COMMAND +environment variable (set by sshd) and executes it only if it matches +commands used by \fIbtrbk\fR. The accepted commands are specified by +the "\-\-source", "\-\-target", "\-\-delete" and "\-\-info" options. +.PP +Note that the following btrfs commands are always allowed: "btrfs +subvolume show", "btrfs subvolume list". +.PP +Example line in /root/.ssh/authorized_keys on a backup target host: +.PP +.RS 4 +.nf +command="ssh_filter_btrbk.sh \-\-target \-\-delete \-\-restrict\-path /mnt/btr_backup" ssh\-rsa AAAAB3NzaC1...hwumXFRQBL btrbk@mydomain.com +.fi +.RE +.SH OPTIONS +.PP +\-s, \-\-source +.RS 4 +Allow commands for backup source: "btrfs subvolume snapshot", "btrfs +send". Equivalent to "\-\-snapshot \-\-send". +.RE +.PP +\-t, \-\-target +.RS 4 +Allow commands for backup target: "btrfs receive". Equivalent to +"\-\-receive". +.RE +.PP +\-d, \-\-delete +.RS 4 +Allow commands for subvolume deletion: "btrfs subvolume delete". This +is used for backup source if \fIsnapshot_preserve_daily\fR is not set +to \[lq]all\[rq], and for backup targets if +\fItarget_preserve_daily\fR is not set to \[lq]all\[rq]. +.RE +.PP +\-i, \-\-info +.RS 4 +Allow informative commands: "btrfs subvolume find-new", "btrfs +filesystem usage". This is used by btrbk \fIinfo\fR and \fIdiff\fR +commands. +.RE +.PP +\-\-snapshot +.RS 4 +Allow btrfs snapshot command: "btrfs subvolume snapshot". +.RE +.PP +\-\-send +.RS 4 +Allow btrfs send command: "btrfs send". +.RE +.PP +\-\-receive +.RS 4 +Allow btrfs receive command: "btrfs receive". +.RE +.PP +\-p, \-\-restrict\-path +.RS 4 +Restrict btrfs commands to . +.RE +.PP +\-l, \-\-log +.RS 4 +Log ACCEPT and REJECT messages to the system log. +.RE +.PP +\-\-sudo +.RS 4 +Call SSH_ORIGINAL_COMMAND using sudo. +.RE +.SH AVAILABILITY +Please refer to the btrbk project page +\fBhttp://www.digint.ch/btrbk/\fR for further +details. +.SH SEE ALSO +.BR btrbk (1), +.BR btrbk.conf (5), +.BR btrfs (1) +.SH AUTHOR +Axel Burri diff --git a/ssh_filter_btrbk.sh b/ssh_filter_btrbk.sh index cb89f16..6547f54 100755 --- a/ssh_filter_btrbk.sh +++ b/ssh_filter_btrbk.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash set -e set -u @@ -6,47 +6,137 @@ set -u export PATH=/sbin:/bin:/usr/sbin:/usr/bin enable_log= -if [ "$#" -ge 1 ] && [ "$1" = "-l" ]; then - enable_log=1 -fi +use_sudo= +restrict_path_list= +allow_list= log_cmd() { - if [ -n "$enable_log" ]; then - logger -p $1 -t ssh_filter_btrbk.sh "$2 (Name: ${LOGNAME:-}; Remote: ${SSH_CLIENT:-}): $SSH_ORIGINAL_COMMAND" + if [[ -n "$enable_log" ]]; then + logger -p $1 -t ssh_filter_btrbk.sh "$2 (Name: ${LOGNAME:-}; Remote: ${SSH_CLIENT:-})${3:+: $3}: $SSH_ORIGINAL_COMMAND" fi } +allow_cmd() +{ + allow_list="${allow_list}|$1" +} + reject_and_die() { - log_cmd "auth.err" "btrbk REJECT" - /bin/echo "ERROR: ssh command rejected" 1>&2 + local reason=$1 + log_cmd "auth.err" "btrbk REJECT" "$reason" + echo "ERROR: ssh_filter_btrbk.sh: ssh command rejected: $reason: $SSH_ORIGINAL_COMMAND" 1>&2 exit 1 } run_cmd() { log_cmd "auth.info" "btrbk ACCEPT" - $SSH_ORIGINAL_COMMAND + $use_sudo $SSH_ORIGINAL_COMMAND } +reject_filtered_cmd() +{ + # note that the backslash is NOT a metacharacter in a POSIX bracket expression! + option_match='-[a-zA-Z-]+' # matches short as well as long options + file_match='[0-9a-zA-Z_@+./-]+' # matches file path (equal to $file_match in btrbk) + + if [[ -n "$restrict_path_list" ]]; then + # match any of restrict_path_list with or without trailing slash, + # or any file/directory (matching file_match) below restrict_path + path_match="(${restrict_path_list})(/|/${file_match})?" + else + # match any absolute file/directory (matching file_match) + path_match="/${file_match}" + fi + + # allow multiple paths (e.g. "btrfs subvolume snapshot ") + btrfs_cmd_match="^(${allow_list})( ${option_match})*( $path_match)+$" + + if [[ ! $SSH_ORIGINAL_COMMAND =~ $btrfs_cmd_match ]] ; then + reject_and_die "disallowed command${restrict_path_list:+ (restrict-path: \"${restrict_path_list//|/\", \"}\")}" + fi +} + + + +allow_cmd "btrfs subvolume show"; # subvolume queries are always allowed +allow_cmd "btrfs subvolume list"; # subvolume queries are always allowed + +while [[ "$#" -ge 1 ]]; do + key="$1" + + case $key in + -l|--log) + enable_log=1 + ;; + + --sudo) + use_sudo="sudo" + ;; + + -p|--restrict-path) + restrict_path_list="${restrict_path_list}|${2%/}" # add to list while removing trailing slash + shift # past argument + ;; + + -s|--source) + allow_cmd "btrfs subvolume snapshot" + allow_cmd "btrfs send" + ;; + + -t|--target) + allow_cmd "btrfs receive" + ;; + + -d|--delete) + allow_cmd "btrfs subvolume delete" + ;; + + -i|--info) + allow_cmd "btrfs subvolume find-new" + allow_cmd "btrfs filesystem usage" + ;; + + --snapshot) + allow_cmd "btrfs subvolume snapshot" + ;; + + --send) + allow_cmd "btrfs send" + ;; + + --receive) + allow_cmd "btrfs receive" + ;; + + *) + echo "ERROR: ssh_filter_btrbk.sh: failed to parse command line option: $key" 1>&2 + exit 1 + ;; + esac + shift +done + +# remove leading "|" on alternation lists +allow_list=${allow_list#\|} +restrict_path_list=${restrict_path_list#\|} + + case "$SSH_ORIGINAL_COMMAND" in - *\$*) reject_and_die ;; - *\&*) reject_and_die ;; - *\(*) reject_and_die ;; - *\{*) reject_and_die ;; - *\;*) reject_and_die ;; - *\<*) reject_and_die ;; - *\>*) reject_and_die ;; - *\`*) reject_and_die ;; - *\|*) reject_and_die ;; - btrfs\ subvolume\ show\ *) run_cmd ;; # mandatory - btrfs\ subvolume\ list\ *) run_cmd ;; # mandatory - btrfs\ subvolume\ snapshot\ *) run_cmd ;; # mandatory if this host is backup source - btrfs\ send\ *) run_cmd ;; # mandatory if this host is backup source - btrfs\ receive\ *) run_cmd ;; # mandatory if this host is backup target - btrfs\ subvolume\ delete\ *) run_cmd ;; # mandatory if scheduling is active - btrfs\ subvolume\ find-new\ *) run_cmd ;; # needed for "btrbk diff" - btrfs\ filesystem\ usage\ *) run_cmd ;; # needed for "btrbk info" - *) reject_and_die ;; + *\$*) reject_and_die "unsafe character" ;; + *\&*) reject_and_die "unsafe character" ;; + *\(*) reject_and_die "unsafe character" ;; + *\{*) reject_and_die "unsafe character" ;; + *\;*) reject_and_die "unsafe character" ;; + *\<*) reject_and_die "unsafe character" ;; + *\>*) reject_and_die "unsafe character" ;; + *\`*) reject_and_die "unsafe character" ;; + *\|*) reject_and_die "unsafe character" ;; + *\.\./*) reject_and_die "directory traversal" ;; + *) + reject_filtered_cmd + run_cmd + ;; esac