rsync + cron + ssh (rsyncd を立てない編)

著者:佐藤裕介 (nuts@cclub.cc.tut.ac.jp)
バージョン:2005/11/05 06:30:00 +0900 (JST)
このドキュメントは、クリエイティブ・コモンズ・ライセンスの下でライセンスされています。 クリエイティブ・コモンズ・ライセンス

目的と環境条件

このテキストでは、 rsync を使ったリモートバックアップ(遠隔バックアッ プ)の方法、特に ssh と cron を利用して、暗号化された経路を経由しての バックアップを自動的に行うための手順を示します。

ここで ssh の認証には、パスフレーズを空にし、かつ実行できるコマンドを 限定した ssh 鍵ペアを作成し使用します。これにより、 ssh-agent や eychain を利用する方法、ホストベース認証を利用する方法よりも安全なバッ クアップ体制が整えられる……はずです。

なお、このドキュメントの内容の正確さについては無保証です。なんせ、備忘 録みたいなものですので……。また、このドキュメントに書かれていることを 実行した結果直接的および間接的に発生した損害について、私(佐藤裕介)は 何ら責任を負いかねます。

用語の説明

ローカルホスト、ローカルマシン、ローカル
バックアップ「元」のホストです(文中でそのように書くこともあります)。 バックアップすべきデータの入ったマシンで、普段は何らかのサービスを提 供していたり、端末として利用されていたりするはずです。
リモートホスト、リモートマシン、リモート
バックアップ「先」のホストです。バックアップ用マシンとして、普段はほ とんど何もしていないかもしれませんし、別のサービスが動いているかもし れません。ローカルホストが吹っ飛んだときは、このマシンからデータを取 り出してきて、復旧作業に使います。

想定する状況(要求条件、環境制約、機能 (?) 制約)

バックアップシステムに対して、次のような要求がなされているとします。

  • ローカルに保存されているデータをリモートに転送することで、バックアッ プを実現したい(それ以外のケースについては省略)。
  • データの転送は暗号化された経路を経由して行いたい。
  • cron (または類似のプログラム)を使って、バックアップを定時に自動実 行したい。
  • セキュリティはできる限り強固に保ちたい。

追加の要求として、次に挙げるものを想定します。

  • バックアップの際に root アカウントを使わなければならない。
    • バックアップすべきローカルのファイルの中に、読み取りに root 権限が 必要なものがある。
    • ファイルのパーミッション、 uid, gid を保存してバックアップしたい (バックアップファイルの owner が、リモートでバックアップファイル を書き込むために使ったユーザになってしまうのを避けたい)。
  • tcp/22 (ssh) 以外のポートへの通信は行えない。 rsyncd が用いる 873/tcp および 873/udp のパケットが、ローカル−リモート間の経路中で破棄され てしまう可能性がある。

また、次の条件を『満たせない』場合、このドキュメントの方法は利用できま せん。

  • ローカルからリモートへ ssh による login が可能。
  • 両方のホストに rsync がインストールされている。
  • リモートホストの FQDN が変わらない(ダイヤルアップ環境等)。

次に挙げる内容については省略します。書いてくれる人を募集します。

  • このドキュメントでは、ローカルからリモートへ、データを送り付ける形で のバックアップについて述べます。逆に、リモートからローカルのデータを 吸い出す形でバックアップを行うこともできます。そのような場合でも、こ のドキュメントはもしかしたら役に立つかもしれません。

具体的な技術的知識

rsync には ssh と [1] 連携する機能が内蔵されています。

  1. ローカルで起動された rsync は
  2. ssh でリモートへ login し
  3. リモート側でも rsync を起動する。
  4. その後、ローカルの rsync とリモートの rsync とで通信を行い、バックア ップを行う。

このドキュメントの方法では、この機能を利用してバックアップを行います。 これによって、リモートで rsyncd を動かさないでもバックアップが行えま す。また、 ssh によって暗号化された経路を使い、安全にデータを転送するこ とができます。

また、次の点に注意してくださ。

[1]厳密には ssh 以外のリモートシェルプログラム(rsh とか)も使える のですが、実際には ssh を使うことがほとんどのようです。

技術的方針

このドキュメントでは、次のような手法を用いてバックアップを行います。

  • ローカルでパスフレーズが空の ssh の鍵ペアを作成し、その公開鍵をリモー トの ~/.ssh/authorized_keys に登録することで、パスフレーズおよび パスワードの入力を省略する。
  • リモートに登録する公開鍵に command オプションを指定することで、 その公開鍵を使った接続で利用できるコマンドを限定し、対応するローカル の秘密鍵が漏れた場合の被害を抑える。
  • SSH protocol version 2 のみを使用する。
  • ホストベース認証は利用しない [2]
[2]

ホストベース認証よりも上述の方法の方が安全だと判断したためです。 ホストベース認証では、公開鍵に command オプションを指定する ような、秘密鍵を盗まれた場合の被害を低減する方法がありません。 一方、パスフレーズが空の鍵ペアを使う方法では、鍵に from オ プションを付加することで、ホストベース認証と同じような効果(攻 撃にはリモートの名前解決処理に細工が必要)を追加で得ることがで きます。

また、ホストベース認証を行うには、ローカルにある ssh-keysign コマンドが setuid root されていなければなりませ ん。自分が管理者ならいいのですが、そうでない場合はいちいち作業 をお願いしなければならず、面倒です。

作業内容

以下の作業では、バックアップに root アカウントを使う [3] ことを想定し ています。一般ユーザの権限だけで十分だ、という場合は、「root アカウント 向けの準備」の項は飛ばして読んでください。

また、以下の例では次のような環境を想定しています。

ローカルについて:

リモートについて:

[3]root アカウントと同じファイルの読み書き権限を持ったアカウントを 作成して、そちらを使うという方法も考えられますが、セキュリティ 上の目立った利点は考えられません(rsyncd を動かす場合と異なり、 この方法では chroot ができない)。

root アカウント向けの準備

デフォルトでは、 root アカウントは ssh 経由でのログインが許可されていな いはずです。これはセキュリティの観点から見て妥当であると言えますが、こ のままでは rsync でのバックアップもできません。

攻撃された際の被害を最小限に抑えながら rsync でのバックアップを可能にす るため、「root の ssh ログインでは公開鍵に指定したコマンドライン [4] 実行のみ許す」という設定を /etc/ssh/sshd_config ファイルで行います。

リモートの /etc/ssh/sshd_config の PermitRootLogin (デフォルト値 は no )の値に forced-commands-only を指定してください。

[nuts@remote nuts]$ sudo jvim /etc/ssh/sshd_config
Password:
  :
#PermitRootLogin no
PermitRootLogin forced-commands-only
  :
[nuts@remote nuts]$
[4]/root/.ssh/authorized_keys ファイルで command="..." オ プションに指定する。詳しくは後述。

ssh2 鍵ペアの作成・リモートへの転送

次に、バックアップの際に用いる鍵ペアを作成します。まず、ローカルで ssh-keygen コマンドに -N "" を指定して実行し、パスフレーズが空 の ssh2 鍵ペアを作成します。

[nuts@local nuts]$ sudo ssh-keygen -t dsa -N "" -f /root/.ssh/rsync
Generating public/private dsa key pair.
Your identification has been saved in /root/.ssh/rsync.
Your public key has been saved in /root/.ssh/rsync.pub.
The key fingerprint is:
xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx root@local.example.com
[nuts@local nuts]$ 

これでローカルの /root/.ssh/ に鍵ペア rsync (秘密鍵)、 rsync.pub (公開鍵)ができました。

次に、出来上がった公開鍵をリモートの /root/.ssh/authorized_keys に 追加します。既に存在するものを上書きしてはならず、また root アカウント の ssh 接続は上記のように許可されていないので [5] 、次のようにします。

リモートで:

[nuts@remote nuts]$ mkdir -m 700 tmp
[nuts@remote nuts]$ 

ローカルで:

[nuts@local nuts]$ sudo scp /root/.ssh/rsync.pub nuts@remote.example.com:~/tmp
Password: (ローカルのパスワードを入力)
Password: (リモートのパスワードを入力)
rsync.pub            100% |*****************************|   613       00:00
[nuts@local nuts]$ 

リモートで:

[nuts@remote nuts]$ sudo sh -c 'cat ~nuts/tmp/rsync.pub >> /root/.ssh/authorized_keys'
Password:
[nuts@remote nuts]$ rm -rf ~/tmp
[nuts@remote nuts]$
[5]リモートから ssh root@local.example.com 'cat /root/.ssh/rsync.pub' >> /root/.ssh/authorized_keys のように はできない、ということ。一般ユーザであればこっちの方が楽。

実行コマンドを限定した接続のテスト

次に、リモートに転送した公開鍵に command="..." オプションを指定 [6] します。その鍵を使って ssh 接続を行うと、 command="..." 以降に 指定したコマンドが強制的に実行されます。

まず、テストのために /bin/ls を指定してみます。

[nuts@remote nuts]$ sudo jvim /root/.ssh/authorized_keys
Password:
command="/bin/ls" ssh-dss AAA...
  :
[nuts@remote nuts]$ 

設定したら、ローカルから接続してみます。

[nuts@local nuts]$ sudo ssh -i /root/.ssh/rsync root@remote.example.com
.Xauthority     .fonts.cache-1  .k5login        .profile        ref
.bash_history   .gnupg          .lftp           .ssh            supfile.ports
.cshrc          .history        .login          XF86Config.new  tmp
Connection to remote.example.com closed.
[nuts@local nuts]$ 

指定通り /bin/ls が実行されて、すぐに接続が切断されればテストは成 功です。

[6]詳しくは sshd(8) の "AUTHORIZED_KEYS FILE FORMAT" の項を参 照してください。

公開鍵に指定すべきコマンドラインを得る

次に、rsync がリモートで rsync を起動する際のコマンドラインがどのような ものかを調べます。

cron で実行したいコマンドライン [7]-vv オプションを追加したも のを実行します。次のような感じで失敗するはずです。

[nuts@local backup]$ sudo rsync -vv -az -e "ssh -i /root/.ssh/rsync" \
> /home/backup/ root@remote.example.com:/home/backup/
Password:
opening connection using ssh -i /root/.ssh/rsync -l root remote.example.com rsync
 --server -vvulogDtprz . /home/backup/ 
protocol version mismatch - is your shell clean?
(see the rsync man page for an explanation)
rsync error: protocol incompatibility (code 2) at compat.c(69)
[nuts@local backup]$ 

この opening connection using ssh ... の後にある rsync から始 まる部分に注目します。 rsync --server -vvulogDtprz . /home/backup/ です。ここからオプション -vv を取り除いたものを、リモートの /root/.ssh/authorized_keys に指定します。

[nuts@remote nuts]$ sudo jvim /root/.ssh/authorized_keys
Password:
command="rsync --server -ulogDtprz . /home/backup/" ssh-dss AAA...
    :
[nuts@remote nuts]$
[7]それを実行すると、パスワードの入力を要求される以外は、自動化し た際と同じようにバックアップが行われるようなコマンドライン。もち ろんローカルで実行します。

実際に rsync してみる

元々、 rsync は内部で次のような処理を行っているようです。

  1. ローカルで rsync を起動
  2. ssh でリモートへログイン。その際、引数に rsync --server ... を 指定して、リモートで rsync を起動
  3. ローカルの rsync -az ... な rsyncと、リモートの rsync --server ... な rsync とで通信する

前節のような公開鍵を使って rsync を実行すると、次のようになるはずです。

  1. ローカルで rsync を起動
  2. ssh でリモートへログイン、rsync が ssh の引数に指定したコマンドライ ンは無視される
  3. リモートの /root/.ssh/authorized_keys に指定した rsync が起動
  4. ローカルの rsync -az ... な rsyncと、リモートの rsync --server ... な rsync とで通信する

では、これが上手くいくか実際にやってみましょう。なお、次のコマンドライ ンに -vv オプションを指定しているのは、ちゃんと通信が行えているかど うかの確認のためです。別になくても構いません。

[nuts@local nuts]$ sudo rsync -vv -az -e "ssh -i /root/.ssh/rsync" /home/backup/ \
> root@remote.example.com:/home/backup/
Password:
opening connection using ssh -i /root/.ssh/rsync -l root remote.example.com rsync
 --server -vvulogDtprz . /home/backup/ 
   :
(バックアップしているファイルの名前がずらずらと出力される)
   :
total: matches=0  tag_hits=0  false_alarms=0 data=355683537

wrote 311046900 bytes  read 79920 bytes  586478.45 bytes/sec
total size is 355683632  speedup is 1.14
[nuts@local nuts]$

あとはリモートに一般ユーザでログインして、実際にバックアップが成功して いるか確認してください。

セキュリティ上の問題がないか確認

rsync 用の鍵で、指定した以外のコマンドが実行できたりしないか確認します。

[nuts@local nuts]$ sudo ssh -i /root/.ssh/rsync root@remote.example.com
Password:
(Ctrl-C) Connection to remote.example.com closed.
[nuts@local nuts]$ 

ログインした後反応がなければ大丈夫です。Ctrl-C をタイプすれば通信が中 断されます。

cron に設定

全て成功しているようなら、これをローカルの root の crontab に指定すれ ば自動化は完了です。 rsync のオプションから -vv を抜くのを忘れずに。

[nuts@local nuts]$ sudo crontab -e
Password:
  :
0 6 * * wed     /usr/local/bin/rsync -az -e "ssh -i /root/.ssh/rsync" /home/backu
p/ root@remote.example.com:/home/backup/
  :
[nuts@local nuts]$ 

セキュリティを高める

authorized_keys ファイルには、 command="" 以外にもセキュリティ を高めるのに使えるオプションがくつか存在します [8] 。例えば、 from="" オプションを指定することで、指定したホスト以外からの接続・ コマンド実行を拒否することができます。

まとめて指定してしまいましょう。各オプションの間にはカンマを入れ、スペー スを入れてはいけません。

[nuts@remote nuts]$ sudo jvim /root/.ssh/authorized_keys
Password:
command="rsync --server -ulogDtprz . /home/backup/",from="local.example.com",no-p
ort-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-dss AAA...
[nuts@remote nuts]$ 

設定したら、バックアップが行えるかどうかの確認を忘れずに。一度リモート のバックアップの一部を削除して、再度 rsync する等すれば確認できるでしょ う。

[8]詳しくは sshd(8) の "AUTHORIZED_KEYS FILE FORMAT" の項を参 照してください。

発生し得るリスク

次のような攻撃方法(と、それが成功した際の被害)が予想されます。

  1. バックアップの消去

    成立条件
    • リモートの authorized_keys に対応する秘密鍵が盗まれた
    • リモートおよびローカルのホスト名が分かった
    • 次の 2 つのうち 1 つが満たされた
      • リモートの名前解決処理を改竄できた(リゾルバに細工ができた、 リモートが参照している DNS に細工ができた)
      • ローカルに侵入できた
    被害
    • バックアップ先ディレクトリの内容が消去される。バックアップ元に 空のディレクトリを、末尾に "/" を付けて指定して rsync を実行す ることで攻撃する。
  2. バックアップの改竄

    成立条件
    • "1." の条件全てが満たされた
    • バックアップしているファイル名のリストが得られた
    被害
    • バックアップの内容が改竄される。バックアップ先ディレクトリの内 容を、同じ名前だが別の内容のもので置き換える。バックアップファ イルをそのまま復旧に使うと穴ができる、という状況を作り出せる。

また、rsync 自体にセキュリティホールがあった場合は、これを上回る被害が 出る可能性があることもつけ加えておきます。

この文章を書いた動機

ssh を利用した処理を自動化したいという場合、大抵は次のような文章を読む ことになると思います。

「パスフレーズを空にした ssh の鍵ペアを利用する。鍵に command="..." オプションを指定すれば、鍵が盗まれても相手はそこに書かれてることし かできないよ」

これを読んだ私はこう思いました [9]

  1. 普段バックアップをしたいときにキーボードから打ち込んでる rsync -az /var/hoge remote.co.jp:~/backup みたいなコマンドをそ のまま鍵の command= の後に書けばいいんだろう。
  2. でも、 rsync -e ssh で使われる鍵にコマンドが指定されているって どういう意味? rsync で認証にその鍵を使ったら、同期じゃなくて鍵に指 定されたコマンドがリモートで実行されるって意味に見える。
  3. 鍵を使ってリモートで rsync を起動させる、という意味だと考えることも できる。そうならば、リモートからローカルへ rsync が ssh で接続する んだろうけど、そのときの認証ってどうするの?

実際、リモートに置いた公開鍵に 1. のようなコマンドを指定して、ローカル でそれとペアの鍵を使って rsync を実行すると、rsync が ssh でリモートに login し、するとさらに rsync が起動して ssh を起動する、という訳の分か らない状態になってしまいます。

[9]

root の crontab に rsync を仕掛けたい、という場合に見るべき文書 が無いんです。ssh では通常 root の login は禁止されていると。 /etc/ssh/sshd_configPermitRootLogin forced-commands-only と書きなさいと。 cron rsync ssh command authorized_keys forced-commands-only とか、「これは書 いてあるだろう」という単語を並べて Google 先生にお伺いを立てて みても、2ちゃんねるのログとかしか引っ掛かりません。

いろいろ調べて rsync に --server とか --sender とかいう オプションを付けるということが分かったとしても、 rsync(1) にそんなオプションは書いてない、で頭を抱えてしまう。rsync が複 雑なのも原因の一つかもしれません。

更新履歴

2004-12-16
  • 最初のバージョン。
2005-01-20
  • 「動機」は余分なので最後に持ってくる。
  • 文章の細部を推敲。
  • ライセンス表示を http://creativecommons.org/license/ で作ったもの に変更。
  • XHTML 出力の meta や title タグを設定。
  • 整形に所々ミスがあったのを修正。
2005-08-24
  • 分かりにくい日本語、誤字を修正。
  • ライセンスを「帰属 - 同一条件許諾 2.0 日本」から「帰属 2.1 日本」 に変更。
2005-11-05
  • 分かりにくいかな?という日本語を修正。
  • reStructuredText のソースを修正。