一般に、マシン C からマシン S へのアクセスを制限するには、
(IN) マシン S でアクセスを受け付けるところで制限する「入口規制」
(OUT) マシン C でアクセスを出すところで制限する「出口規制」
の2通りの方法があります。一般にアクセス制限というと (IN) の入口規制を用いることが多いのですが、 これはアクセスを出すマシンが特定できないか、 あるいは特定はできるのだけどマシンの数ないし種類が多過ぎるためです。
例えば、マシン S へのログインを制限したい場合を考えてみましょう。 一般的にはアクセス元となりうるマシンは特定できないか、 特定できたとしても数が多いでしょうし、 そのマシンで動く OS の種類も多種多様でしょう。 アクセス元となりうるマシンの全てで、 アクセスを出すことを制限しようとするのは非現実的です。 なので普通はアクセスを受け付ける側のマシン S で クライアントの認証を行なってアクセス制限をします。
例えば ssh の場合ならどこのマシンからのアクセスであっても、 秘密鍵を持っていることを証明できるならアクセスを許可し、 そうでなければ却下します。 アクセスを出す側のマシンでは何の出口規制もなく、 アクセスを受け付ける側のマシンでの入口規制だけですね。
ところが、データセンタのラック内のマシンに限定すれば、 事情は変わってきます。 ラック内に入れてあるマシンの数は高々有限 ;) ですし、 多くの場合全てのマシンで同じ OS が動いているでしょうし、 管理の都合からいえば全てのマシンを同じチームが管理すべきでしょう。
このような特殊な環境下では、 (OUT) の出口規制も現実的な方法となります。 例えば DB サーバへのアクセスを考えると、 DB サーバは通常のサーバ認証のみであっても、 クライアント側でアクセスを出すのを制限する出口規制を設けることによって、 セキュリティを向上させることができます。
クライアント側の OS が Linux であれば、 出口規制の方法として iptables の OUTPUT チェインを使うことができます。 例えば DB サーバが 12345番ポートでアクセスを受け付けているならば、 ラック内の全マシンにおいて
iptables -N dbaccess 2>/dev/null || iptables -F dbaccess iptables -A dbaccess -j ACCEPT -m owner --uid-owner dbuser …(2) iptables -A dbaccess -j DROP …(3) iptables -A OUTPUT -j dbaccess -p tcp --dport 12345 --syn …(1)
を実行しておきます。
DB サーバへアクセスを出そうとして、 DB サーバが動いているマシンの 12345番ポートへの接続しようとすると、 まず OUTPUT チェインの (1) のルールによって dbaccess チェインへジャンプします。 アクセスを出そうとしたユーザが dbuser であれば、 dbaccess チェインの (2) のルールによって通信が許可されますが、 dbuser 以外のユーザであれば、 (3) のルールによって通信が却下されます。
つまり、dbuser 以外のユーザは、たとえ DB のパスワードを知っていても、 DB にアクセスできない、というわけです。
DB にアクセスしたいユーザが増えてくると、 そのたびに iptables の設定を変更するのは面倒ですので、 DB サーバへ接続できるユーザは一つだけ (例えば dbuser) にしておいて、 そのユーザ権限で中継プログラムを動かすようにすると便利でしょう。
例えば、DB にアクセスしたいユーザ user1, user2, user3, user4 が あるとします。 まず、DB 上に同名のユーザアカウントを作ります。 次に、ラック内の全サーバにおいて、 ユーザ毎に UNIX ドメインソケットを listen し、 そのソケットへ接続があったら、 それを DB サーバへ中継するプログラム (ここでは dbrelay と呼ぶことにしましょう) を動かします。 例えば次のようなソケットをオープンすることになるでしょう。
/home/user1/socket /home/user2/socket /home/user3/socket /home/user4/socket
各ソケット (あるいはソケットを置いてあるディレクトリ) は、 自分以外のユーザは読み書きできないように パーミッションを設定します。 つまり dbrelay は root 権限で起動する必要があります。 さらに、前述したように DB サーバへアクセスするには dbuser ユーザ権限で実行する必要がありますから、 各ソケットをオープンした後は、 dbuser ユーザ権限に setuid する必要があります。
そして、ここが肝なのですが、 dbrelay は DB サーバへの通信を監視し、 接続を受け付けたソケットのユーザ名と、 DB へのアクセスを行なう DB ユーザ名が一致しない場合は 通信を遮断します。
以上のような仕掛けにより、 例えばユーザ user1 は、 /home/user1/socket へ接続することによって DB サーバへアクセスできるが、 そのとき使用できる DB ユーザ名は user1 だけで、 それ以外のユーザ名で DB へアクセスしようとしても dbrelay によって 通信を遮断されることになります。 しかも /home/user1/socket は user1 以外は読み書きできません。 つまり、各ユーザは自身のユーザ名以外では (仮に他の DB ユーザのパスワードを知っていたとしても) DB へアクセスできない ということになります。