NFS | ||
---|---|---|
HOME |
NFS ファイル共有システムは、良い意味でも悪い意味でも「古い」。UNIX 系 OS にはほぼ必ず実装されているので、LINUX マシン同士でファイルを簡単にやりとりすることができる。しかし一方、メールの SMTP プロトコルのように、他人 (他PC) が信じられる時代の性善説で成り立っているプロトコルである点は否めず、セキュリティを確保しながら利用するのは難しい。出来る限りのセキュリティ対策を施し、ローカルネットワーク内のみでの使用に限定し、使う時だけ起動させる姿勢で用いるべきだ。
※ 当ページは NFSv4 が使い物になる前、主に nfs-utils 1.0.4 での検証を元に書いたものだ。NFSv4 については、もっと最近別ページにまとめた。
RedHat 系の RPM パッケージでは、portmap だけは portmap パッケージ、それ以外は nfs-utils パッケージに含まれる。下表のセルの色分けは、RedHat 系 ディストリビューションで、どの rc スクリプトで起動されるかを示す。 NFS に関係する rc スクリプトとして、この他に netfs もあるが、それはログイン時に勝手に /etc/fstab を読んで samba や NFS タイプのファイルシステムを自動的にマウントするためのものであり、その都度手動マウントする場合にはサーバ側にもクライアント側にも必要なく、セキュリティ面からも停止が推奨されている。
実行ファイル名 | 役割 | デフォルトポート | サーバに必要 | クライアントに必要 |
portmap | RPC系デーモンの使用ポートナンバーを管理する、いわばポート版の DNS のようなもの。NFS 関連だけでなく NIS (ypbind, ypserv) と, ruser, ruptime などの "r" 系サービスもその守備範囲 | 111 (TCP), 111 (UDP) | ○ | ○ |
rpc.nfsd | NFS サーバのユーザ空間プログラム。rpc.nfsd の役目はスレッドの生成のみで、 NFS の根幹機能はカーネルモジュール nfsd.o が司る | 2049 (UDP), 2049 (TCP) | ○ | × |
マウント要求を受け付けるデーモン | 動的 | ○ | × | |
クォータの問い合わせに答えるデーモン。 クォータを使わないのであれば必要ない。RedHat 系なら /etc/sysconfig/nfs に RQUOTAD=no と書いておけば、 rquotad は起動されなくなる。ただし RedHat Enterprise Linux では副作用も (コラム「rquotadの使用/不使用でnfslockdのポートが変わる?」参照) | 動的 | △ | × | |
rpc.lockd | 排他制御モジュール。NFS ロックマネージャ (NLM)。正確にはデーモンではなくカーネルモジュールであり、 2.2.18 より新しいカーネルの場合は敢えて起動する必要はなく、必要であれば自動的にロードされる | 動的 | ○ | ○ |
rpc.statd | RPC プロトコルの ネットワークステータスモニタ (NSM) デーモン。マシンが不正終了した際に、それを lockd に伝え、ファイルロック復帰の手助けをする | 動的 | ○ | ○ |
実行ファイル名/hosts_access名/RPCサービス名 対応表
実行ファイル名 | host_access名 | RPC名 |
portmap | portmap | portmapper |
rpc.nfsd | nfsd | nfs |
mountd | mountd | |
rquotad | rquotad | |
rpc.lockd | lockd | nlockmgr |
rpc.statd | statd | status |
書式:
directory client(option,option...) client(option,option...) ...
ro | 読み取り専用で共有。デフォルト |
rw | 読み書き可能で共有 |
sync async |
遅延書き込みを有効にするか。遅延書き込みを有効 (async) にしておくと、サーバ上で実際に sync が行われる前にサーバがリブートした場合データが壊れる恐れがある。NFS-1.1 以上では sync がデフォルト |
root_squash no_root_squash |
root_squash を直訳すれば「root 権限つぶし」。クライアントから UID 0 としてのファイル操作要求を受けた際、権限を剥奪し、nobody なユーザにマッピングする。デフォルトは root_squash 有効。下記 anonuid, anongid によってマッピング先 UID/GID が指定できる |
all_squash no_all_squash |
上記の拡大版で、 root に限らず全てのユーザが特定の 1ユーザにマッピングされる。デフォルトでは no_all_squash つまり無効。マッピング先 UID/GID は下記 anonuid, anongid で指定できる |
anonuid=xxx anongid=xxx |
上記の squash を行う際のマッピング先ユーザを指定したい場合に使用。 ID は文字列呼称でなくナンバーで指定しなければならない。つまり、マッピング先ユーザ/グループは、サーバとクライアントとで同一の UIDナンバー/GIDナンバー、同一の文字列呼称を持っていないと、おかしなことになる。指定しない場合のデフォルトは、nobody, nobody (RedHat系パッケージの場合は nfsnobody, nfsnobody) |
設定例:
/home/hoge 192.168.1.0/28(ro,sync)
NFS 起動後に主設定ファイルを書き換えた場合は、 exportfs -ra コマンドを発行すれば変更が反映される。
Solarisでは /etc/dfs/dfstab ファイルに下記のような塩梅で書く。 `root=' は Linux の場合の no_root_squash にあたる。
share -F nfs -o root=client1:client2,rw=client1:client2,ro=client3 /home/hoge
反映するには shareall または shareall -F nfs あるいは
root# /etc/init.d/nfs.server stop root# /etc/init.d/nfs.server start
単純に一時的にマウントするなら、例えばサーバ hoge の /home/hoge を /mnt/export にマウントする場合、クライアント上で:
root# mount -t nfs hoge:/home/hoge /mnt/export
とコマンドすれば良い。しかし、より最適なオプションを指定してクライアントの /etc/fstab に書いておくのが賢明。エントリは:
hoge:/home/hoge /mnt/export nfs ro,nosuid,_netdev,noauto,rsize=8192,wsize=8192,hard,intr,nfsvers=3,mountvers=3,notcp 0 0
主なマウントオプションの意味:
nosuid | 実行バイナリファイルに立っている SetUID ビットや SetGID を無視する |
_netdev | このマウントエントリにはネットワークアクセスが必須であることを mount に伝え、ネットワークが上がっていなければマウントを許さない |
noauto | mount -a による fstab エントリ一斉マウントに含めない (典型的にはブート時の自動マウント) |
noexec | バイナリファイルの実行を禁止する。上の記述例では使用していないが、さらに厳重にするなら指定しても良い |
rsize=xxx | データをサーバから読み取る際の転送ブロックサイズ。 1024 の倍数にすべき。 NFS Ver.2 での理論上の最大値は 8192。 Ver.3 では 8192 より大きくすることも可能ではあるが、上限はカーネルによって異なり、その値は カーネルソースの include/linux/nfsd/const.h にある NFSSVC_MAXBLKSIZE で決まる |
wsize=xxx | データをサーバに書き込む際の転送ブロックサイズ。上記同様 |
hard | クライアント上のプログラムがファイルにアクセスしている最中にサーバがクラッシュした場合、そのプログラムはタイムアウトせずに、サーバが回復するまで待ち続ける。相反するオプションに soft があるが、そちらだとプログラムは自動的にタイムアウトするが、高い確率でファイルが破損するらしい |
intr | interrupt の意 (たぶん)。上記 hard に伴ってハングしたプログラムを、中断や KILL できるようにする |
nfsvers=x | サーバとの通信にどのバージョンの NFS で使うか。現状では Version-2, 3 が使用可能。 rsize, wsize を 8192 (Ver.2 の理論上の最大値) 超に設定してパフォーマンスを上げたい場合には 3 を使う必要がある。デフォルトは 2 |
mountvers=x | 現行の NFS では mountd Version-1, 2, 3 が使用できるが、このディレクティブによって、サーバのどのバージョンの mountd に要求するかを明示することができる。NFS Version-3 で通信する場合には mountd Version-2 または 3 を指定するのが良いらしい。Fedora Core 3 の NFS マウント (nfs-utils-1.0.6, kernel-2.6.x) では mountvers=2 だと segmentation fault が出てNGで、mountvers=3 にしたら動作した。カーネル 2.4.x でのデフォルトは 1、 2.6.x では未調査 |
udp/tcp | NFS サーバに対してどちらのプロトコルを使うかを指定。デフォルトでは UDP (nfs-utils 1.0.4)。 Solaris では元来 TCP がデフォルトらしい。 "no" を付けて否定することもできる。つまり、例で使っているのは 「TCP は使うな !」 という意味。 こうして UDP だけに制限すると、ファイヤーウォールで開放しなければならないポートがより限定できるというセキュリティ上のメリットがある。ただし、両ピア間がネットワーク的に非対称 (例えばサーバのNICが100Mでクライアントが1000M) だったり、間にルータを挟んでいたりする場合には、逆に TCP を使ったほう (つまり `tcp' を指定) がトラブルを避けられることもある。 NFS over UDP ではリトライが起きた場合にその「ファイル」全体を再要求/再送してもらわなければならないが、 NFS over TCP ならば、落ちたパケットだけの再送で済むし、経路途中でのネットワークスピードの変化を上手に「吸収」してくれるからだ |
timeo=x | 要求(例えばファイル読取り要求) に対するサーバからの返答をどれだけの時間待つかの初期値。単位は 1/10 秒。例えばタイムアウトが `7' (nfs-utils 1.0.4 のデフォルト) つまり 0.7 秒であれば、最初、それだけ待ってサーバから返答が返ってこないと、次にクライアントは timeo を 2倍つまり 1.4 秒にして要求を送りなおす。それでもダメならさらに倍の 2.8 秒にしてまた再要求。これを、再送回数が retrans (次項参照) に達するか、timeo が 600 (60秒) に達するかのどちらか早いほうがやってくるまで繰り返す。デフォルト値の `7' はややせっかち過ぎるきらいがあり、調整時の現実的な値は 50 (5秒) や 100 (10秒) といった辺りのようだ。ただし、NFS-HOWTO によると、この設定は NFS over UDP の時にだけ作用するものらしい |
retrans=x | 上記 timeo によって再要求が生じた場合に、何度まで再要求するか。 3 回 (nfs-utils 1.0.4 のデフォルト) の場合、3回目の再要求でなおタイムアウトが起こると「メジャータイムアウト」となり `server not responding' というメッセージを表示するとともに、あと 1回 (つまり 4回目) だけトライする。それでもダメな場合は、またマイナータイムアウトに立ち返り、 3回目の再要求で...となる。 NFS-HOWTO によると、これも NFS over UDP に特有の設定であるように読める |
意図したクライアント以外からアクセスされないよう、サーバに制限を設ける。上層では TCP Wrappers による規制を掛け、さらにもっと手前で iptables (ファイヤーウォール) によるパケットフィルタを掛ける。
TCP ラッパーは tcpd が司る TCP/UDP のアクセス制限フィルタ。xinetd 経由で起動されるサービス、あるいは libwrap (/usr/lib/libwrap.a) をサポートしているサービスならば、 /etc/hosts.allow (許可するホストのリスト) と /etc/hosts.deny (拒否するホストのリスト) によってアクセスを規制できる。 TCP ラッパーをおおよそ理解したいのなら Red Hat ドキュメントサイト にある (例えば) Red Hat Enterprise Linux 3 の「リファレンスガイド」 tar ボールに含まれる ch-tcpwrappers.html が役に立つだろう。
実行バイナリが libwrap.a にリンクされているかどうかは、下記のコマンドで調べられる (nfsd の例):
strings /usr/sbin/rpc.nfsd |grep hosts
上記の試験によると、Fedora Core 1 に関しては、NFS関連デーモンの内、portmap と rquotad だけしか libwrap に対応していないようだが、付属の man を読んでみると、その他に少なくとも rpc.mountd と rpc.statd は TCP Wrappers をサポートしていることになっている。しかしともかく、関連サービスすべてを TCP ラッパーに掛けておくことにする。ホストアクセス設定ファイルに記述するサービス名は実行ファイルの名前とは異なる点に注意。実行ファイル名/hosts_access名/RPCサービス名対応表 参照。記述法については `man 5 hosts_access' してみると良い。
まず、こちらの設定ファイルで、すべてのホストからのアクセスを拒否する。
portmap mountd statd nfsd lockd rquotad: ALL
必要なホスト (クライアント) からの接続を許可する。192.168.1.1-15 からのアクセスを受け入れる場合:
portmap mountd statd nfsd lockd rquotad: 192.168.1.0/255.255.255.240
規制がちゃんと効いているかどうかは、相手側でデーモン群を起動しておき、クライアントから以下のコマンドを発行すれば分かる。 host の部分は相手のホスト名に置き換えるべし。ちなみに、host 引数を指定しないと localhost が対象となる:
root# rcpinfo -p host
または、UDP パケットを送って反応を調べるコマンド。こちらは引数省略不可:
root# rcpinfo -u host service
で状態を詳細表示してみる。 service の部分はこれまた実行ファイル名ともホストアクセスのサービス名とも違い、rpcinfo -p で表示される名称でなくてはならない。例えば lockd であれば nlockmgr を指定するといった具合。
iptables によるパケットフィルタは、その要求がどのプロセス (TCP Wrappers も含めて) に渡されるよりも前に行われるので、まさに水際での防御となる。
NFS サービスをファイヤーウォールでフィルタリングするには、少々厄介な問題がある。 デフォルトでは、portmap と nfsd 以外の NFS 関連デーモンは、使用するポートが動的に変化してしまうのだ。そこで、まず、それらのデーモンのポートを固定する必要がある。
mountd, rquotad, statd の 3 つは、デーモン起動時にオプションパラメータを渡すことで固定が可能だ。なお、RedHat 系では /etc/sysconfig/nfs に特定の変数を設定しておくことによって、rc スクリプトが読み取ってくれるようになっている。以下にその変数名も示す:
デーモン | ポートオプション | RedHat系rc変数名 |
mountd | -p port | MOUNTD_PORT |
rquotad | -p port | RQUOTAD_PORT |
statd | -p port (待機ポート) | STATD_PORT |
-o port (送信ポート) | STATD_OUTGOING_PORT |
また、 NFS デーモン群はデフォルトでは TCP と UDP の両方で待機するが、 mountd に関しては、オプションによって UDP でのみ待機するように指示することができる。多くのNFSクライアントは、マウント要求にデフォルトでは UDP を使うので、多種多様なOSやディストリビューションから成る大クライアント群を相手にしなければならない場合を除いては、TCP 待機は無効にしてしまっても構わないようだ。 rpc.mountd に渡すオプションは --no-tcp 。 RedHat 系ならば sysconfig/nfs に:
RPCMOUNTDOPTS="--no-tcp"
を書き加えておけば、この変数が rc スクリプトに拾われ、 rpc.mountd のオプションパラメータとして渡される。
他のプログラムとは異なり、lockd はカーネルモジュールなので、オプションを直接渡す仕掛けを持たない。その代わり /etc/modules.conf にモジュールオプションとして書いておくのが標準的な手段。あるいは、/etc/sysctl.conf に書いておくという手法もある。ただし RedHat 系では、init.d/nfs の中で、 sysctl コマンドを使ってカーネルパラメータを直接設定するルーティンが組まれており、やはり /etc/sysconfig/nfs に変数として書いておけば設定できる。
modules.conf に書いておく場合の記述:
options lockd nlm_udpport=xxxx nlm_tcpport=xxxx
sysctl.conf に書いておく場合の記述:
fs.nfs.nlm_udpport = xxxx fs.nfs.nlm_tcpport = xxxx
RedHat 系の rc スクリプト変数 (/etc/sysconfig/nfsで設定):
UDP ポートの指定変数: | LOCKD_UDPPORT |
TCP ポートの指定変数: | LOCKD_TCPPORT |
root# service nfs rstart root# rpcinfo -p
rquotad の使用/不使用で nfslockd のポートが変わる? RedHat Enterprise Linux 4 でのことだが、上記のどの方法を採っても lockd が意図したポートへ固定できなくて、しばし苦闘した経験がある。ところが、 /etc/sysconfig/nfs の RQUOTAD=no によって止めていた rquotad を自由にしてやった途端、 lockd のポートが指定通りに固定されるようになった。 /etc/init.d/nfs スクリプトにいくら目をこらしてみても道理が行かないのだが、実際疑いようのない事実だ。 Fedora Core 3 でも Fedora Core 4 でもこんな関連性は見られなかった。 |
最低限、許可しなければならないポートは以下。下記の例では、 portmap を除いては、mound も nfsd も UDP のみ使用する前提に立っている。
まずは汎用的なルール記述例を挙げることにしよう。
INPUT chain
Proto/Port | UDP 111 (portmap) |
設定例 | -A INPUT -p udp --dport 111 -s 192.168.1.0/28 -j ACCEPT |
Proto/Port | TCP 111 (portmap) |
設定例 | -A INPUT -p tcp --dport 111 -s 192.168.1.0/28 -j ACCEPT |
Proto/Port | UDP 2049 (nfsd) |
設定例 | -A INPUT -p udp --dport 2049 -s 192.168.1.0/28 -j ACCEPT |
Proto/Port | UDP xxxx (mountd にバインドしたポート) |
設定例 | -A INPUT -p udp --dport 32765 -s 192.168.1.0/28 -j ACCEPT |
Proto/Port | UDP xxxx (statd 待機ポート) |
設定例 | -A INPUT -p udp --dport 32766 -s 192.168.1.0/28 -j ACCEPT |
Proto/Port | UDP xxxx (lockd にバインドした UDP ポート) |
設定例 | -A INPUT -p udp --dport 32768 -s 192.168.1.0/28 -j ACCEPT |
OUTPUT chain
Proto/Port | UDP 111 (portmap) |
設定例 | -A OUTPUT -p udp --sport 111 -d 192.168.1.0/28 -j ACCEPT |
Proto/Port | TCP 111 (portmap) |
設定例 | -A OUTPUT -p tcp --sport 111 -d 192.168.1.0/28 -j ACCEPT |
Proto/Port | UDP 2049 (nfsd) |
設定例 | -A OUTPUT -p udp --sport 2049 -d 192.168.1.0/28 -j ACCEPT |
Proto/Port | UDP xxxx (mountd にバインドしたポート) |
設定例 | -A OUTPUT -p udp --sport 32765 -d 192.168.1.0/28 -j ACCEPT |
Proto/Port | UDP xxxx (statd 送信ポート) |
設定例 | -A OUTPUT -p udp --sport 32767 -d 192.168.1.0/28 -j ACCEPT |
Proto/Port | UDP xxxx (lockd にバインドした UDP ポート) |
設定例 | -A OUTPUT -p udp --sport 32768 -d 192.168.1.0/28 -j ACCEPT |
Iptables の設定例を記載した当家「インターネットサーバの自己防衛」に NFS サーバを追加する場合の例を示す。
tcp_packets chain (INPUT の子チェーン)
# {1-3-7} tcp_packets chain rules -A tcp_packets -p tcp -m multiport --dports 80,110,25,22,443 -j allowed -A tcp_packets -p tcp -s 192.168.1.0/28 --dport 111 -j allowed -A tcp_packets -p tcp -i eth0 -j ms_packets_tcp
udp_packets chain (INPUT の子チェーン)
# {1-3-8} udp_packets chain rules -A udp_packets -p udp -s 192.168.1.0/24 --dport 123 -j ACCEPT -A udp_packets -p udp -s 192.168.1.0/28 --dport 111 -j ACCEPT -A udp_packets -p udp -s 192.168.1.0/28 --dport 2049 -j ACCEPT -A udp_packets -p udp -s 192.168.1.0/28 --dport 32765:32766 -j ACCEPT -A udp_packets -p udp -s 192.168.1.0/28 --dport 32768 -j ACCEPT -A udp_packets -p udp -i eth0 --dport 67:68 -j DROP -A udp_packets -p udp -i eth0 -j ms_packets_udp
OUTPUT chain
OUTPUT パケットに関しては、ローカル発のパケットかどうか確認してから基本的には全部許可しているので、追加のルールは特に必要ない。とはいえ、より厳重にしたい場合は OUTPUT チェーンに以下のようなルールを加えてもいいだろう。なお、最初にあるローカル to ローカルのパケットを許可するルールは、NFS デーモン群のスタート/ストップ時に portmap がそうしたパケットを出すので必要となる。
# {1-3-C} OUTPUT chain rules -A OUTPUT -p all -d 127.0.0.1 -s 127.0.0.1 -j ACCEPT -A OUTPUT -p tcp -d ! 192.168.1.0/28 --sport 111 -j DROP -A OUTPUT -p udp -d ! 192.168.1.0/28 --sport 111 -j DROP -A OUTPUT -p tcp -d ! 192.168.1.0/28 --sport 2049 -j DROP -A OUTPUT -p udp -d ! 192.168.1.0/28 --sport 2049 -j DROP -A OUTPUT -p tcp -d ! 192.168.1.0/28 --sport 32765:32768 -j DROP -A OUTPUT -p udp -d ! 192.168.1.0/28 --sport 32765:32768 -j DROP -A OUTPUT -p all -s 127.0.0.1 -j ACCEPT -A OUTPUT -p all -s 192.168.1.1 -j ACCEPT -A OUTPUT -m limit --limit 3/minute --limit-burst 3 -j LOG --log-level info --log-prefix "IPT OUTPUT packs died: " COMMIT
パフォーマンス以前の問題なのだが、マウントは出来るが存在するはずのファイルが読めない、`server not responding' が多発する、といった場合には、まず第一に、NFS-HOWTO の Optimizing NFS Performance (NFSの性能を最適化する) の章をありがたく御拝読すること。その中からつまみ食いで、筆者が体験的に得た戒めや功を奏した項目を並べると...
Optimizing NFS Performance では、上に紹介した以外にも、クライアントからの要求をバッファするための、サーバのメモリ割り当てを増やすというアプローチが紹介されている。筆者は前述のパラメータと同時に試したため、これが純粋にどの程度の効果を発揮したかは定かでないが、加減さえ誤らなければ、少なくともマイナスに働くことはないはずだ。
具体的な方法としては;
という手順だが、これをいちいち手動でやるのは現実的でない。そこで、 RedHatの /etc/init.d/nfs に少々手を加え、そいつがデフォルトで使うパラメータファイル /etc/sysconfig/nfs でキューメモリの値もセットできるようにしたのが、下のツールだ。適法方法を示す;
if [ -f /usr/local/sbin/nfsmemtune ]; then . /usr/local/sbin/nfsmemtune qmemtune -tune fi
[ -n "$QMEMTUNE" ] && qmemtune -restore
RMEM_DEFAULT=524288 RMEM_MAX=1048576 WMEM_DEFAULT=524288 WMEM_MAX=1048576