RHEL 中級 #1 SELinux 入門 — Enforcing/Permissive、ラベル、トラブルシューティング
基礎 #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 を回避できません。
リクエスト: 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 | ポリシー違反を 許可 + ログだけ残す | デバッグ・ポリシー作成時の一時 |
| Disabled | SELinux 自体が動作しない | ❌ 運用絶対禁止 |
確認:
$ 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: 33sestatus がより豊かな情報です。Loaded policy name が targeted であるのが一般的です (ほとんどの環境で)。MLS は軍/政府などの特殊環境でのみ使用します。
一時的な切り替え — setenforce
#
$ sudo setenforce 0 # Permissive へ (再起動すれば元に戻る)
$ sudo setenforce 1 # Enforcing へ
$ getenforce
Permissiveデバッグ中だけ ちょっと Permissive に下げておいて、終わればまた Enforcing に上げます。
永久切り替え — /etc/selinux/config
#
SELINUX=enforcing
SELINUXTYPE=targetedSELINUX= の値を permissive / enforcing / disabled に変えて再起動すれば適用されます。ただし:
disabledは絶対に使わないでください。 一度 disabled で起動すると、すべてのファイルのラベルが解けて、再び Enforcing に上げるときに全体ファイルシステムの relabel (数十分~数時間) が必要です。- 一時的に SELinux 検査を切りたければ 常に
permissiveで。デバッグ・ログ収集は Permissive でも同様に行えます。
autorelabel — ラベルが壊れたとき #
何かおかしくなって全体ファイルシステムのラベルを再付与したいとき:
$ sudo touch /.autorelabel
$ sudo reboot起動時にすべてのファイルをポリシー基準で再ラベルします。disabled → enforcing 切り替えの最後のステップに登場するコマンドで、一般的には一生に 1 度打つかどうかのものです。
ラベル (context) の形 #
SELinux のすべての決定は ラベル に基づきます。ls -Z と ps -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 つの部分です。
system_u : object_r : httpd_sys_content_t : s0
────┬─── ───┬──── ─────────┬────────── ─┬─
│ │ │ │
user role type level (MLS)| フィールド | 意味 | よく見る値 |
|---|---|---|
| user | SELinux ユーザー (Linux ユーザーと違う) | system_u、unconfined_u、staff_u |
| role | 役割 (RBAC) | object_r (ファイル)、system_r (システムプロセス)、unconfined_r |
| type | タイプ — 実質的にポリシーのキー | httpd_t、httpd_sys_content_t、shadow_t、ssh_port_t |
| level | MLS レベル (targeted ポリシーではほぼ s0) | s0 |
targeted ポリシー では事実上 type だけ気にすればよいです。なので SELinux 作業の 90% は「どのファイルにどの type が付与されているか」を見る作業です。user と role はほぼ常に自動で合い、level は targeted で意味がありません。
よく出会う type たち #
| type | 意味 |
|---|---|
httpd_t | Web サーバー (Apache/nginx) プロセス |
httpd_sys_content_t | Web サーバーが読み取り可能な静的コンテンツ |
httpd_sys_rw_content_t | Web サーバーが読み書きできるコンテンツ |
shadow_t | /etc/shadow のようなパスワードファイル |
ssh_home_t | ~/.ssh 内の SSH キー |
user_home_dir_t | 一般ユーザーホームディレクトリ |
var_log_t | /var/log 内のログファイル |
bin_t | /usr/bin 内の実行ファイル |
unconfined_t | SELinux 制約をほぼ受けないプロセス (ユーザーシェルなど) |
これらの type と ポリシー の組み合わせで「これがあそこにアクセスしてよいか」が決定されます。たとえばポリシーが httpd_t → httpd_sys_content_t の読み取りは許可しますが、httpd_t → shadow_t の読み取りは絶対に許可しません。
ラベルを直す — chcon / restorecon
#
運用でもっともよく出会う作業です。ファイルを間違ったパスに置いたり間違ったコマンドでコピーすればラベルが間違って付与されて SELinux が遮断します。2 つのコマンドが答えです。
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 — ポリシー基準で復元
#
$ 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 度で解けます。ポリシー自体 を変えなければなりません。
$ sudo dnf install -y policycoreutils-python-utils
$ sudo semanage fcontext -a -t httpd_sys_content_t "/srv/www(/.*)?"
$ sudo restorecon -Rv /srv/www3 行の意味:
policycoreutils-python-utils—semanageコマンドを提供するパッケージ。RHEL 9 デフォルトインストールに入っていない場合があるsemanage fcontext -a -t <type> "<regex>"— その正規表現パスパターンにその type を刻むようポリシーに登録restorecon -Rv— 登録したポリシーを実際のファイルたちに適用
これで /srv/www 内のどのファイルも常に httpd_sys_content_t になり、新しく作られるファイルも自動でその type を受けます。この方式が運用の標準 です。
$ 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, 22Web サーバーポート(http_port_t)、DB ポート(postgresql_port_t) などもも同じモデルです。デーモンが標準ではないポートで起動するときは常にこのステップを経なければなりません。
Booleans — ポリシーの on/off スイッチ #
毎回ポリシーを新しく書かなくてもよく使うオプションは boolean で事前に準備されています。名前そのまま on/off するスイッチです。
$ 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 にする項目:
# 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.) トラブルシューティングの出発点です。
$ 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"— どのコマンドが試みたかscontext— source context — 試みたプロセスのラベル (httpd_t)tcontext— target context — アクセスしようとしたリソースのラベル (user_home_t)tclass— リソース種類 (file / dir / tcp_socket など)
この 1 行でほぼすべての診断ができます。上の例では httpd_t が user_home_t のファイルを読もうとして拒否 — ポリシーがその組み合わせを許可しなかったという意味です。答えはほぼ常に「そのファイルのラベルを httpd_sys_content_t に変えなさい」です。
journalctl でも見る #
$ 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 パッケージがインストールされていれば、より親切なツールが使えます。
$ 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 を入力として受けてそれを許可する ポリシーモジュール を自動で作ってくれます。
# 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 つの方法:
# 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 が非標準ポートで開かれるべきなのに止まっています」 #
$ sudo semanage port -a -t postgresql_port_t -p tcp 5433Web・DB・メッセージングデーモンはほぼすべて自分だけのポート type があります。semanage port -l | grep <サービス> で確認。
「Container がホストディレクトリをマウントしたのに見えません」 #
Podman / Docker コンテナがホストボリュームをマウントしたのに SELinux が止めたとき。マウントオプションに :Z (専用) または :z (共有) を付ければ自動的にラベルが付きます。
$ 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 パッケージをそのまま持ってきたものなので違いがありません。semanage、restorecon、audit2allow、sealert すべて同一です。
よく使うコマンド 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/off | boolean 設定 |
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 段階深いオプションまで押さえます。