Hatena::ブログ(Diary)

JULYの日記

2013-03-27 あれこれ欲張っているうちに、月日は流れ...

[]セキュアな rsync - 実践

理論編(id:JULY:20111127)を書いてから、既に1年半も経ってしまいましたが*1、ようやく実践編です。

2台のホスト

ホスト
コピーmaster.example.com
コピーslave.example.com

します。SSH は slave.example.com から master.example.com へ接続します。

rsync 用のユーザ作成

双方のホスト上で rsync 用のユーザを作りますこのユーザ普通にログインする必要が無いので、ログインシェルを /sbin/nologin にしておきます(後述しますが、とりあえずこの時点では、ログインシェルは /sbin/nologin としておきます)ホームディレクトリ必要ありません。

あとは基本的には任意ですが、多くのディストリビューションで 500 番以上の UID を一般ユーザに割り当てる習慣があるので、500 番未満が良いでしょう*2

# groupadd -g 480 rsync
# useradd -d / -M -u 480 -s /sbin/nologin -g rsync rsync

sshホストベース認証

これについて書いているサイトは多いので、省略... でも良いのですが、一応、書いてみます

鍵の用意

多くの Linux ディストリビューションでは、最初に sshd が起動された時に生成するようになってます。/etc/ssh の下に、下記のようなファイルがあると思います

秘密鍵公開鍵対応プロトコルバージョン
ssh_host_keyssh_host_key.pubVersion 1
ssh_host_dsa_keyssh_host_dsa_key.pubVersion 2
ssh_host_rsa_keyssh_host_rsa_key.pubVersion 2

今時、SSH1 のプロトコルを使う必要は無いので、「ssh_host_dsa_key、ssh_host_dsa_key.pub」か「ssh_host_rsa_key、ssh_host_rsa_key.pub」のどちらかを使う事になります。違いは公開鍵暗号として、DSA を使うか、RSA を使うか、ということですが、現在は RSA を使うのが一般的です*3

これらの生成済みのファイルは、「このホスト認証してもらうための鍵」です。今回の例では、slave.example.com 側のファイルです。

もし無ければ*4ssh-keygen で生成します。

$ ssh-keygen -f ssh_host_rsa_key -t rsa -N ''
Generating public/private rsa key pair.
Your identification has been saved in ssh_host_rsa_key.
Your public key has been saved in ssh_host_rsa_key.pub.
The key fingerprint is:
......

-f は出力するファイル名で、このファイル名で秘密鍵が生成されます。このファイル名に「.pub」が付いた名前で、対応する公開鍵ファイルが保存されます。-t は公開鍵暗号暗号方式で rsa をして、-N はパスフレーズの指定で、「-N ''」とする事で、パスフレーズ無しの鍵が作られます*5

鍵の配備

今回は、slave.example.com が、master.example.com認証してもらう立場なので、slave.example.comホスト公開鍵を、master.example.comコピーします。

認証する側では、/etc/ssh/ssh_known_hosts ファイル公開鍵の内容を追記します。認証される側で ssh サーバが動いているのであれば、認証する側のホスト上で下記のようにすると、/etc/ssh_known_hosts ファイル公開鍵を追記できます

# ssh-keyscan slave.example.com >>/etc/ssh/ssh_known_hosts
ホストユーザの制限

認証する側の/etc/ssh/shosts.equiv で、許可するホストユーザ名を指定できます。ただし、sshd_config の次の項目を確認する必要があります

IgnoreRhosts
yes にすると、ユーザのホームディレクトリにある .shost ファイルを無視します。これが no になっていると、/etc/ssh/shosts.equiv に記述が無くても、エンドユーザレベルで許可されている可能性があります

今回は master.example.com管理者による制限を行いたいので、IgnoreRhosts は yesしま*6

shosts.equiv の記述自体は簡単で、認証を許可するホスト名とユーザ名を記述します。ユーザ名は省略可能で、その場合記述したホストの全てのユーザを許可しま*7。slave.example.com から master.example.com へ接続するユーザを user1 とすると、shosts.equiv の内容は以下のようになります

slave.example.com user1

しかし、今回の場合

ユーザホストユーザホスト
rsyncslave.example.comrsyncmaster.example.com

という接続になります。shosts.equiv に書くのは、あくまでも、認証してもらう側のホスト上のユーザなので、

slave.example.com rsync

となります

sshd_config の設定

先述の IgnoreRhosts に加えて、残っているのは、そもそもホストベース認証有効にするための設定です。

HostbasedAuthentication
ホストベース認証有効無効を設定します。これと似たものに RhostsRSAAuthentication がありますが、これは SSHプロトコルバージョン 1 用の設定で、プロトコルバージョン 2 で使われるのは、この HostbasedAuthentication です。

ssh_config の設定

ssh クライアント側の設定で必要なのは、下記の2項目です。

HostbasedAuthentication
ホストベース認証有効無効を設定します。ここは sshd_config の時と同様です。
EnableSSHKeysign
ホストベース認証時にクライアントサイドで動く補助プログラム ssh-keysign を有効します。

ssh_config は、Host キーワードを使って、特定の接続先にだけオプション適用する事ができますsshサーバになるホスト名をして、

Host master.example.com
    HostbasedAuthentication yes

とすれば、ホストベース認証を使う相手先を master.example.com だけに絞る事ができます。ただしEnableSSHKeysign は、この Host キーワードによる指定の外側に記述する必要があります

EnableSSHKeysign yes

Host master.example.com
    HostbasedAuthentication yes

sudo の設定

rsync コマンドを cron に設定して実行する側は、

  • 一般ユーザの cron で実行する。
  • 面倒だし、分かりづらくなるので、/etc/cron.daily とかに設定してしまう。

の2つの方法が考えられます後者であれば、rsyncスケジュール起動する側(ここでは、slave.example.com)では、sudo の設定は不要です。

前者であれば、/etc/sudoers で、rsync を実行する時のユーザで、rsyncroot 権限で実行できるようにする必要があります。仮にその時のユーザアカウントrsync-user であれば、

rsync-user localhost=(root) NOPASSWD:/usr/bin/rsync

記述し、後述の requiretty に関する記述を追加した上で、「sudo rsync 〜」と、sudo 経由で rsync を実行します。

逆に、ssh 経由で rsync を呼び出される方は sudo の設定は必須です*8

rsync ALL=(root) NOPASSWD:/usr/bin/rsync

これで、rsyncroot 権限で呼び出せるようにはなったのですが、多くのディストリビューションの /etc/sudoers で、

Defaults    requiretty

という行が入っています。これは、sudo を使ってコマンドを呼び出す際、端末としてログインしている状態である事を要求します。リモート側で実行される sudo rsync は、ログインセッションでは無いので、除外してやる必要があります。ここで、

Defaults    !requiretty

としたり、コメントアウトする例を見かけますが、これだと、全ての sudo の実行で、ログインセッション以外での sudo 実行を許可する事になりますrsync ユーザだけ、ログインセッション以外での実行を許可するためには、

Defaults:rsync  !requiretty

という行を追加します。

rsyncコマンドライン

ポイントは2つです。

通信を、sudo 付きの ssh 経由にする

「-e」オプションssh を指定するのですが、その際、「-e 'sudo -u rsync ssh -l rsync'」と、sudo 経由で、かつ、ユーザ名付きにする必要があります。こうすることで、

という動作になります*9。この時の「-u」で指定するユーザ名は、master.example.com の shosts.equiv で指定したユーザ名と合致している必要があります。今回は、ssh を実行するユーザ(-u rsync)と、接続先のユーザ(-l rsync)が同じなので、「-l rsync」は省略可能です。

リモート側で実行される rsyncsudo 付きにする。

「--rsync-path='sudo rsync'」とします。これを忘れると、リモート側の rsync は「rsync アカウント権限で rsync を実行」する事になり、読み出しに root 権限が必要ファイルが取得できなくなります

全体としては、こんな感じのコマンドラインになります

rsync -a -e 'sudo -u rsync ssh -l rsync' --rsync-path='sudo rsync' master.example.com:コピー元 コピー先

root ユーザでこれを実行する場合は、この手前に sudo を付けます

コピー元の rsync ユーザの制限

実は、動作を確認している中で、ここでつまずきました。目指す rsync の実行は、

  1. slave.example.com 側で rsync を実行。
  2. その rsync が「ssh -l rsync」として ssh を実行。
  3. 繋がれた master.example.com では「sudo rsync 〜」を実行。

となります。冒頭、rsync ユーザログインシェルを「/sbin/nologin」としていますが、そうするとすると、ssh でつながった直後に切断され、3 番目の「sudo rsync 〜」を実行できなくななります

なので、普通の「/bin/bash」などをログインシェルにしてしまえば、これまでの設定で目的が果たせます

... ただ、なんとかしたい。rsync 実行の為だけに用意したアカウントで、slave.example.com から繋いだ時だけとは言え、rsync ユーザに許可されている任意プログラムが実行できる、というのは、どうにも格好悪い。rssh を使えば、scp や sftp、rsync 用に絞ることも可能だけど、今回は sudo 経由で rsync を呼び出すので、これは使えない...。

で、あれこれ調べまわった挙句*10ログインシェル自作しました*11

#!/bin/sh
if [ "$1" != "-c" ]; then
    exit 0
fi

if (expr "$2" : 'sudo  *rsync  *--server  *--sender ' >/dev/null); then
    echo -n "$2" | egrep -q '[^\][;<>]|&&|\|\|'
    if [ $? -eq 0 ]; then
        exit 0
    fi
    /bin/sh "$@"
else
    exit 0
fi

ssh 経由でリモート側で呼び出される rsync コマンドには「--server」というオプション付きで呼び出されます。で、今回のケースのように、リモート側からローカル側へダウンロードする場合、「--sender」というオプションも付きます

これらのオプション付きの rsyncsudo 経由で呼ばれ、かつ、途中に「;」や「&&」「||」といった、更に別のコマンドを呼び出す事が可能なメタ文字や、リダイレクト用の文字が含まれていない場合にのみ、処理を実行できるようにしています

このシェルスクリプトを例えば「/usr/local/bin/rsyncshell」というファイル名で保存し、

usermod -s /usr/local/bin/rsyncshell rsync

といった具合に、ログインシェルに設定すれば完成です。

但し、このログインシェルスクリプト、通常のシェルログインシェルにするよりは制限がかかっている状態になっていますが、

といったところは十分に検証されていません。後者の問題が無ければ、通常のシェルを指定するよりはマシ、といった程度に考えて下さい。

また、あくまでも「マスターからダウンロードする rsync」という前提にしています。というのは、不用意にリモート側の $(HOME)/.bashrc などを書き換えないように、と思うと、ダウンロードする方が安全ではないかと思ったからです。

まとめ

全体をまとめると、こんな感じになります

*1時間がかかった理由は、本文中にもあるログインシェルの問題と、sudo の requiretty をまるごと無効にしないための方法、あと、我が家中途半端IPv6 環境のせい、とか、いろいろありまして...

*2RHEL / CentOS Ver.6 以降、490 番台に、特定のプログラムが使うアカウントが配置されるケースがあるので、ちょっと気を付ける必要があります

*3:OpenSSH の ssh-keygen が DSA の鍵を生成する場合、鍵長が 1024 bit に制限される、というのが、RSA が推奨される理由になってます。DSA 自体は 1024 bit より長い鍵長もあるけど、現状、実際に使えるのは 1024 bit のみ、ということのようです。参照: DSA鍵の鍵長 | dodaの日記 | スラド

*4:sshd が動作しているホストであれば、必ずあるはずです。

*5:この時、生成した秘密鍵を置くパスに関して、どこにどんな名前で保存しても、設定ファイルで指定できそうな気もするのですが、ちょっと分かりませんでした。秘密鍵を読み出すのは、ssh クライアントプログラムから呼び出される ssh-keysign というプログラムなのですが、ssh-keysign の manpage を見ても、固定のパスしか書いてありません。ちょっと謎...

*6:一応、デフォルトyes です。

*7:ただし、root は除く

*8:「ALL」のところをホスト名で制限したいのですが、ssh 経由の時にこの sudoers のホストによる制限は効かないようです。参照: HowTO: Sudoers Configuration - 「Note: This applies to computers running application services. If you connect to a machine via ssh, this does not apply.」

*9:/etc/sudoers に、「root ALL=(ALL) ALL」といった行があることが前提です。普通、入っていると思います

*10:もちろん、rbash や rksh などの Restricted Shell も検討しました。

*11:元ネタは http://www.oreillynet.com/linux/blog/2006/05/restricting_rsync_over_ssh.html

トラックバック - http://d.hatena.ne.jp/JULY/20130327/p1
Connection: close