PR

Rocky Linux 9にfirewalldを導入してSSHだけに絞る:インストールから始める設定手順と、締め出しから復旧する実験記録

IT情報

はじめに

「サーバーを立てれば、ファイアウォールくらい最初から入っているもの」――そう思っていませんか?

連載第1回で使った検証用 VPS を改めて調べたところ、Rocky Linux 9 の標準ファイアウォールである firewalld が、そもそもインストールされていませんでした。VPS のテンプレート側で削除されていたのです。その間、SSH(22番)と rpcbind(111番)が、ホスト側のファイアウォールなしで待ち受けている状態でした。

この記事では、firewalld のインストールから始めて、最終的に「SSH のみ許可」の最小構成にするまでの手順を、実機で確認しながら進めます。途中で「デフォルトでは Cockpit が許可されている」という注意点や、設定が保存される仕組み(runtime と permanent の違い)、そして最後には、わざと新規 SSH 接続を締め出して復旧する実験まで含めました。

対象読者:Rocky Linux 9 系を運用している方、VPS や小規模サーバーのファイアウォールを見直したい情シス・サーバー管理者の方。

結論(先に要点)

  • VPS のテンプレートによっては firewalld 自体が入っていないことがある。設定の前に、まず有無の確認から始める
  • インストール直後のデフォルトの許可サービスは cockpit / dhcpv6-client / ssh の3つ。「入れただけ」では最小構成にはならない
  • 設定変更は --permanent を付けて保存し、--reload で反映させるのが基本形。runtime だけの変更は reload で消える(実演で確認)
  • 不要なサービスを外した最終状態は services: ssh のみ。到達性は外側の PC から Test-NetConnection で確認し、22番=True(到達可)/111番=False(到達不可)の対が取れた
  • rpcbind 自体は止めず、firewalld で「外から 111 番に届かなくする」のがこの記事の範囲。rpcbind の停止手順は今回検証していないので扱わない

検証環境

  • VPS:WebARENA Indigo
  • OS:Rocky Linux release 9.8 (Blue Onyx)(第1回の検証後にフルアップデートで 9.8 になりました)
[root@server ~]# cat /etc/redhat-release
Rocky Linux release 9.8 (Blue Onyx)

まず現状確認:firewalld が入っていなかった

最初に、firewalld の有無を確認します。

[root@server ~]# dnf list installed firewalld
Error: No matching Packages to list
[root@server ~]# systemctl status firewalld
Unit firewalld.service could not be found.

インストールされていません。なぜ無いのかは dnf history に証拠が残っていました。

[root@server ~]# dnf history
ID     | Command line                                 | Date and time    | Action(s)      | Altered
---------------------------------------------------------------------------------------------------
     4 | -C -y remove firewalld --setopt=clean_requir | 2023-11-13 16:06 | Removed        |   10 EE

2023年11月、VPS イメージの作成時点で firewalld が削除されています。つまりこのサーバーは、ホスト側のファイアウォールが無い状態で提供されていました。「OS の標準ファイアウォールだから入っているはず」という思い込みは危険、というのが最初の教訓です。

続いて、今どのポートが待ち受けているかを確認します。

[root@server ~]# ss -tlnp
State     Recv-Q    Send-Q       Local Address:Port        Peer Address:Port    Process
LISTEN    0         128                0.0.0.0:22               0.0.0.0:*        users:(("sshd",pid=4790,fd=7))
LISTEN    0         4096               0.0.0.0:111              0.0.0.0:*        users:(("rpcbind",pid=642,fd=4),("systemd",pid=1,fd=59))
LISTEN    0         128                   [::]:22                  [::]:*        users:(("sshd",pid=4790,fd=8))
LISTEN    0         4096                  [::]:111                 [::]:*        users:(("rpcbind",pid=642,fd=6),("systemd",pid=1,fd=61))

待ち受けは2つだけです。

  • 22番(sshd)……SSH。用があるので当然
  • 111番(rpcbind)……次節で説明する portmapper の窓口。使っている自覚がないのに開いている、というのが今回のポイント

Cockpit(Web 管理画面)の有無も確認しておきます。

[root@server ~]# systemctl status cockpit.socket
○ cockpit.socket - Cockpit Web Service Socket
     Loaded: loaded (/usr/lib/systemd/system/cockpit.socket; disabled; preset: disabled)
     Active: inactive (dead)
   Triggers: ● cockpit.service
     Listen: [::]:9090 (Stream)

Cockpit 本体はインストールされていますが、ソケットは無効(disabled)で停止中。ss の結果にも 9090 は出ていないので、今のところ 9090 は開いていません(Cockpit 自体の停止・削除は次回以降の記事で扱います)。

用語整理:portmapper と rpcbind と 111番ポート

111番の正体を理解するために、用語を整理します。無効化や遮断を勧める前に「それが何者か」を押さえるのが大事だと思っています。

portmapper は「RPC サービスの受付窓口」です。

RPC(Remote Procedure Call)を使うサービスは、SSH=22 のような固定ポートを持たず、代わりに「プログラム番号」で呼ばれます。たとえば NFS は 100003、portmapper 自身は 100000、という具合です。

  • RPC サービスが起動すると、portmapper に「私(番号 100003)はポート 2049 にいます」と登録する
  • クライアントは、まず 111 番の portmapper に「100003 はどこ?」と聞いてから、本体のポートへ接続する

会社の代表電話にたとえると分かりやすいでしょう。外線番号(111番)は固定で公開されていて、内線(実際のポート)は代表が案内する。その案内係が portmapper です。

名前が3つ出てくるので対応を押さえておきます。

  • portmapper ……役割・仕組みの名前
  • rpcbind ……今の Linux で portmapper の役割を実装しているプログラムの名前
  • 111 ……portmapper 用に固定された窓口ポート

Rocky Linux では、NFS などがいつでも使えるように rpcbind が初期状態で動いています(正確には、接続が来たら起動できるようソケット側で待ち受けています)。ただ、この窓口が意味を持つのは、登録する RPC サービスがいてこそです。

実際に登録状況を見てみます。読み取り専用の確認コマンドです。

[root@server ~]# rpcinfo -p localhost
   program vers proto   port  service
    100000    4   tcp    111  portmapper
    100000    3   tcp    111  portmapper
    100000    2   tcp    111  portmapper
    100000    4   udp    111  portmapper
    100000    3   udp    111  portmapper
    100000    2   udp    111  portmapper

登録されているのは portmapper(100000)自身だけでした。つまりこのサーバーでは、rpcbind に依存しているサービスは存在しません。案内係しかいない受付、という状態です。それでも開けっ放しにしておくと、外部から「このサーバーでは何の RPC が動いているか」を探る足がかりにされうる、というのが「使っていないなら塞ぐ/止める対象」と言われる理由です。

なお、rpcbind を止めて困るのは RPC を使うもの(NFS、NIS、一部のレガシー/バックアップ系)だけです。Web や SSH、メールは固定ポートで動く別物なので影響しません。

ただしこの記事では、rpcbind 自体は止めません。停止の手順は今回検証していないためです。本記事の範囲は「firewalld を入れて、外から 111 番に届かなくする」ところまで、と線を引きます。

firewalld のインストールと有効化

ではインストールします。本体に加えて、依存関係で nftables など関連パッケージも入ります。

[root@server ~]# dnf install -y firewalld
(途中省略)
================================================================================
 Package                       Architecture    Version                    Repository          Size
================================================================================
Installing:
 firewalld                     noarch          1.3.4-20.el9_8             baseos             452 k
Installing dependencies:
 firewalld-filesystem          noarch          1.3.4-20.el9_8             baseos             8.9 k
 ipset                         x86_64          7.11-11.el9_5              baseos              41 k
 ipset-libs                    x86_64          7.11-11.el9_5              baseos              68 k
 iptables-nft                  x86_64          1.8.10-11.el9_5            baseos             187 k
 libnftnl                      x86_64          1.2.6-4.el9_4              baseos              87 k
 nftables                      x86_64          1:1.0.9-9.el9_8            baseos            453 k
 python3-firewall              noarch          1.3.4-20.el9_8            baseos            355 k
 python3-nftables              x86_64          1:1.0.9-9.el9_8            baseos             43 k
Installing weak dependencies:
 libcap-ng-python3             x86_64          0.8.2-7.el9                appstream           29 k

Transaction Summary
================================================================================
Install  10 Packages

Total download size: 1.7 M
Installed size: 6.4 M
(途中省略)
Complete!

計10パッケージが入りました。続いて有効化して、状態を確認します。

[root@server ~]# systemctl enable --now firewalld
[root@server ~]# firewall-cmd --state
running

ファイアウォールを有効化する操作で一番怖いのは「その瞬間に今の SSH セッションが切れること」ですが、この検証では既存セッションは切れず、そのままコマンド実行を続行できました。さらに、別のターミナルから新規の SSH ログインができることも確認済みです(デフォルトのゾーンが SSH を許可しているためです。次節でその中身を見ます)。

デフォルト設定を確認する(cockpit が許可されている)

有効化できたから終わり、ではありません。デフォルトで何が許可されているかを必ず確認します。

[root@server ~]# firewall-cmd --get-default-zone
public
[root@server ~]# firewall-cmd --list-all
public (active)
  target: default
  icmp-block-inversion: no
  interfaces: eth0
  sources:
  services: cockpit dhcpv6-client ssh
  ports:
  protocols:
  forward: yes
  masquerade: no
  forward-ports:
  source-ports:
  icmp-blocks:
  rich rules:

デフォルトゾーンは public、許可サービスは次の3つでした。

  • ssh(22番)……必要
  • cockpit(9090番)……要注意
  • dhcpv6-client(DHCPv6 の応答を受けるためのもの)……環境による

問題は cockpit です。前節で確認したとおり、cockpit.socket は無効で 9090 は LISTEN していません。ところが firewalld 側では cockpit が「許可済み」になっています。つまり、将来 Cockpit を起動したら、その瞬間に 9090 が外からそのまま通る状態です。「今は開いていない」ことと「ファイアウォールで許可されている」ことは別、というのがここでのポイントです。

方針は単純で、「使っていないサービスの許可は外す」。次節の仕組みを使って cockpit を外し、最後に dhcpv6-client の要否も判断して、ssh だけに絞ります。

runtime と permanent:reload で設定が「戻る」理由

firewalld の設定には2種類あります。

  • runtime ……今この瞬間に効いている設定。変更は即座に反映されるが、保存されていない
  • permanent ……ファイルに保存された設定。サービスの起動時や reload 時に読み込まれる

firewall-cmd の変更は、既定では runtime に対して行われます。ここを理解していないと「設定したはずなのに、再起動したら元に戻っていた」という事故になります。cockpit の除去で、実際に挙動を実演してみました。

まず runtime だけで cockpit を外します。

[root@server ~]# firewall-cmd --remove-service=cockpit
success
[root@server ~]# firewall-cmd --list-services
dhcpv6-client ssh

cockpit が消えました。ところが、ここで reload すると――

[root@server ~]# firewall-cmd --reload
success
[root@server ~]# firewall-cmd --list-services
cockpit dhcpv6-client ssh

cockpit が復活しました。 reload は「permanent の内容で runtime を読み直す」動作なので、保存されていない runtime の変更は消えてしまうのです。

今度は --permanent を付けて保存してから reload します。

[root@server ~]# firewall-cmd --permanent --remove-service=cockpit
success
[root@server ~]# firewall-cmd --reload
success
[root@server ~]# firewall-cmd --list-services
dhcpv6-client ssh

今度は reload 後も cockpit は消えたままです。firewalld の変更の基本形は、この2ステップだと覚えておくと安全です。

  1. --permanent を付けて変更する(保存)
  2. --reload で反映する

逆に、runtime だけの変更は「お試し用」と割り切ると、この後のコラムのような実験にも使えます。

SSH のみに絞る(dhcpv6-client の要否判断)

残るは dhcpv6-client です。これは DHCPv6 で IPv6 アドレス等の設定を受け取るクライアント側の通信(UDP 546番)を許可するサービスです。要るかどうかは、そのサーバーが IPv6 アドレスを DHCPv6 で受け取る構成かどうかで決まります。

実機の IPv6 の付き方を確認します。

[root@server ~]# ip -6 addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 state UNKNOWN qlen 1000
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
    inet6 fe80::213:5dff:fe13:aad/64 scope link proto kernel_ll
       valid_lft forever preferred_lft forever

eth0 に付いているのは link-local アドレス(fe80::/64)だけで、グローバルな IPv6 アドレスはありません。つまりこの環境は DHCPv6 でアドレスを受け取る構成ではないため、dhcpv6-client は不要と判断しました。

注意:この判断はこの検証環境でのものです。読者の環境で IPv6 アドレスを DHCPv6 で取得している場合は、外すと IPv6 側の通信ができなくなるおそれがあります。外す前に必ず ip -6 addr で自分の環境を確認してください。

では外します。cockpit と同じく、permanent への変更+reload です。

[root@server ~]# firewall-cmd --permanent --remove-service=dhcpv6-client
success
[root@server ~]# firewall-cmd --reload
success
[root@server ~]# firewall-cmd --list-all
public (active)
  target: default
  icmp-block-inversion: no
  interfaces: eth0
  sources:
  services: ssh
  ports:
  protocols:
  forward: yes
  masquerade: no
  forward-ports:
  source-ports:
  icmp-blocks:
  rich rules:

services: ssh のみになりました。これがこの記事の到達点です。

外側から確認する(22番は通り、111番は届かない)

サーバー上の設定だけ見て満足せず、外側の PC から実際に届くかどうかを確認します。手元の Windows PC の PowerShell で Test-NetConnection を使います(以下、サーバーの IP アドレスは伏せて表記します)。

まず SSH の 22 番。

PS C:\> Test-NetConnection 203.0.113.10 -Port 22

ComputerName     : 203.0.113.10
RemoteAddress    : 203.0.113.10
RemotePort       : 22
InterfaceAlias   : Wi-Fi
SourceAddress    : 192.168.40.69
TcpTestSucceeded : True

TcpTestSucceeded : True、到達できます。次に 111 番。

PS C:\> Test-NetConnection 203.0.113.10 -Port 111
警告: TCP connect to (203.0.113.10 : 111) failed
警告: Ping to 203.0.113.10 failed with status: TimedOut

ComputerName           : 203.0.113.10
RemoteAddress          : 203.0.113.10
RemotePort             : 111
InterfaceAlias         : Wi-Fi
SourceAddress          : 192.168.40.69
PingSucceeded          : False
PingReplyDetails (RTT) : 0 ms
TcpTestSucceeded       : False

TcpTestSucceeded : False、到達できませんでした。

ここで注目したいのは、False が返るまでの挙動です。このテストは結果が出るまでかなり待たされました。firewalld のデフォルトの動作は、許可されていない通信を**応答を返さずに捨てる(DROP)**ためです。一般に、ファイアウォールが無く単にポートが閉じているだけなら、OS が即座に接続拒否を返すのでテストはすぐ終わります。黙って捨てられると、相手は応答を待ち続けて再送を繰り返し、やがてタイムアウトして失敗になります。待たされてから失敗する、その待ち時間自体が DROP のサインです。

そして大事な対比として、111 番はサーバー上では今も LISTEN したままです(最初の ss の結果どおり、rpcbind は止めていません)。「プロセスが待ち受けている」ことと「外から届く」ことは別であり、その間を塞ぐのがファイアウォールの仕事だ、という確認が取れました。

コラム:締め出し実験 ― runtime の ssh を消して、reload で復旧する

firewalld の設定ミスで一番怖いのは、SSH を締め出して自分が入れなくなることです。この検証環境で、あえてそれを再現し、「接続済みセッションから復旧できるか」を実測しました。安全設計つきでやるのがポイントです。

手順0:復旧の生命線と、安全装置を用意する

まず、permanent 側に ssh が残っていることを確認します。復旧は「permanent の内容を reload で読み直す」ことで行うので、これが生命線です。

[root@server ~]# firewall-cmd --permanent --list-services
ssh

次に安全装置として、10分後に自動で firewall-cmd --reload が実行されるタイマーを仕掛けます。万一このセッションごと落ちても、最大10分で ssh ありの状態に戻ります。

[root@server ~]# systemd-run --on-active=10min --unit=fw-safety /usr/bin/firewall-cmd --reload
Running timer as unit: fw-safety.timer
Will run service as unit: fw-safety.service
[root@server ~]# systemctl list-timers fw-safety.timer
NEXT                        LEFT      LAST PASSED UNIT            ACTIVATES
Thu 2026-11-05 14:53:26 UTC 9min left -    -      fw-safety.timer fw-safety.service

手順1:壊す(runtime のみ)

作業中のセッションは絶対に閉じず、そのまま runtime の ssh を外します。--permanent は付けません。

[root@server ~]# firewall-cmd --remove-service=ssh
success
[root@server ~]# firewall-cmd --list-services

許可サービスが空になりました。この状態で確認できたことは2つです。

  • 接続済みのセッションは生きている:このとおり、その後もコマンドを実行し続けられました
  • 新規接続は締め出される:自宅 PC から Test-NetConnection(IP) -Port 22 を実行すると、今度は TcpTestSucceeded : False(待たされてから接続失敗)になりました

手順2:生きているセッションから復旧する

復旧は reload 一発です。

[root@server ~]# firewall-cmd --reload
success
[root@server ~]# firewall-cmd --list-services
ssh

permanent に残っていた ssh が runtime に戻りました。自宅 PC から再度 Test-NetConnection を実行すると TcpTestSucceeded : True に復帰。安全装置のタイマーが発動する前に、手動での復旧を完遂できました。最後に後片付けとして systemctl stop fw-safety.timer でタイマーを止め、firewall-cmd --list-all で最終状態(services: ssh)が維持されていることを再確認して終了です。

この実験から得た教訓は4つです。

  1. 作業セッションは閉じない。接続済みセッションが残っていれば、そこが復旧の足場になる
  2. 壊す前に、自動で元に戻る仕掛けを入れる(今回は systemd のタイマーで10分後に reload)
  3. 壊すのは runtime だけ。--permanent を付けて ssh を消したり、壊れた runtime を --runtime-to-permanent で保存したりすると、復旧手段そのものが消える
  4. 復旧は reload。permanent に正しい設定が残っていることが前提

まとめ

  • 検証した VPS では firewalld がテンプレート段階で削除されていた。「標準だから入っているはず」と思い込まず、まず有無を確認する
  • デフォルトの許可は cockpit / dhcpv6-client / ssh の3つ。cockpit は、起動していない今は開いていなくても「許可はされている」状態だった
  • 設定変更は --permanent で保存し --reload で反映する。runtime のみの変更は reload で消える(復活する)ことを実演で確認した
  • dhcpv6-client は ip -6 addr の結果(link-local のみ)から不要と判断。最終状態は services: ssh のみ
  • 外側からの確認では 22番=True/111番=False の対が取れた。111番のプロセス自体は止めておらず、firewalld で届かなくした
  • 締め出しは「安全装置+runtime だけを壊す+作業セッションを残す」で安全に実験でき、reload で復旧できることを実証した

次回は、Cockpit 本体の停止・アンインストールを扱う予定です。


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