RHEL 中級 #1 SELinux 入門 — Enforcing/Permissive、ラベル、トラブルシューティング

読了 14分

基礎 #7 で SELinux を 1 行だけ押さえました。「RHEL 9 で SELinux はデフォルト Enforcing で、切ってはいけない。」 しかし運用に入るとその 1 行に最もよく悩まされます。この記事では SELinux を切らずに問題を解く方法を扱います。モードを変える方法、ラベルを確認する方法、アクセスを拒否されたときの対処方法まで一度に整理します。SELinux を理解すれば RHEL 運用の安定性が 1 段上がります。

RHEL 中級 シリーズでこの記事の位置:

  • #1 SELinux 入門 — Enforcing/Permissive、ラベル、トラブルシューティング ← この記事
  • #2 LVM — PV/VG/LV、スナップショット、拡張
  • #3 ストレージ深化 — Stratis、NFS、Samba
  • #4 ネットワーキング — NetworkManager (nmcli)、bonding、teaming
  • #5 ログ管理 — journald、rsyslog、log rotation
  • #6 ジョブスケジューリング — cron、systemd timer、at
  • #7 コンテナ入門 — Podman/Buildah/Skopeo (Docker との違い)

SELinux とは何か #

Linux の基本権限モデルは DAC (Discretionary Access Control、任意アクセス制御) です。ファイルに書かれたユーザー/グループ/rwx で決定される、基礎 #5 で扱ったそのモデルです。問題は 1 行に要約されます。root 権限を取ったプロセスはすべてのファイルにアクセスできます。

Web サーバーが 1 度破られて httpd プロセスを攻撃者が掴んだとしましょう。そのプロセスは root か apache ユーザーで動作しますが、どちら側でもシステムのどこへでも自分の権限内で自由に動き回れます。/etc/shadow 読み取り、/var/lib/mysql アクセス、SSH キー盗難。DAC だけでは防げません。

SELinux (Security-Enhanced Linux) はその上にもう 1 層置く MAC (Mandatory Access Control、強制アクセス制御) です。すべてのファイル・プロセス・ソケット・ポートに ラベル(context) を付与しておき、カーネルが「このラベルのプロセスがあのラベルのリソースにアクセスしてもよいか」をポリシーに従って強制的に検査します。DAC が通過しても SELinux が止められて、root も SELinux を回避できません

DAC vs MAC の 2 層
   リクエスト: httpd プロセスが /etc/shadow を読もうとする
   ┌─────────────────────────────────────┐
   │ 1. DAC 検査                           │
   │    httpd のユーザーが /etc/shadow の │
   │    パーミッション(通常 600 root)を  │
   │    通過するか?                       │
   │    → ほぼ常に拒否。通過すれば ↓     │
   └─────────────────────────────────────┘
   ┌─────────────────────────────────────┐
   │ 2. SELinux (MAC) 検査                │
   │    httpd_t タイプのプロセスが        │
   │    shadow_t タイプのファイルを読む  │
   │    ポリシーが許可したか?             │
   │    → ポリシーになければ拒否        │
   └─────────────────────────────────────┘
                     アクセス許可

DAC が一度破られても SELinux が捕まえてくれます。2 番目の鍵 と考えればよいです。RHEL 9 のセキュリティモデルでもっとも大きな違いが出る部分です。

3 つのモード — Enforcing / Permissive / Disabled #

SELinux の動作モードは 3 つです。

モード動作推奨
Enforcingポリシー違反を 遮断 + ログ残す✅ 運用標準 (RHEL 9 デフォルト値)
Permissiveポリシー違反を 許可 + ログだけ残すデバッグ・ポリシー作成時の一時
DisabledSELinux 自体が動作しない❌ 運用絶対禁止

確認:

現在のモード確認
$ getenforce
Enforcing

$ sestatus
SELinux status:                 enabled
SELinuxfs mount:                /sys/fs/selinux
SELinux root directory:         /etc/selinux
Loaded policy name:             targeted
Current mode:                   enforcing
Mode from config file:          enforcing
Policy MLS status:              enabled
Policy deny_unknown status:     allowed
Memory protection checking:     actual (secure)
Max kernel policy version:      33

sestatus がより豊かな情報です。Loaded policy nametargeted であるのが一般的です (ほとんどの環境で)。MLS は軍/政府などの特殊環境でのみ使用します。

一時的な切り替え — setenforce #

ランタイムモード切り替え
$ sudo setenforce 0       # Permissive へ (再起動すれば元に戻る)
$ sudo setenforce 1       # Enforcing へ

$ getenforce
Permissive

デバッグ中だけ ちょっと Permissive に下げておいて、終わればまた Enforcing に上げます。

永久切り替え — /etc/selinux/config #

/etc/selinux/config
SELINUX=enforcing
SELINUXTYPE=targeted

SELINUX= の値を permissive / enforcing / disabled に変えて再起動すれば適用されます。ただし:

  • disabled は絶対に使わないでください。 一度 disabled で起動すると、すべてのファイルのラベルが解けて、再び Enforcing に上げるときに全体ファイルシステムの relabel (数十分~数時間) が必要です。
  • 一時的に SELinux 検査を切りたければ 常に permissive で。デバッグ・ログ収集は Permissive でも同様に行えます。

autorelabel — ラベルが壊れたとき #

何かおかしくなって全体ファイルシステムのラベルを再付与したいとき:

次の起動時に全体 relabel
$ sudo touch /.autorelabel
$ sudo reboot

起動時にすべてのファイルをポリシー基準で再ラベルします。disabledenforcing 切り替えの最後のステップに登場するコマンドで、一般的には一生に 1 度打つかどうかのものです。

ラベル (context) の形 #

SELinux のすべての決定は ラベル に基づきます。ls -Zps -Z がラベルを見せるコマンドです。

ファイルのラベル
$ ls -Z /var/www/html/index.html
unconfined_u:object_r:httpd_sys_content_t:s0 /var/www/html/index.html

$ ls -Z /etc/shadow
system_u:object_r:shadow_t:s0 /etc/shadow

$ ls -Z /home/curtis
unconfined_u:object_r:user_home_dir_t:s0 /home/curtis
プロセスのラベル
$ ps -eZ | grep httpd
system_u:system_r:httpd_t:s0    1234 ?  00:00:01 httpd

$ id -Z
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

ラベルはコロンで 4 つの部分です。

ラベルの 4 フィールド
   system_u : object_r : httpd_sys_content_t : s0
   ────┬───   ───┬────   ─────────┬──────────   ─┬─
       │         │                │              │
       user      role             type           level (MLS)
フィールド意味よく見る値
userSELinux ユーザー (Linux ユーザーと違う)system_uunconfined_ustaff_u
role役割 (RBAC)object_r (ファイル)、system_r (システムプロセス)、unconfined_r
typeタイプ — 実質的にポリシーのキーhttpd_thttpd_sys_content_tshadow_tssh_port_t
levelMLS レベル (targeted ポリシーではほぼ s0)s0

targeted ポリシー では事実上 type だけ気にすればよいです。なので SELinux 作業の 90% は「どのファイルにどの type が付与されているか」を見る作業です。user と role はほぼ常に自動で合い、level は targeted で意味がありません。

よく出会う type たち #

type意味
httpd_tWeb サーバー (Apache/nginx) プロセス
httpd_sys_content_tWeb サーバーが読み取り可能な静的コンテンツ
httpd_sys_rw_content_tWeb サーバーが読み書きできるコンテンツ
shadow_t/etc/shadow のようなパスワードファイル
ssh_home_t~/.ssh 内の SSH キー
user_home_dir_t一般ユーザーホームディレクトリ
var_log_t/var/log 内のログファイル
bin_t/usr/bin 内の実行ファイル
unconfined_tSELinux 制約をほぼ受けないプロセス (ユーザーシェルなど)

これらの type と ポリシー の組み合わせで「これがあそこにアクセスしてよいか」が決定されます。たとえばポリシーが httpd_thttpd_sys_content_t の読み取りは許可しますが、httpd_tshadow_t の読み取りは絶対に許可しません。

ラベルを直す — chcon / restorecon #

運用でもっともよく出会う作業です。ファイルを間違ったパスに置いたり間違ったコマンドでコピーすればラベルが間違って付与されて SELinux が遮断します。2 つのコマンドが答えです。

chcon — 一時変更 #

chcon
$ sudo chcon -t httpd_sys_content_t /var/www/html/new.html
$ ls -Z /var/www/html/new.html
unconfined_u:object_r:httpd_sys_content_t:s0 /var/www/html/new.html

$ sudo chcon -R -t httpd_sys_content_t /var/www/html/   # 再帰

chcon手動で type を変えます。早くて直感的ですが罠があります。restorecon や relabel が起こると元のポリシーが定めた type に戻ります。 一時作業のみに。

restorecon — ポリシー基準で復元 #

restorecon
$ sudo restorecon -v /var/www/html/new.html
Relabeled /var/www/html/new.html from unconfined_u:object_r:user_home_t:s0 to unconfined_u:object_r:httpd_sys_content_t:s0

$ sudo restorecon -Rv /var/www/html/    # 再帰

restoreconポリシーがそのパスにどの type を付与すべきと定義しておいたか を見てそこに戻します。なので「ラベルが壊れた」ならほぼ常に restorecon が正解です。chcon より安全です。

一般的な流れ #

ラベル事故 → 復元流れ
   1. ファイルを新しいパスにコピー (cp / mv)
        ↓ ラベルが元のパスのものを引きずってくる (間違い)

   2. Web サーバーが読み取り拒否 (SELinux AVC denial)

   3. ls -Z で間違ったラベル確認

   4. restorecon -Rv <パス> でポリシー基準で復元

   5. 再試行 → 通過

cp はだいたい対象パスのポリシー基準で新しいラベルが付き、cp -a(archive) や mv のようなメタデータを保存する作業は元のラベルを維持できます。なので mv ~/page.html /var/www/html/ のような単純な移動がラベル事故の 1 位です。移動後には restorecon を 1 度実行するのが安全です。

永久ラベルポリシー変更 — semanage fcontext #

/var/www/html 以外に 別のディレクトリ を Web コンテンツパスとして使いたければどうしますか? そこに毎回 chcon を適用しても restorecon 1 度で解けます。ポリシー自体 を変えなければなりません。

新しいパスを httpd_sys_content_t として登録
$ sudo dnf install -y policycoreutils-python-utils
$ sudo semanage fcontext -a -t httpd_sys_content_t "/srv/www(/.*)?"
$ sudo restorecon -Rv /srv/www

3 行の意味:

  1. policycoreutils-python-utilssemanage コマンドを提供するパッケージ。RHEL 9 デフォルトインストールに入っていない場合がある
  2. semanage fcontext -a -t <type> "<regex>" — その正規表現パスパターンにその type を刻むようポリシーに登録
  3. restorecon -Rv — 登録したポリシーを実際のファイルたちに適用

これで /srv/www 内のどのファイルも常に httpd_sys_content_t になり、新しく作られるファイルも自動でその type を受けます。この方式が運用の標準 です。

登録された fcontext 確認 / 削除
$ sudo semanage fcontext -l | grep '/srv/www'
/srv/www(/.*)?    all files    system_u:object_r:httpd_sys_content_t:s0

$ sudo semanage fcontext -d "/srv/www(/.*)?"     # 登録解除

ポートにもラベルがある — semanage port #

基礎 #7 の SSH ポート変更で少し出会ったコマンドです。ポート番号にも SELinux ラベルがあって、「許可されたポートではないポートでデーモンが listen しようとすれば」ポリシーが拒否します。

ポートラベル確認 / 追加
$ sudo semanage port -l | grep ssh
ssh_port_t                     tcp      22

$ sudo semanage port -a -t ssh_port_t -p tcp 2222    # 追加
$ sudo semanage port -l | grep ssh
ssh_port_t                     tcp      2222, 22

Web サーバーポート(http_port_t)、DB ポート(postgresql_port_t) などもも同じモデルです。デーモンが標準ではないポートで起動するときは常にこのステップを経なければなりません。

Booleans — ポリシーの on/off スイッチ #

毎回ポリシーを新しく書かなくてもよく使うオプションは boolean で事前に準備されています。名前そのまま on/off するスイッチです。

boolean リスト / 検索
$ getsebool -a | head -10
abrt_anon_write --> off
abrt_handle_event --> off
...

$ getsebool -a | grep httpd | head -10
httpd_anon_write --> off
httpd_can_check_spam --> off
httpd_can_connect_ftp --> off
httpd_can_network_connect --> off
httpd_can_network_connect_db --> off
httpd_can_sendmail --> off
httpd_enable_cgi --> on
...

よく on にする項目:

よく on にする booleans
# httpd が外部ネットワークへ出るように (例: nginx → upstream)
$ sudo setsebool -P httpd_can_network_connect on

# httpd が DB(postgres/mysql など)に接続
$ sudo setsebool -P httpd_can_network_connect_db on

# NFS クライアントとしてリモートホームをマウントして SSH キー認証
$ sudo setsebool -P use_nfs_home_dirs on

-P永久適用 です。付けなければ再起動時に元に戻ります。運用ではほぼ常に -P を一緒に付けます。

現在の値 / 説明見る
$ getsebool httpd_can_network_connect
httpd_can_network_connect --> on

$ semanage boolean -l | grep httpd_can_network_connect
httpd_can_network_connect      (on   ,   on)  Allow HTTPD scripts and modules to connect to the network using TCP.

AVC denial — SELinux が遮断したシグナル #

SELinux が何かを遮断すれば AVC denial ログが残ります。 (Access Vector Cache.) トラブルシューティングの出発点です。

最近の AVC denial 見る
$ sudo ausearch -m avc -ts recent
time->Wed Apr 16 10:23:12 2026
type=AVC msg=audit(1713248592.123:456):
    avc:  denied  { read } for  pid=1234 comm="httpd"
    name="config.json" dev="vda2" ino=12345
    scontext=system_u:system_r:httpd_t:s0
    tcontext=unconfined_u:object_r:user_home_t:s0
    tclass=file permissive=0

各フィールドを確認すると:

  • denied { read } — 何を拒否したか (read / write / open / connectto など)
  • comm="httpd" — どのコマンドが試みたか
  • scontextsource context — 試みたプロセスのラベル (httpd_t)
  • tcontexttarget context — アクセスしようとしたリソースのラベル (user_home_t)
  • tclass — リソース種類 (file / dir / tcp_socket など)

この 1 行でほぼすべての診断ができます。上の例では httpd_tuser_home_t のファイルを読もうとして拒否 — ポリシーがその組み合わせを許可しなかったという意味です。答えはほぼ常に「そのファイルのラベルを httpd_sys_content_t に変えなさい」です。

journalctl でも見る #

systemd 統合ログ
$ sudo journalctl -t setroubleshoot
Apr 16 10:23:14 rhel9-lab setroubleshoot[5678]:
    SELinux is preventing httpd from read access on the file config.json.
    For complete SELinux messages run: sealert -l ...

setroubleshoot デーモンが AVC を人が読みやすいメッセージに変換してくれます。親切な案内文に近いです。

sealert — 人のためのトラブルシューティング案内文 #

setroubleshoot-server パッケージがインストールされていれば、より親切なツールが使えます。

sealert インストール
$ sudo dnf install -y setroubleshoot-server
$ sudo systemctl enable --now setroubleshootd
最近の通知を受け取って見る
$ sudo sealert -a /var/log/audit/audit.log

100% done
found 1 alerts in /var/log/audit/audit.log

--------------------------------------------------------------------------------
SELinux is preventing /usr/sbin/httpd from read access on the file config.json.

*****  Plugin restorecon (99.5 confidence) suggests   ************************
If you want to fix the label.
/var/www/html/config.json default label should be httpd_sys_content_t.
Then you can run restorecon. The access attempt may have been stopped due
to insufficient permissions to access a parent directory in which case
try this command:
# restorecon -Rv /var/www/html/config.json
...

Plugin が推奨ソリューションを自信スコアと一緒に見せてくれます。99.5% ならほぼそのまま従えばよいです。運用で SELinux トラブルシューティングの半分はこの 1 つのコマンドで解決されます。

ポリシー作成 — audit2allow #

sealert が答えを出せないとき、または直接ポリシーを作りたいときに使うコマンドです。AVC denial を入力として受けてそれを許可する ポリシーモジュール を自動で作ってくれます。

audit2allow 流れ
# 1) Permissive に下ろしてすべての作業を 1 度回す (denial ではなく would-deny まで全部ロギング)
$ sudo setenforce 0
$ # ... アプリを 1 度正常動作させる ...
$ sudo setenforce 1

# 2) 累積された AVC を集めてポリシーモジュールに変換
$ sudo ausearch -m avc -ts recent | sudo audit2allow -M myapp
$ ls
myapp.pp  myapp.te

# 3) モジュール適用
$ sudo semodule -i myapp.pp

.te ファイルが人が読めるポリシー、.pp がコンパイルされたモジュールです。.te を 1 度開いて検討して 適用するのが安全です — audit2allow は見えるすべての denial を許可に変えてくれるので、間違えればセキュリティホールをポリシーで固めてしまうことがあります。

モジュール管理
$ sudo semodule -l | grep myapp        # 適用されたモジュールリスト
$ sudo semodule -r myapp               # 削除

運用推奨順序 — (1) restorecon で解けるか先に、(2) ダメなら semanage fcontext / semanage port でポリシーに登録、(3) boolean で解けるケースか確認、(4) それでもダメなら最後に audit2allow。1~3 で解けるケースが 90% を超えます。

よく出会う罠 5 つ #

「ポートを変えたらデーモンが立ち上がりません」 #

基礎 #7 でも扱ったケース。22 → 2222 のような非標準ポートで sshd を立てれば SELinux が止めます。semanage port -a -t ssh_port_t -p tcp 2222 で登録。

「ホームディレクトリに置いた Web コンテンツが読まれません」 #

~/public_html のようなパスに Web コンテンツを置けばラベルが user_home_t なので httpd_t が読めません。2 つの方法:

解決策 2 つ
# 1) boolean で — ユーザーホームディレクトリで httpd が読むように
$ sudo setsebool -P httpd_enable_homedirs on
$ sudo setsebool -P httpd_read_user_content on

# 2) コンテンツを /var/www/html または登録したパスに移動 (運用推奨)

mv で移動したファイルが読まれません」 #

mv は元のラベルを持ってくることがあります。移動後に restorecon -Rv <パス> を 1 度実行することが安全です。

「DB が非標準ポートで開かれるべきなのに止まっています」 #

PostgreSQL の非標準ポート
$ sudo semanage port -a -t postgresql_port_t -p tcp 5433

Web・DB・メッセージングデーモンはほぼすべて自分だけのポート type があります。semanage port -l | grep <サービス> で確認。

「Container がホストディレクトリをマウントしたのに見えません」 #

Podman / Docker コンテナがホストボリュームをマウントしたのに SELinux が止めたとき。マウントオプションに :Z (専用) または :z (共有) を付ければ自動的にラベルが付きます。

Podman ボリュームマウント
$ podman run -v /host/path:/container/path:Z myimage

詳しいコンテナ + SELinux はこのシリーズ #7 で。

setroubleshoot 通知がデスクトップに出るとき #

GUI 環境では setroubleshoot がデスクトップ通知で AVC denial を出してくれます。学習環境で on にしておけば SELinux がどこで何を止めているかすぐに見えて学習が速く進みます。運用では通知が多すぎて切るケースが多いですが、学習用 VM では推奨します。

AlmaLinux / Rocky の違い #

この記事のすべてのコマンドが そのまま動作します。SELinux ポリシーは RHEL の selinux-policy パッケージをそのまま持ってきたものなので違いがありません。semanagerestoreconaudit2allowsealert すべて同一です。

よく使うコマンド 1 つの表 #

コマンドすること
getenforce / sestatus現在のモード / 詳細状態
setenforce 0/1ランタイムモード切り替え
ls -Z <file> / ps -eZ / id -Zファイル・プロセス・自分のラベル
chcon -t <type> <file>ラベル一時変更
restorecon -Rv <path>ポリシー基準でラベル復元
semanage fcontext -a -t <type> "<regex>"永久ラベルポリシー登録
semanage port -a -t <type> -p tcp <port>ポートラベル登録
getsebool -a / setsebool -P <bool> on/offboolean 設定
ausearch -m avc -ts recent最近の AVC denial
sealert -a /var/log/audit/audit.log人が読む診断
audit2allow -M <name>denial ログをポリシーモジュールに変換
semodule -l / -i / -rポリシーモジュール管理

まとめ #

この記事で押さえた絵:

  • SELinux は DAC の上にもう 1 層置く MAC — root も回避できないポリシー検査。
  • モードは Enforcing (デフォルト) / Permissive (デバッグ) / Disabled (絶対禁止)。一時は setenforce、永久は /etc/selinux/config
  • すべてのファイル・プロセス・ポートに ラベル (context) が付与されていて、targeted ポリシーでは事実上 type だけ気にすればよい。
  • ラベルを直すのは restorecon (ポリシー基準復元) が 90%、chcon (一時) は補助。永久ポリシー変更は semanage fcontext
  • 非標準ポートは semanage port で登録。
  • よく使うポリシーオプションは boolean で on/off するスイッチ — setsebool -P が標準。
  • 遮断されたときの流れ: AVC denial ログ → sealert で診断 → restorecon / semanage / boolean / 最後に audit2allow

次 — LVM #

基礎 #6 で少し見た LVM を本格的に扱います。運用ディスク管理の標準です。

#2 LVM — PV/VG/LV、スナップショット、拡張 では PV / VG / LV の 3 層の関係を直接作ってみて、ディスクが満杯になったときに新しい PV を追加して LV を増やす流れ、スナップショットでバックアップ直前の状態を捕まえて復元するパターン、そして thin provisioning と striping のような 1 段階深いオプションまで押さえます。

X