88171.net

I stand with Ukraine🇺🇦

rrsync がステキ

必要なディレクトリだけ cpio なりでアーカイブして、 rsync over SSH で吸い出すことにする。 まだ設定してないけど。

88171.net :: FreeNAS 稼働開始

というわけで、この鯖 (さくらの VPS) のクリティカルなデータを自宅の NAS にバックアップしましょう、と、ようやく重い腰を上げた次第。で、前々から目をつけてた rrsync がいざ使ってみたらちょいステキだったので軽く紹介してみようかと。

rrsync とは何ぞや

かの有名な rsync に付属のプログラム、というか 200 行ちょっとの Perl スクリプト。名前の由来は Restricted 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 を適切に設定できれば、万一秘密鍵を盗まれても被害を最小限に抑えられます。

# まぁ、そもそも秘密鍵を盗まれるという管理体制が問題アリアリなんじゃ、というツッコミもあるけど、それはまた別のお話。

参考文献


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));
   }
 }

セキュリティ的に本当に大丈夫かなこれ‥‥。