rrsyncがステキ
必要なディレクトリだけcpioなりでアーカイブして、rsync over SSHで吸い出すことにする。 まだ設定してないけど。
というわけで、この鯖 (さくらのVPS) のクリティカルなデータを自宅のNASにバックアップしましょう、と、ようやく重い腰を上げた次第。で、前々から目をつけてたrrsyncがいざ使ってみたらちょいステキだったので軽く紹介してみようかと。
rrsyncとは何ぞや
かの有名なrsyncに付属のプログラム、というか200行ちょっとのPerlスクリプト。名前の由来はRestricted rsyncの略 (のはず) 。
いざインターネット越しでサーバからデータを吸い出す処理を自動化しようと思うと、これが単純に見えて意外とめんどくさい。
--deleteとかチョー便利だから、やっぱscpとかじゃなくてrsync使いたいよね。- インターネットに出るなら通信を暗号化したいから、やっぱ通信はover SSH一択だよね。
- その場合お決まりのパスフレーズなし鍵ペアで公開鍵認証、なんだけど、シェル開けるのはなんか怖いよね。
- えっ?ログイン時に実行するコマンドを公開鍵に設定できるの?でも rsyncのサーバ側コマンドなんてどう決め打てばいいのさ!?
このモヤモヤをさくっと解決してくれるのが、rrsync。
ざっくり使い方と仕組み
まずはrrsyncをサーバ上のどっかに置く。Debian系のパッケージならたぶん /usr/share/doc/rsync/scripts/rrsync.gz にgzip圧縮されたものが、source tarballなら support/rrsync に生スクリプトがあるはず。
次にrsyncしたいアカウントで普通に鍵ペアを作って、公開鍵認証の設定をする。で、ログインできるのを確認したらサーバ側でおもむろにauthorized_keysの command= にrrsyncを設定する。アクセスを許可するサブディレクトリと、必要なら -ro (read-only) オプションも。
これだけで、そのアカウントの権限でSSHログインする際、許可されたサブディレクトリに対するrsyncアクセス ( -ro オプションが付いていればpullなアクセス) 以外は慈悲なく蹴られるようになる。
仕組みとしては、公開鍵の設定で起動されたrrsyncが本来リクエストされたrsyncのサーバ側コマンドライン (OpenSSHの仕様上、 $SSH_ORIGINAL_COMMAND から取れる) と自分の引数を突き合わせて、問題がなければrsyncをexecするというもの。まぁ言ってしまえば単なるラッパなんだけど、これマジメに自分で書こうと思うとかなりめんどいはず。
まとめ
めんどくさいので細かい設定手順とか書かなかったけど、まぁ実際に自分でrrsyncのソースを読んでみてくだせぇ。大した分量じゃないし、設定方法も書いてあるし。
rrsyncを適切に設定できれば、万一秘密鍵を盗まれても被害を最小限に抑えられます。
# まぁ、そもそも秘密鍵を盗まれるという管理体制が問題アリアリなんじゃ、というツッコミもあるけど、それはまた別のお話。
参考文献
- rrsyncのソースコード
- SSHD(8) のAUTHORIZED_KEYS FILE FORMATセクション
2015-03-07追記
どうも2月の頭くらいからバックアップが止まってた様子。いろいろ調べてみたところこのFIXで、crontabにrsyncのコマンドラインを書くときにリモートパスをより一層ご丁寧にクォートするようになったことが原因らしい。
"user"@remote-host:\""remote-path"\"
つまりこれはFreeNASのエンバグか?と言えばそう簡単な話でもなくて、普通はサーバ側のrsyncもシェル経由で実行されるので、シェルがクォートを外してくれるから何ら問題はない。一方でrrsyncは $SSH_ORIGINAL_COMMAND を自力でバラして解釈するので、結果的にクォートを外さずにrsyncにパスを渡してしまい、
rsync: link_stat ""remote-path"" failed: No such file or directory (2)
とか言われるというわけ。そりゃ、ねーわ。
まぁイレギュラーなことしてるのはこっちですよねー、というわけでrrsyncにクォート外す処理を入れて回避。
--- ./rrsync 2015-03-07 00:37:07+09 1.1
+++ ./rrsync 2015-03-07 00:43:58+09 1.2
@@ -180,6 +180,8 @@
s{^$}{.};
die "$0: do not use .. in any path!\n" if m{(^|/)\\?\.\\?\.(\\?/|$)};
}
+ # Remove quotation from args
+ s{^["'](.*)["']$}{$1} if m{^".*"$} or m{^'.*'$};
push(@args, bsd_glob($_, GLOB_LIMIT|GLOB_NOCHECK|GLOB_BRACE|GLOB_QUOTE));
}
}
セキュリティ的に本当に大丈夫かなこれ‥‥。