課題:原因不明の「Permission Denied」エラー
/srv/www/site のようなカスタムドキュメントルートを指定して、新規にNginxバーチャルホストをデプロイしたとします。ディレクトリの権限設定に不備はなく、ls -l で確認しても所有者は nginx:nginx、パーミッションは 755 と完璧に見えます。しかし、テストアクセスを行うと即座に 403 Forbidden が返されてしまいます。
/var/log/nginx/error.log を確認すると、見慣れたログが記録されています。open() "/srv/www/site/index.html" failed (13: Permission denied)。標準的なUNIXパーミッション上ではワーカープロセスに読み取り権限があるにもかかわらず、カーネルによって読み取り処理が拒絶されているのです。
この壁に直面したとき、多くの管理者は安易な解決策に走りがちです。setenforce 0 を実行したり、/etc/selinux/config で SELINUX=disabled を設定したりしてSELinuxを無効化してしまうことです。確かにエラーは一瞬で解消されます。しかし、Security-Enhanced Linux(SELinux)を無効化すると多層防御の最も強固な防壁が失われ、万が一アプリケーションデーモンが侵害された際にホスト全体が危険に晒されることになります。
根本原因:任意アクセス制御(DAC)と強制アクセス制御(MAC)
従来のLinuxファイルセキュリティは任意アクセス制御(DAC: Discretionary Access Control)に依存しています。アクセス可否は所有者ID、グループ所属、および標準の rwx ビットのみに基づいて判定されます。仮に攻撃者がNginxのリモートコード実行(RCE)脆弱性を悪用した場合、UID 998(nginx)の権限を奪取します。その結果、一時的なデータベースダンプファイルや機密性の高い内部設定ファイルなど、そのユーザーやグループにアクセス権があるシステム上のあらゆるファイルを即座に読み取ることが可能になります。
一方、SELinuxはLinuxカーネル内で直接強制アクセス制御(MAC: Mandatory Access Control)を適用します。すべてのプロセス、ファイル記述子、ディレクトリ、ネットワークソケットには、明示的なセキュリティコンテキストラベルが付与されています。SELinuxはアクセスを許可する前にアクティブなポリシープールを参照します。たとえDACパーミッションで rwxrwxrwx(0777)が付与されていても、セキュリティポリシーでそのドメインとタイプ間のやり取りが禁止されていれば、SELinuxはそのリクエストを遮断します。
主要な概念:コンテキスト、動作モード、ブール値
SELinuxの効果的な運用管理は、以下の3つの要素を理解することに集約されます。
1. セキュリティコンテキスト
すべてのファイルおよびプロセスのラベルは、コロンで区切られた4つのフィールド user:role:type:level で構成されています。3番目のフィールドであるタイプ(実行中のプロセスに割り当てられている場合はドメイン)が、タイプ強制(Type Enforcement)ルールを規定します。
ファイルのコンテキストを確認するには、ls コマンドに -Z フラグを付与します。
ls -lZ /var/www/html/index.html
# 出力結果:
# -rw-r--r--. 1 root root system_u:object_r:httpd_sys_content_t:s0 242 Oct 12 10:14 index.html
実行中のプロセスのコンテキストを確認するには、ps コマンドを使用します。
ps -eZ | grep nginx
# 出力結果:
# system_u:system_r:httpd_t:s0 1842 ? 00:00:01 nginx
このTargetedポリシーのルールにおいて、カーネルは httpd_t ドメインに制限されたプロセスに対して、httpd_sys_content_t タイプが付与されたファイルの読み取りを許可しています。
2. 動作モード
SELinuxには3つの動作ステートが存在します。
- Enforcing: ルールが厳格に適用されます。拒絶された操作は即座にブロックされ、監査ログに記録されます。
- Permissive: アクセス拒否はデバッグ用にログ記録されますが、操作自体はそのまま実行が許可されます。ステージング環境での検証に最適です。
- Disabled: SELinuxカーネルサブシステムが完全に無効化されます。再有効化するには、再起動時にファイルシステム全体の再ラベル付け(relabel)が必要になります。
再起動することなく、実行時に動作モードを切り替えることができます。
# 現在の動作モードを確認する
getenforce
# トラブルシューティング用にPermissiveモードへ切り替える
sudo setenforce 0
# デバッグ完了後、Enforcingモードへ戻す
sudo setenforce 1
3. SELinuxブール値(Booleans)
ブール値とは、セキュリティポリシー内にコンパイルされたランタイム機能フラグです。カスタムポリシーコードを作成することなく、事前定義されたトグルを切り替えるだけで、Webデーモンによるネットワーク接続開始やメール配信エージェントの実行といった標準的な処理を許可できます。
実践手順:ラベルの修正とアクセス拒否の解決
ステップ1:主要管理ツールのインストール
RHEL 9、Rocky Linux、AlmaLinux、またはFedoraでは、ポリシーユーティリティと監査ログ解析デーモンをインストールします。
sudo dnf install -y policycoreutils-python-utils setroubleshoot-server
ステップ2:ファイルコンテキストの恒久的な修正
/srv/www/site のようなカスタムWebディレクトリを作成すると、ファイルシステムはフォールバック用ラベル(通常は default_t や var_t)を付与します。httpd_t プロセスドメインは、これらの一般的なタイプにアクセスできません。
chcon の使用は避けてください。これはラベルを一時的に変更するだけであり、メンテナンス時の再ラベル付け処理によって手動の変更内容がすべて消去されてしまいます。常に semanage を使ってシステムポリシーデータベースにコンテキストルールを書き込み、その上で restorecon を使って適用します。
# 1. 恒久的なラベリングルールをポリシーデータベースに登録する
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/www/site(/.*)?"
# 2. 登録したコンテキストをディスク上へ再帰的に適用する
sudo restorecon -Rv /srv/www/site
ユーザーによるファイルのアップロードやキャッシュの書き込みが必要な場合(例: WordPressが /srv/www/site/uploads にキャッシュを保存する場合)、代わりに読み書き可能なタイプ httpd_sys_rw_content_t を適用します。
sudo semanage fcontext -a -t httpd_sys_rw_content_t "/srv/www/site/uploads(/.*)?"
sudo restorecon -Rv /srv/www/site/uploads
ステップ3:ブール値によるネットワークアクセスの切り替え
Nginx Webアプリケーションからポート5432で稼働するPostgreSQLデータベースへのクエリ発行や、HTTPS経由でのアップストリームAPIへの接続が必要になったとします。デフォルトのTargetedポリシーでは、HTTPデーモンによる外部向けネットワークソケットの確立は禁止されています。
getsebool を使用して関連するブール値を検索します。
getsebool -a | grep httpd_can_network
必要な機能をピンポイントで有効化します。-P フラグを付与することで、システムの再起動後も設定を永続化できます。
# Webアプリからリレーショナルデータベースへの接続のみを許可する
sudo setsebool -P httpd_can_network_connect_db on
# または、一般的な外部ネットワーク接続を一括で許可する
sudo setsebool -P httpd_can_network_connect on
ステップ4:ausearch、sealert、audit2allowによるアクセス拒否ログの解析
カーネルがシステムコールを遮断すると、/var/log/audit/audit.log にアクセスベクトルキャッシュ(AVC: Access Vector Cache)のレコードが書き込まれます。
type=AVC msg=audit(1710328912.421:842): avc: denied { read } for pid=1842 comm="nginx" name="index.html" dev="nvme0n1p2" ino=2097156 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:default_t:s0 tclass=file permissive=0
ausearch を使って直近の拒否ログを抽出します。
sudo ausearch -m AVC -ts recent
アクションが失敗した理由と解決方法を分かりやすい解説で確認するには、ログを sealert に渡します。
sudo sealert -a /var/log/audit/audit.log
ブール値が存在しない独自の内部デーモンやサードパーティ製バイナリエージェントを扱う場合は、audit2allow を使って個別のType Enforcementモジュールを生成します。
# 1. 拒否ログから生成される人間が判読可能なポリシードラフトをプレビューする
sudo grep "mydaemon" /var/log/audit/audit.log | audit2allow -m mydaemon_custom
# 2. ルールをソースファイル (.te) およびバイナリポリシーパッケージ (.pp) にコンパイルする
sudo grep "mydaemon" /var/log/audit/audit.log | audit2allow -M mydaemon_custom
# 3. 有効化する前に、必ず生成された .te ソースコードを確認する
cat mydaemon_custom.te
# 4. コンパイルされたポリシーパッケージをカーネルにロードする
sudo semodule -i mydaemon_custom.pp
生成されたポリシーモジュールを決して盲目的にインストールしないでください。もしWebデーモンの拒否ログが、悪意あるコードによる /etc/shadow への読み取り試行によるものだった場合、その拒否を audit2allow でコンパイルしてしまうと、攻撃者にパスワードハッシュへのアクセス権を恒久的に与えることになります。
実践における基本原則
一貫した手順に従えば、SELinuxの運用は決して難しくありません。ファイルラベルは ls -Z で確認し、永続的なディレクトリパスの登録には semanage fcontext を使用します。運用上必要な機能は setsebool -P で解放し、audit2allow の利用は独自の内部サービスのみに留めましょう。AVCの拒否ログは単なる障害ではなく、原因究明のための重要な手がかりとして捉えることが大切です。SELinuxをEnforcingモードで維持していれば、万一Webサービスが侵害された場合でも、その被害をドメイン内に封じ込め、サーバー全体の乗っ取りを防ぐことができます。

