目次
- DACとMAC|SELinux・AppArmorが守っているもの
- パーミッションだけでは防げない攻撃
- MACは「プロセスの役割」で制限する
- SELinuxの基本要素|コンテキスト・ポリシー・ブール値
- コンテキストはすべてに付いたラベル
- ブール値は「よくある例外」のスイッチ
- Permissiveモードで調査し、audit2allowで許可する
- 「無効化」ではなく「記録だけする」モードに切り替える
- audit2allowで拒否ログからポリシーを作る
- 典型事例|Nginxが403を返す・起動しない
- 症状
- 原因
- 対処
- Ubuntu系のAppArmorはどう扱うか
- プロファイル単位でcomplain/enforceを切り替える
- まとめ
Rocky LinuxやAlmaLinuxでNginxを立てたら、パーミッションは正しいのに403が返る。ポートを変えたら起動すらしない。検索すると「SELinuxを無効化すれば直る」と出てきて、そのとおりにしたら動いた——こうした経験を持つ方は少なくないはずです。しかし無効化は「鍵が開かないからドアを外す」のと同じで、サーバーの防御層を一枚まるごと捨てています。
この記事では、SELinuxとAppArmorが何を守っているのかをDACとMACの違いから説明し、コンテキスト・ポリシー・ブール値の読み方、Permissiveモードを使った安全な調査手順、audit2allowによるポリシー追加、そしてNginxが403になる典型事例の切り分けまでを扱います。RHEL系(SELinux)とUbuntu系(AppArmor)のどちらを使っていても、「無効化せずに動かす」ための判断ができるようになります。
DACとMAC|SELinux・AppArmorが守っているもの
パーミッションだけでは防げない攻撃
通常のLinuxのアクセス制御は、ファイルの所有者が「誰に読み書きを許すか」を決める任意アクセス制御(DAC: Discretionary Access Control)です。chmod・chownで設定するパーミッションがこれにあたります。DACの弱点は「プロセスはそれを起動したユーザーの権限をすべて持つ」ことです。Nginxのワーカープロセスが脆弱性を突かれた場合、nginxユーザーが読める全ファイル、つまり /etc/passwd やアプリの設定ファイル、他サイトのコードまで読み出せてしまいます。
MACは「プロセスの役割」で制限する
強制アクセス制御(MAC: Mandatory Access Control)は、ユーザーではなくプロセスごとに「このプログラムはこの種類のファイルにしか触れない」というルールをシステム全体で強制します。Nginxは「Webコンテンツと自分のログとポート80/443」にしかアクセスできない、と決めておけば、たとえ乗っ取られても被害はその範囲で止まります。SELinux(RHEL・Rocky・Alma・Fedora・Amazon Linuxで標準)とAppArmor(Ubuntu・Debian・SUSEで標準)はどちらもこのMACを実装したものです。
| SELinux | AppArmor | |
|---|---|---|
| 主な採用ディストリ | RHEL系、Fedora、Amazon Linux | Ubuntu、Debian、openSUSE |
| 制御の単位 | ラベル(コンテキスト)ベース。ファイルやプロセスに付与された型で判定 | パスベース。プロファイルに書かれたファイルパスで判定 |
| 設定の粒度 | 細かく強力だが学習コストが高い | 読みやすく、プロファイルを自作しやすい |
| 調査用モード | Permissive | complain |
| 主なコマンド | getenforce, semanage, restorecon, audit2allow | aa-status, aa-complain, aa-enforce, aa-logprof |
SELinuxの基本要素|コンテキスト・ポリシー・ブール値
コンテキストはすべてに付いたラベル
SELinuxではファイル・プロセス・ポートのすべてに「ユーザー:ロール:タイプ:レベル」というコンテキストが付いています。実務で見るのはほぼタイプです。ls -Z や ps -Z で確認できます。
$ ls -Z /usr/share/nginx/html/index.html
system_u:object_r:httpd_sys_content_t:s0 /usr/share/nginx/html/index.html
$ ps -eZ | grep nginx
system_u:system_r:httpd_t:s0 1234 ? 00:00:00 nginx
# ポートに付いたタイプ
$ sudo semanage port -l | grep http_port_t
http_port_t tcp 80, 81, 443, 488, 8008, 8009, 8443, 9000
ポリシーは「httpd_t のプロセスは httpd_sys_content_t のファイルを読める」といった許可ルールの集合です。デフォルトのtargetedポリシーには主要なデーモン向けのルールが最初から含まれているため、ディストリ標準の配置場所を使っている限り、ほとんど何もしなくても動きます。問題が起きるのは「標準と違う場所・違うポート・違う接続先」を使ったときです。
ブール値は「よくある例外」のスイッチ
ポリシーを書き換えずに挙動を切り替えられるのがブール値です。たとえばNginxをリバースプロキシとしてバックエンドに接続させる、NFS上のコンテンツを配信する、といった典型的な要件はブール値の切り替えで済みます。
# httpd関連のブール値を一覧
getsebool -a | grep httpd
# Nginx/Apacheからバックエンド(アプリサーバー・DB)へのネットワーク接続を許可
# -P を付けると再起動後も維持される
sudo setsebool -P httpd_can_network_connect on
# 非標準ポート8080でリッスンさせたい場合はポートにタイプを追加
sudo semanage port -a -t http_port_t -p tcp 8080
semanage は policycoreutils-python-utils パッケージに含まれます。入っていない場合はdnfでインストールしてください。
Permissiveモードで調査し、audit2allowで許可する
「無効化」ではなく「記録だけする」モードに切り替える
SELinuxには3つの状態があります。Enforcing(拒否して記録)、Permissive(拒否せず記録だけ)、Disabled(完全停止)です。トラブル調査のときは一時的にPermissiveにすると、「どのアクセスが拒否されるはずだったか」をログで確認しながらアプリを動かせます。
getenforceで現在の状態を確認する。sudo setenforce 0でPermissiveに切り替える(再起動で元に戻る一時的な変更)。- 問題の操作を再現し、
sudo ausearch -m AVC -ts recentで拒否ログ(AVC: Access Vector Cache)を確認する。 - 原因に応じてコンテキスト修正・ブール値変更・ポリシー追加を行う。
sudo setenforce 1でEnforcingに戻し、再度動作確認する。
サーバー全体をPermissiveにしたくない場合は、sudo semanage permissive -a httpd_t のように特定のドメインだけPermissiveにできます。これなら他のサービスの防御は維持したまま調査できます。
audit2allowで拒否ログからポリシーを作る
ブール値でもコンテキスト修正でも解決しない場合、拒否ログをもとにカスタムポリシーを生成します。
# 直近のAVC拒否を人が読める形で確認(setroubleshoot-serverが必要)
sudo sealert -a /var/log/audit/audit.log
# 拒否ログからポリシーモジュールを生成してインストール
sudo ausearch -m AVC -ts recent | audit2allow -M my_nginx
sudo semodule -i my_nginx.pp
# 生成された内容(.teファイル)は必ず目視で確認する
cat my_nginx.te
注意点は、audit2allowは「ログにあった拒否をすべて許可するルール」を機械的に作ることです。中身を見ずにインストールすると、攻撃の痕跡まで許可してしまう可能性があります。.te ファイルを開き、「なぜこのアクセスが必要なのか」を説明できる行だけ残すのが正しい使い方です。
典型事例|Nginxが403を返す・起動しない
症状
Rocky Linux 9でNginxを構築し、コンテンツを /srv/www/site に配置しました。chmod 755 と chown nginx も済ませ、curl でアクセスすると403。Nginxのエラーログには open() "/srv/www/site/index.html" failed (13: Permission denied) と出ています。DACのパーミッションは正しく、ファイアウォールも開いています。
原因
ls -Z で確認すると、ファイルのタイプが user_home_t や var_t になっていました。ホームディレクトリで展開したアーカイブを mv で移動したため、元の場所のコンテキストがそのまま引き継がれたのです(cp なら移動先のデフォルトが付きますが、mv はラベルを保持します)。httpd_t プロセスは user_home_t のファイルを読めないため、SELinuxが拒否していました。
対処
# 恒久的なルールとして「/srv/www以下はWebコンテンツ」と登録
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/www(/.*)?"
# 登録したルールに従ってラベルを付け直す
sudo restorecon -Rv /srv/www
# アップロード先など書き込みが必要なディレクトリは別タイプにする
sudo semanage fcontext -a -t httpd_sys_rw_content_t "/srv/www/site/uploads(/.*)?"
sudo restorecon -Rv /srv/www/site/uploads
検索で見つかる chcon コマンドでもその場では直りますが、chcon は一時的な変更で、restorecon やシステムの再ラベル時に元に戻ります。必ず semanage fcontext でルールを登録してから restorecon を実行してください。同様に、bind() to 0.0.0.0:8080 failed (13: Permission denied) で起動しない場合はポートタイプの未登録、リバースプロキシで502になる場合は httpd_can_network_connect がoffである、というのが定番の原因です。Nginxの基本構築はLinux×Nginx入門、ファイアウォール側の切り分けはLinuxファイアウォール設定入門を参照してください。
Ubuntu系のAppArmorはどう扱うか
プロファイル単位でcomplain/enforceを切り替える
AppArmorはSELinuxと違い、プログラムごとのプロファイル(/etc/apparmor.d/ 配下のテキストファイル)でアクセス可能なパスを列挙します。プロファイルがないプログラムは制限されません。UbuntuではMySQLやsnap系パッケージなどにプロファイルが同梱されていますが、Nginxには標準で付属しないため、SELinuxのような「Nginxが403」のトラブルはUbuntuではほとんど起きません。代わりにMySQLのデータディレクトリを変更したときに apparmor="DENIED" で起動できない、という事例がよくあります。
# 状態確認(apparmor-utilsパッケージ)
sudo aa-status
# 特定プロファイルを調査モード(拒否せず記録)にする
sudo aa-complain /etc/apparmor.d/usr.sbin.mysqld
# 拒否ログを確認
sudo journalctl -k | grep 'apparmor="DENIED"'
# ログをもとに対話的にプロファイルへ許可を追加
sudo aa-logprof
# 確認できたら強制モードに戻す
sudo aa-enforce /etc/apparmor.d/usr.sbin.mysqld
MySQLのデータディレクトリを /data/mysql に移す場合は、/etc/apparmor.d/local/usr.sbin.mysqld に /data/mysql/ r, と /data/mysql/** rwk, を追記し、sudo systemctl reload apparmor で反映します。パスベースなので、SELinuxのラベル付け直しのような概念はなく、「新しい場所を書き足す」だけで済むのがAppArmorのわかりやすさです。
まとめ
SELinuxとAppArmorは「面倒な障害の原因」ではなく、プロセスが乗っ取られたときの被害範囲を限定する防御層です。要点を振り返ります。
- DACは「誰が」、MACは「どのプログラムが」で制限する。両方あって初めて多層防御になる。
- トラブル時はDisabledではなくPermissive(AppArmorはcomplain)で調査する。
- 解決の順番は、コンテキスト修正(semanage fcontext + restorecon)→ ブール値 → audit2allowによるポリシー追加。
chconは一時的、semanage fcontextが恒久的。- audit2allowの生成結果は必ず目視確認する。
まずは今動いているサーバーで getenforce や aa-status を実行し、無効化されていないかを確認してみてください。もしDisabledになっていたら、まずPermissiveに戻してログを1週間眺めるところからが現実的な第一歩です。セキュリティ層を維持したままのサーバー構築や、既存環境の見直しでお悩みの際は、Harmonic Societyのシステム開発・インフラ支援にご相談ください。
Harmonic Society
この記事の内容、自社の業務でも活かせそうですか?
ローカルLLM・AI・クラウドなどの技術導入を、要件整理からPoC・社内展開まで代表エンジニアが伴走します。オンライン対応・全国OK。まずは30分の無料相談から。売り込みはしません。
関連記事
Related / 9 articles
Notes & Insights
- プログラミング
DDoS攻撃の仕組みと対策入門|レイヤー別の防御とCDN・クラウドの活用
DDoS攻撃をボリューム型・プロトコル型・アプリ層に分けて仕組みを解説し、自前サーバーで防げない理由、CloudflareやAWS Shieldの標準防御、オリジンIPの隠し方、レートリミットとBot対策、攻撃を受けたときの初動、費用が跳ね上がるDenial of Walletへの備えまでわかります。
- プログラミング
WAFとは?仕組み・導入パターン・誤検知対策|Webアプリを攻撃から守る実践ガイド
WAFがファイアウォールやIDSと何が違うのか、シグネチャとマネージドルールの仕組み、Cloudflare WAF・AWS WAF・ModSecurityの比較、フォーム送信がブロックされる誤検知の調査と例外設定、ログ監視、WAFが代替できないことまで実践的に解説します。
- プログラミング
セキュリティヘッダー入門|CSP・HSTS・X-Frame-Optionsの設定と効果を実践解説
CSP・HSTS・X-Frame-Options・X-Content-Type-Optionsなど主要セキュリティヘッダーが防ぐ攻撃と、CSPのReport-Onlyからの段階導入、nonce/hash、HSTS preloadの不可逆リスク、Nginx・Next.js・Astroでの設定例、確認方法を解説します。
- プログラミング
クラウドの通信費(Egress)入門|データ転送量課金の仕組みと転送コストを抑える設計
クラウドの「受信無料・送信有料」の原則、AZ間・リージョン間・インターネット向けの単価差、NATゲートウェイ処理料の罠、CDNで転送量を減らす方法、バックアップやログ転送の見落とし、請求書で転送料を特定する手順を解説。想定外の請求を防げます。
- プログラミング
秘密情報をGitに入れない仕組み|.gitignore・git-secrets・履歴から漏れた鍵の削除
APIキーや.envをGitにコミットしてしまう典型経路と、.gitignore・.env.exampleの運用、pre-commitでのgitleaks検知、GitHub secret scanningの活用、漏れた鍵の無効化と履歴書き換え(git filter-repo)の手順を解説。仕組みで再発を防げます。
- プログラミング
開発・ステージング・本番環境の分離設計|環境差分をなくす構成とアクセス制御
開発・ステージング・本番それぞれの目的と、構成をコードで揃える方法、環境別の設定注入、本番データを使わないテストデータ戦略、ステージングの保護(Basic認証・IP制限・noindex)、コストを抑える運用までを解説。環境差分による本番障害を防げます。
- プログラミング
ngrok・Cloudflare Tunnelでローカルを公開|Webhook開発とデモ環境の作り方
NAT内のローカル環境にStripeやLINEのWebhookを届けるトンネリングの仕組みを解説。ngrok・Cloudflare Tunnel・localtunnelの比較、固定ドメインと認証、リクエスト検査、公開時のセキュリティ、自宅サーバー公開への応用までわかります。
- プログラミング
ローカル開発環境のHTTPS化|mkcert・hostsファイル・自己署名証明書の正しい使い方
ローカル開発をHTTPS前提にすべき理由(Secure Cookie・Service Worker・OAuth)と、mkcertでローカルCAを作りhostsで独自ドメインを割り当ててVite・Next.js・Dockerで使う手順を解説。証明書警告を無視する癖の危険も理解できます。
- プログラミング
localhost・0.0.0.0・127.0.0.1の違い|ポートとUnixソケットを理解して「つながらない」を解決
localhost・127.0.0.1・0.0.0.0の意味の違い、Dockerで外から接続できない原因、host.docker.internal、ポート競合の調べ方、Unixソケットの利点と権限、1024未満ポートの制約を解説。「つながらない」を仕組みから解決できます。
Harmonic Society
「読んで終わり」にせず、自社の業務で試してみませんか?
AI・ローカルLLM・クラウドの導入を、要件整理からPoC・社内展開まで代表エンジニアが伴走します。オンライン対応・全国OK・売り込みなし。
無料・30分・オンラインOK|1営業日以内に返信します