PermitUserEnvironment yes
を設定しておく必要があります。要件はこんな感じで。
この鍵はパスなしで作ります。
# ssh-keygen -t ed25519 Enter file in which to save the key (/root/.ssh/id_ed25519): /path/to/id_ed25519 Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /path/to/id_ed25519. Your public key has been saved in /path/to/id_ed25519.pub. : :
こんな感じですね……shell スクリプトはあまり得意じゃないので美しくないかもしれないですが、とりあえずは動きますw
#!/bin/bash ################################################################################ # backup_remote.sh # create @17.04.17 ################################################################################ . /path/to/utils.sh ## # settings # LIST_DIR='/path/to/backup.d' LIST_EXT='.list' DEST_DIR='/path/to/dest' LOG_DIR='/var/log/rsync' SSH_USER='collector' SSH_PRIVKEY='/path/to/id_ed25519' RSYNC_CMD='/usr/bin/rsync' RSYNC_DEFOPTS='--delete --exclude lost+found' ## # check variables. # if [ ! -f "${RSYNC_CMD}" ] || [ ! -x "${RSYNC_CMD}" ]; then error "'${RSYNC_CMD} is not executable." exit 1 fi if [ ! -d "${LIST_DIR}" ] || [ ! -x "${LIST_DIR}" ]; then error "Cannot access to '${LIST_DIR}'." exit 1 fi if [ ! -f "${SSH_PRIVKEY}" ] || [ ! -r "${SSH_PRIVKEY}" ]; then error "Cannot access to '${SSH_PRIVKEY}'." exit 1 fi if [ ! -d "${DEST_DIR}" ] || [ ! -w ${DEST_DIR} ]; then error "Cannot access to '${DEST_DIR}'." exit 1 fi if [ ! -d "${LOG_DIR}" ] || [ ! -w ${LOG_DIR} ]; then error "Cannot access to '${LOG_DIR}'." exit 1 fi ## # check argument # if [ -z "$1" ]; then error "usage: ${0} <hostname>" exit 1 fi HOST=${1} if [ "${HOST}" = 'local' ]; then error "Not acceptable hostname '${HOST}'." exit 1 fi FILE="${LIST_DIR}/${HOST}${LIST_EXT}" if [ ! -f "${FILE}" ] && [ ! -r "${FILE}" ]; then error "Cannot access to '${FILE}'." exit 1 fi ## # start main. # LOG_FILE="${LOG_DIR}/${HOST}.$(date +%Y-%m-%d-%H.%M.%S).log" RET=0 info "Backup start." info "Rsync log is ${LOG_FILE}" while read DIR OPTS; do # skip blank line. if [[ "${DIR}" =~ ^\s*$ ]]; then continue fi # skip comment line. if [[ "${DIR}" =~ ^\s*# ]]; then continue fi if [[ "${DIR}" =~ [^/]$ ]]; then warn "Directory path should ends with '/'." DIR="${DIR}/" fi SRC="${HOST}:${DIR}" DST="${DEST_DIR}/${HOST}${DIR}" # create directory tree /bin/mkdir -p ${DST} if [ $? -ne 0 ]; then error "Cannot create directory: '${DST}'." RET=2 continue fi info "Start sync from '${SRC}' to '${DST}'." ${RSYNC_CMD} -avvr \ --rsh "ssh -l ${SSH_USER} -i ${SSH_PRIVKEY} -o strictHostKeyChecking=no" \ --rsync-path="sudo rsync" \ ${RSYNC_DEFOPTS} ${OPTS} \ ${SRC} ${DST} \ >> ${LOG_FILE} 2>&1 if [ $? -ne 0 ]; then error "Failed to rsync from '${SRC}' to '${DST}': $!" RET=3 else info "Succeeded to rsync from '${SRC}' to '${DST}'." fi done < ${FILE} info "Backup finished." exit ${RET}
utils.sh は、info
やら warn
やらの function を定義しているだけなので割愛。<hostname> に local
を使えないようにしているのは、もう1つこのバックアップサーバー内でローカル rsync する shell スクリプトがありまして、そのリストファイルが 'local.list' で作ってあるからです。ちょっと書式が違うので……思いっきり俺環のはなしです。
で、この shell スクリプトはこんな風に使います。
# ./backup.sh <hostname>
${LIST_DIR}
に設定したディレクトリには <hostname>.list というテキストをこんな感じで置いておきます。
/var/log/nginx/ --exclude=error_log --bwlimit=8192 /var/log/portage/ /var/log/hogefuga/ --bwlimit=8192
${IFS}
で区切って1番目にはバックアップを取得するディレクトリを。2番目以降は rsync のオプションです。指定できるのはディレクトリのみで、ファイルを指定することはできません。
バックアップサーバー側はこれで準備okですね。
ここまでやる必要はないですね……っていう程度には作り込みます。
ssh で接続してきて、極力 rsync 以外のことはできないようなユーザーを作成します。
useradd --create-home --uid 1999 --groups users --shell /bin/rbash --comment rsyncBackupCollector collector
--uid
を指定しているのは、複数あるバックアップ対象のサーバーの全てで同じ uid を使うようにするためです。こうしておけば、以降の記事で作る /home/collector/ 以下の環境を tar で固めて展開できたりするので、多少ですが省力化になります。
この時点で --shell
で rbash を指定しているので、これだけでもできることはかなり制限されているのですが……趣味とノリと勢いでもっともっとこのユーザー collector でできることを絞っていきます。
collector ユーザー専用の bin ディレクトリを作成します。
# mkdir /home/collector/bin
作成した専用 bin に、collector ユーザーが使用できるコマンドのシンボリックリンクを作成します。
# ln -sf /usr/bin/sudo /home/collector/bin/sudo # ln -sf /usr/bin/rsync /home/collector/bin/rsync # ls -l /home/collector/bin total 0 lrwxrwxrwx 1 root root 14 Apr 17 12:03 rsync -> /usr/bin/rsync lrwxrwxrwx 1 root root 13 Apr 17 12:03 sudo -> /usr/bin/sudo
/home/collector/.bashrc を編集して、コマンドサーチパスを作成した専用 bin ディレクトリに限定します。
PATH=/home/collector/bin
ついでに .bashrc と .bash_profile を collector 自身で変更できないように owner を変更してしまいます。
# chown root:root /home/collector/.bashrc /home/collector/.bash_profile
ちなみに rbash では、環境変数 ${PATH}
はリードオンリーになっていて変更できません。
# ssh -i /path/to/id_ed25519 collector@server 'PATH=/usr/bin' rbash: PATH: readonly variable
また cd
は禁止されている上に、コマンドに /
を含めることも許されないため、フルパスでコマンドを打つこともできません。
# ssh -i /path/to/id_ed25519 collector@server 'cd /bin;ls' rbash: line 0: cd: restricted rbash: ls: command not found # ssh -i /path/to/id_ed25519 collector@server '/bin/ls' rbash: /bin/ls: restricted: cannot specify `/' in command names
/home/collector/.ssh/authorized_keys に、バックアップサーバーから接続するための公開鍵を追加して、色々な制限を加えます。
from="192.168.ip.addr",environment="PATH=/home/collector/bin",no-agent-forwarding,no-port-forwarding,no-user-rc,no-X11-forwarding,no-pty ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKu83AydT6QgO7x/4VnuMSpTaaHvr6V8CZpsYXQQg7l+ collector
このへんの設定は man sshd
に書いてありますね。from=
で接続元を制限しています。
また environment=
1)でコマンドサーチパスを限定しています。これは、通常の ssh ログインの場合は .bashrc の PATH=/home/collector/bin
の設定が効くのですが、ssh collector@host 'echo $PATH'
のように直接コマンド実行をする場合はログインシェルを取らないので .bashrc 等が適用されない(/etc/env.d/あたりで決定するデフォルトが適用される)ためです。
あと no-
と付くものはとりあえず全部設定しておきます。
ちなみにこの時点で1度 no-pty を外して、ssh でログインしてみるのも良いかもしれません。結構絶望的になにもできない環境になっていますが……やっぱり完璧ではなくどこかに穴があるんでしょうねw
collector ユーザーの権限では触れないファイル等もあるので、rsync は sudo コマンドを経由して root 権限で実行させます。そのために sudoers にこんな設定を入れます。
collector ALL=(root) NOPASSWD:/home/collector/bin/rsync
ここまでできたら、バックアップサーバー側から秘密鍵を使って ssh 経由で sudo rsync が起動できるかどうかを確認してみます。
# ssh -i /path/to/id_ed25519 collector@server 'sudo rsync --version' rsync version 3.1.2 protocol version 31 Copyright (C) 1996-2015 by Andrew Tridgell, Wayne Davison, and others. Web site: http://rsync.samba.org/ Capabilities: 64-bit files, 64-bit inums, 64-bit timestamps, 64-bit long ints, socketpairs, hardlinks, symlinks, IPv6, batchfiles, inplace, append, ACLs, xattrs, iconv, symtimes, prealloc rsync comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it under certain conditions. See the GNU General Public Licence for details.
こんな感じで rsync のバージョン情報が表示されれば、バックアップ対象となるサーバーの設定は完了です。
というわけで、バックアップサーバー側で /path/to/backup.sh <hostname>
を好きなだけ並べた shell スクリプトを作って、そいつをキックする backup.service やら backup.timer やらを作って完成です。うちの環境では、リモート rsync で引っ張ってきたログをさらにバックアップサーバー上で別のディスクに rsync するローカルバックアップ用の shell スクリプトと、さらにリモートとローカルのバックアップを順次実行、リターンコードを見て logger
コマンドで syslog に投げるような上位 shell スクリプトを作って、そいつを timer 起動しています。
……ムダに手間かけて作りこんじゃいました。
PermitUserEnvironment yes
を設定しておく必要があります。