犬でも分かるLinuxネットワーク設定(10):outboundのアクセス制限

以前、firewalldによるアクセス制限の記事では、ゾーンに対してアクセス制限を設定する方法を解説しました。でも、ゾーンに対してアクセス制限ができるのはinboundのみということが分かりました。今回は、outboundに対するアクセス制限はどのように設定するかという話しをします。

outboundのアクセス制限で留意すること..

(1) outboundのアクセス制限にはダイレクトルールを使う

firewalldによるoutbound(すなわち、ホストから外部に出ていくパケット)のアクセス制限をするには、ゾーンに対して設定するのではなく、ダイレクトルールを使って設定を行います。
ちなみに、firewalldの裏でnftablesというネットワークフィルタのサービスが存在しているのですが、nftablesに対して直接ルールを適用するという意味でダイレクトルールというらしいです。nttableに対する操作となるので、ゾーンに対するアクセス制限(inboundのアクセス制限)の場合と比較して以下の点が異なるとに注意しましょう。

① ダイレクトルールでは、ゾーンではなくネットワークインターフェースを対象にしてルールを適用する。
② ルールを適用するための書式が異なる

(2) クライアント側のportはエフェメラルポートが使われるという点に注意する

firewalldに限らず、ファイヤーウォールのアクセス設計をするとき、ウェルノウンポートエフェメラルポートのことを知っておいたほうが良いです(いや、知っておくべきかな..)。

(2-1) ウェルノウンポートとエフェメラルポートについて

クライアント-サーバの通信において、サーバ側は予め決められた固定のport番号でリクエストを待ち受けます。sshの例でいうとport番号は22です。このようにサービス毎に決められたport番号のことをウェルノウンポートと呼びます(正式にはシステムポートと呼ぶらしい)。一方でクライアント側はというと、任意のport番号が選ばれ、そのport番号で接続を行います。このように動的に決められるport番号を、エフェメラルポートと呼びます(ダイナミックポートとか動的ポートとか呼ぶこともあります)。エフェメラルポートの番号は、接続時に動的に決定され、ポート番号の範囲はOSによって異なります。

このように、例えばsshの例では、1つのsshセッション(セッションとは接続単位だと思ってください)において、クライアントで任意に決まったエフェメラルポートとサーバの22番ポートの間で通信を行っています。使用するエフェメラルポートは、セッション毎に異なる番号になります。

Linuxにおいてプロセスが使用しているport番号を調べるには「lsof -i」コマンドを使います。
今、クライアント(ホストbase1)からサーバ(ホストbase0)に、sshセッションを1本接続したとします。
サーバ側(ホストbase0)の状態は以下の通りでした(抜粋)。

# lsof -i
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME

sshd 777 root 3u IPv4 26997 0t0 TCP *:ssh (LISTEN)
sshd 777 root 4u IPv6 26999 0t0 TCP *:ssh (LISTEN)

sshd 34659 root 4u IPv4 840820 0t0 TCP base0:ssh->base1:35460 (ESTABLISHED)
sshd 34661 root 4u IPv4 840820 0t0 TCP base0:ssh->base1:35460 (ESTABLISHED)

PID=777はsshd(SSHサーバ)のプロセスで「*:ssh」の通り、全てのアドレスからsshのウェルノウンポート(22)でLISTEN(待ち受け)していることが分かります。sshセッションは親プロセスがPID=34650、子プロセスがPID=34661で、サーバ(base0)のsshのウェルノウンポート(22)とクライアント(base1)のポート35460(エフェメラルポート)が接続されていることが分かります。

一方、クライアント側(ホストbase1)の状態は以下の通りでした(抜粋)。


ssh 2062 root 3u IPv4 34695 0t0 TCP base1:35460->_base0:ssh (ESTABLISHED)

sshセッションのプロセスはPID=2062で、クライアント(base1)のポート35460(エフェメラルポート)とサーバ(base0)のsshのウェルノウンポート(22)が接続されていることが分かります。

ちなみに、Rocky9.5でエフェメラルポートの番号の範囲を調べてみたら、以下のようになりました。

# sysctl net.ipv4.ip_local_port_range
net.ipv4.ip_local_port_range = 32768 60999

(2-2) ウェルノウンポートを意識したアクセス設定

さて以下の図のクライアントホストにおいて、外部へのssh接続を許可し、その他の接続は許可しないようアクセス制限をしたいと思います。
最初に頭に思い浮かぶのは、「22番portのみ許可(ACCEPT)して、その他は拒否(DROP)」という設定です。

しかし、この設定だけでは、サーバからクライアントに帰る通信は22番以外の任意ポート(エフェメラルポート)になるので、拒否されてしまいます。
この問題を解決するために、接続追跡(state tracking) を活用して、RELATED および ESTABLISHED 状態のパケットを許可するルールを追加する必要があります。

このようにRELATED および ESTABLISHED 状態のパケットを許可することで、クライアントから外部へのssh接続だけを許可することができました。

ダイレクトルールを使って実際にoutboundのアクセス制限をしてみる

Rocky9.5のホストbase1において、ダイレクトルールを使ってoutboundのアクセス制限をしてみようと思います。設定するルールは以下のようにしたいと思います。

① ネットワークインターフェース enp3s0から外に出ていく(outbound)パケットを制限する
② 許可するアクセスはssh(port=22)とhttps(port=443)とする
③ それ以外のアクセスは拒否する

(1) ダイレクトルールを適用する

前述の①~③の設定を行うダイレクトルールのコマンドは以下の通りとなります。

# firewall-cmd --direct --add-rule ipv4 filter OUTPUT 0 -o enp3s0 -p tcp --dport 22 -j ACCEPT
success
# firewall-cmd --direct --add-rule ipv4 filter OUTPUT 1 -o enp3s0 -p tcp --dport 443 -j ACCEPT
success
# firewall-cmd --direct --add-rule ipv4 filter OUTPUT 100 -o enp3s0 -m state --state RELATED,ESTABLISHED -j ACCEPT
success

# firewall-cmd --direct --add-rule ipv4 filter OUTPUT 1000 -o enp3s0 -j DROP
success

パラメタ意味
--directダイレクトルールを指定する
--add-rule新しいルールを追加する
ipv4ルールを適用するアドレスファミリーがIPv4であることを指定する
filter操作対象のnftables(iptables)テーブル名。ここではパケットフィルタリングを行うfilterテーブルを指定している。
OUTPUT <優先順位>操作対象のnftables(iptables)チェーン名。ここではoutboundを指定している。優先順位に指定する番号は、小さい数字ほど優先順位が高い。
-o <ifname>ルールを適用するネットワークインターフェースを指定する。
--dport <ポート番号>宛て先ポート番号を指定する。
-j ACCEPT
-j DROP
アクションを指定する。ACCEPTは許可、DROPは拒否
-m state接続状態を判定するためのモジュール(state)を使用する。
--state RELATED,ESTABLISHED接続状態がRELATEDまたはESTABLISHEDのパケットにルールを適用する。接続後のセッションが使用するエフェメラルポートの接続を許可するときに指定すると良い。

ダイレクトルールを設定したら、以下のコマンドで設定状態を確認してみましょう。

# firewall-cmd --direct --get-all-rules

(2) アクセスのテストをしてみる
① sshアクセスのテスト

# ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null 192.168.2.200 hostname
Warning: Permanently added '192.168.2.200' (ED25519) to the list of known hosts.
base0

② httpsアクセスのテスト

# wget --no-check-certificate https://192.168.2.200
--2025-01-26 15:23:28-- https://192.168.2.200/

192.168.2.200:443 に接続しています… 接続しました。
警告: 192.168.2.200' の証明書は信用されません。 警告:192.168.2.200' の証明書の発行者が不明です。

HTTP による接続要求を送信しました、応答を待っています... 200 OK 長さ: 8020 (7.8K) [text/html] index.html' に保存中

index.html 100%[==================================================================================>] 7.83K --.-KB/s 時間 0.008s

2025-01-26 15:23:29 (964 KB/s) - `index.html' へ保存完了 [8020/8020]

③ httpアクセスのテスト

# cat show_direct.sh
firewall-cmd --direct --get-all-rules
[root@base1 FIREWALLD]# wget http://192.168.2.200
--2025-01-26 15:33:42-- http://192.168.2.200/
192.168.2.200:80 に接続しています…

(このままレスポンス無し..)

という訳で、ダイレクトルールによる設定が正しいことを確認できました。

(3) 設定を永続化(保存)する
(1)で設定したルールは「--permanent」を付けていないので再起動すると設定がリセットされちゃいます。なので、以下のコマンドで設定を永続化します。

# firewall-cmd --runtime-to-permanent
success

outboundアクセス設定を行うのはどんなとき..

outboundアクセスを制限したい場合って、どんなときなんだろう。。。家の中からインターネットへのアクセスを制限するなんてことは、あまり考えられない。。会社では、セキュリティを意識して特定のサービスにはアクセスさせたくないとか、そういうケースはあるかもしれません。
もし、そういうケースに対応するときには、ホスト1台毎にoutboundのアクセス制限をかけるよりも、ゲートウェイとなるサーバにアクセス制限をかけるほうが良いかと思います。
ゲートウェイがLinuxサーバの場合は、NAT機能を有効にしますが、このNATの部分にアクセス制限をかけることで、プライベートネットワークに所属するホストのoutboundアクセスを制限することができるので、次回はこの方法を解決してみたいと思います。

広告主へのリンク

オイラが今回のテストに使っているのは、高価なサーバではなくて、安価なミニPC MINISFORUM GK41 というマシンです。LANポートが2つ付いているので、PXEブートのテストにも便利です。


更に、このMINISFORUM GK41のUSBポートに以下のUSB-LAN変換アダプタを付けて、LANポートを合計3つにして運用しています。




このブログにおける関連リンク

犬でも分かるLinuxネットワーク
犬でも分かるLinuxネットワーク設定(1): nmcliコマンドの使い方
犬でも分かるLinuxネットワーク設定(2): ボンディング(bonding)
犬でも分かるLinuxネットワーク設定(3): 仮想ブリッジ
犬でも分かるLinuxネットワーク設定(4): ボンディング+仮想ブリッジ
犬でも分かるLinuxネットワーク設定(5):定義ファイルの修正
犬でも分かるLinuxネットワーク設定(6): ルーティングとNAT
犬でも分かるLinuxネットワーク設定(7):firewalldによるアクセス制限
犬でも分かるLinuxネットワーク設定(8):VLAN
犬でも分かるLinuxネットワーク設定(9):IPエイリアス
犬でも分かるLinuxネットワーク設定(10):outboundのアクセス制限
犬でも分かるLinuxネットワーク設定(11):NATポリシーにアクセス制限を設定する

コメント

タイトルとURLをコピーしました