午前2時とsetenforce 0という罠
CentOS 8のEOL時、1週間以内に5台のサーバーをRocky Linuxへ急ぎで移行しなければならなかった。初日の夜、Nginxが新しいポートにbindできず、chownは正しいはずなのにPHP-FPMがpermissionエラーを出し続けた。手は震え、目はかすむ中でsetenforce 0を叩くと、すべてが動き出した。作業完了、就寝。
翌朝確認して青ざめた:本番サーバーがSELinux無効の状態で動いている。1台じゃない――5台全部だ。このままセキュリティ監査を受けたら、真っ先に減点されることは分かっていた。
そこから自問するようになった:SELinuxは何をブロックしているのか、なぜなのか、そして無効化せずに修正するにはどうすればいいのか。この記事はその答えだ――5台すべてをEnforcingモードに戻した方法で、何も壊さずに済んだ。
SELinuxへの3つのアプローチ――そのうち2つを本番環境で使ってはいけない理由
フォーラムやStackOverflowを見ると、多くのsysadminがSELinuxを3つの方法のどれかで対処している:
方法1:SELinuxを完全に無効化する(SELINUX=disabled)
この方法は古いチュートリアル、特に2018年以前のLAMPスタック構築記事でよく見られる。/etc/selinux/configを編集してrebootするだけだ。
# /etc/selinux/config
SELINUX=disabled
唯一のメリット:すぐに動く、デバッグ不要。デメリットの方がはるかに大きい――カーネルレベルのセキュリティレイヤーを完全に無効化し、後で再有効化する際にファイルシステム全体のrelabelが必要になる。150GBのサーバーでこのステップに18分近くかかった。
方法2:Permissiveモード――ログは記録するがブロックしない
SELinuxは動作し続けてpolicyをチェックするが、ブロックする代わりにaudit logに記録するだけだ。簡単に言えばdry run――違反を検出するが何もしない。
setenforce 0 # 一時的(再起動後に元に戻る)
getenforce # 確認:「Permissive」と表示される
再起動後も設定を維持するには:
vi /etc/selinux/config
# SELINUX=permissive
方法3:Enforcing――真のセキュリティ
これこそが目指すべきモードだ。policy違反は即座にブロックされ、警告も例外もない。Rocky Linuxをクリーンインストールした際のデフォルト設定だ。
setenforce 1
getenforce # 出力: Enforcing
実践的な比較:どのケースにどのモードを使うか
| モード | セキュリティ | デバッグのしやすさ | 用途 |
|---|---|---|---|
| Disabled | なし | 不要 | 本番環境では絶対に使わない |
| Permissive | ブロックなし | 最も簡単 | 開発/ステージング、またはデバッグ時 |
| Enforcing | 完全 | auditログの知識が必要 | 本番環境 — 最終目標 |
本番サーバーを運用しているなら、Disabledは最悪の選択だ。Permissiveはテスト環境には適しているが、長期的な解決策にはならない。Enforcingこそが目指すべき場所だ。
判断:Permissive → Enforcingへの計画的な移行
5台のサーバーを移行した際に使ったアプローチは次の通りだ:
- まずPermissiveを有効にする
- 数日間サービスを通常通り動かし、SELinuxにdenialを十分にログさせる
audit2allowでカスタムpolicyを作成する- policyをロードしてEnforcingに切り替える
ログが蓄積されるまで数日かかるが、その分暗中模索せずに済む――Enforcingにする前に、どのサービスが何のpermissionを必要としているか正確に把握できる。
実践的な実装ガイド
ステップ1:現在の状態を確認する
sestatus
# 出力例:
# SELinux status: enabled
# SELinuxfs mount: /sys/fs/selinux
# SELinux mount point: /sys/fs/selinux
# Loaded policy name: targeted
# Current mode: permissive
# Mode from config file: enforcing
# Policy MLS status: enabled
# Policy deny_unknown status: allowed
# Memory protection checking: actual (secure)
# Max kernel policy version: 33
「Current mode」(実行時)と「Mode from config file」(再起動後)の違いに注意。setenforceを使ってconfigファイルをまだ変更していない場合、この2つが一致しないことがある。
ステップ2:AVC denial logを読む
Permissiveモードでは、すべての違反が/var/log/audit/audit.logにログされる:
# 最新のdenialを確認
ausearch -m avc -ts recent
# またはgrepで直接確認
grep "avc: denied" /var/log/audit/audit.log | tail -20
典型的なAVC denialの例:
type=AVC msg=audit(1709712345.123:456): avc: denied { read } for pid=12345 comm="nginx" \
name="app.sock" dev="tmpfs" ino=67890 \
scontext=system_u:system_r:httpd_t:s0 \
tcontext=system_u:object_r:var_run_t:s0 tclass=sock_file permissive=1
読む順番:どのプロセスか(comm)、何をしているか(read)、どのファイルに対してか(name)、typeが一致しないため拒否された。
ステップ3:sealertで解析する(より読みやすい形式で)
auditログを直接読むのはかなり疲れる。setroubleshoot-serverが人間が読みやすい形式に変換してくれる:
dnf install -y setroubleshoot-server
# audit logに対して実行
sealert -a /var/log/audit/audit.log
sealertは具体的な提案を出してくれる――通常は実行すべきsetseboolやsemanageコマンドと、その理由の説明だ。
ステップ4:audit2allowでカスタムpolicyを作成する
サービスに特殊な動作がある場合――たとえばNginxが非標準ディレクトリからファイルを配信する場合――独自のpolicyを作成する必要がある:
# audit logからpolicyモジュールを作成
ausearch -m avc -ts recent | audit2allow -M my_nginx_custom
# 作成したpolicyをロード
semodule -i my_nginx_custom.pp
# ロード済みか確認
semodule -l | grep my_nginx
.teファイルはテキストソース、.ppはコンパイル済みpolicyだ。.teファイルは保存しておくこと――後でpolicyを修正する必要が生じた際に、最初からやり直す代わりにそのファイルを編集すればいい。
ステップ5:新しいpolicyを作る代わりにbooleanを使う
よくあるケースには既製のbooleanが用意されており、使用がより速くシンプルだ:
# httpdが外部接続できるようにする(API呼び出し、プロキシ)
setsebool -P httpd_can_network_connect 1
# httpdがホームディレクトリを読めるようにする
setsebool -P httpd_enable_homedirs 1
# nginx/apacheが非標準ポートにbindできるようにする
semanage port -a -t http_port_t -p tcp 8080
# httpd関連のbooleanをすべて確認
getsebool -a | grep httpd
ステップ6:必要に応じてファイルcontextをrelabelする
ファイルを新しいディレクトリにコピーしたり、標準パス外にファイルを作成したりすると、SELinuxのcontextが間違ってしまうことが多い。修正方法は次の通りだ:
# 現在のcontextを確認
ls -Z /var/www/html/
# Webディレクトリに正しいcontextを設定
semanage fcontext -a -t httpd_sys_content_t "/data/web(/.*)?";
restorecon -Rv /data/web/
# 1つのファイルだけ修正する場合
chcon -t httpd_sys_content_t /data/web/index.php
ステップ7:Enforcingに切り替える
数日間のPermissiveモードで新しいdenialが出なくなったら、切り替えのタイミングだ:
# 一時的に変更(即時テスト)
setenforce 1
# 30分間ログを監視
tail -f /var/log/audit/audit.log | grep denied
# 問題なければ設定ファイルに永続化
vi /etc/selinux/config
# SELINUX=enforcing
Enforcingに切り替えた直後、別のターミナルタブでtail -f audit.logを実行するようにしている。約30分待ち、アプリの主要な機能をすべてテストする。新しいdenialが出なければ完了だ。
クイックリファレンス:覚えておくべきSELinuxコマンド
# ステータス確認
sestatus
getenforce
# runtimeのmode変更
setenforce 0 # Permissive
setenforce 1 # Enforcing
# contextの確認
ls -Z /path/to/file
ps auxZ | grep nginx
# contextの修正
restorecon -Rv /path/ # policyのデフォルトに復元
chcon -t TYPE /file # 一時的に変更
# ポート
semanage port -l | grep http
semanage port -a -t http_port_t -p tcp 8443
# ログ
ausearch -m avc -ts recent
sealert -a /var/log/audit/audit.log
# Policy
audit2allow -M mypolicy < /var/log/audit/audit.log
semodule -i mypolicy.pp
semodule -l
まとめ
SELinuxの罠は無効化が簡単で、再有効化が難しいことだ。あの移行作業以来、自分でルールを決めた:SELinux Enforcingになっていないサーバーは本番環境に上げたと言えない。最初のデバッグに1〜2時間余分にかかるが、その代わりに得られるのは本物のセキュリティレイヤーだ――見せかけのセキュリティではなく。
Rocky LinuxはRHELのSELinuxの動作をそのまま維持している。上記のコマンドはすべてAlmaLinuxや他のRHELクローンでもそのまま使える。

