RHEL 上級 #4 SELinux 上級 — ポリシー作成と audit2allow
中級 #1 SELinux 入門 でモード、ラベル、AVC 拒否、audit2allow で解く流れまで見ました。この記事はその上の 1 階です。audit2allow が作る成果物の正体、足りないときに直接 .te ファイルを書いてコンパイルする手順、ポリシーモジュールをシステムに永続登録して次回ブート後も生き残らせる流れまで 1 サイクルで扱います。拒否が見えるたびに setenforce 0 で逃げず、最後まで追ってモジュールに固めるのが目標です。
RHEL 上級 シリーズでこの記事の位置:
- #1 ブートプロセス — GRUB2、dracut、レスキューモード
- #2 カーネルチューニング — sysctl、tuned、kdump
- #3 パフォーマンス分析 — sar、top/htop、iostat、vmstat、perf
- #4 SELinux 上級 — ポリシー作成と audit2allow ← この記事
- #5 セキュリティ強化 — auditd、OpenSCAP、FIPS
- #6 Subscription / Satellite / Insights
- #7 Cockpit による GUI 管理と Web Console
ポリシーファイルの正体 — .te / .fc / .if #
SELinux ポリシーモジュールは次の 3 種類のソースファイルで構成されます。
| ファイル | 何を入れるか |
|---|---|
*.te | Type Enforcement — allow ルール、type 宣言 |
*.fc | File Contexts — どのパスにどのラベルを付けるか |
*.if | Interface — 他モジュールから呼び出せるマクロ (任意) |
.te が核心です。.fc は restorecon が参照するラベルマッピング、.if はモジュールをライブラリのように再利用するためのインタフェースです。小さなユーザモジュールは通常 .te 1 つで完結します。
コンパイルパイプライン #
mymodule.te ──checkmodule──▶ mymodule.mod ──semodule_package──▶ mymodule.pp
│
▼
semodule -i
(システム登録).pp (policy package) が実際にシステムにインストールされる単位。audit2allow -M も結局このパイプラインを 1 コマンドで束ねて回します。
audit2allow — 拒否をモジュールに #
中級 #1 で見たコマンドを再度見ます。AVC 拒否が出たときにそれを許すポリシーを自動生成します。
# 1. 何が拒否されたか確認
$ sudo ausearch -m AVC -ts recent
# 2. 拒否の束をモジュールに変換
$ sudo ausearch -m AVC -ts recent | audit2allow -M mywebapp
# 出力:
# mywebapp.te — 人が読めるポリシーソース
# mywebapp.pp — コンパイル済みポリシーパッケージ
# 3. システムに登録
$ sudo semodule -i mywebapp.pp生成された .te を覗くのが核心です。audit2allow は拒否された動作をそのまま許可するルールしか作らないため、攻撃者が引き起こした意図しない拒否までそのまま許可してしまう恐れ があります。モジュール登録前に必ず人が確認します。
生成された .te の構造 #
module mywebapp 1.0;
require {
type httpd_t;
type postgresql_port_t;
class tcp_socket name_connect;
}
#============= httpd_t ==============
allow httpd_t postgresql_port_t:tcp_socket name_connect;| ブロック | 意味 |
|---|---|
module mywebapp 1.0; | モジュール名とバージョン |
require { ... } | このモジュールが参照する type、class、attribute 宣言 |
allow ... | 実際の許可ルール |
読み方: httpd_t タイプのプロセスが postgresql_port_t タイプの TCP ソケットに name_connect (ポート接続) するのを許可します。
audit2allow の限界 #
- 新しい type/attribute は宣言できない — 既にシステムにある type のみ参照可能。新 type を作るには直接
.te。 - 拒否されたルールのみ — 一度も拒否されていない動作はルール化できません。本番環境で一度通しで動かしてからモジュール化が定石。
- 危険なルールもそのまま —
dac_override、setuidのような capability 拒否もそのまま許可してしまいます。人による確認は必須。
.te を直接書いてコンパイル #
audit2allow の結果では足りない、または新 type を導入したいときは直接書きます。
最初のモジュール — sample app ポリシー #
仮のシナリオ。社内デーモン myappd が /var/lib/myapp/ にデータを書き、/var/log/myapp.log にログを残します。標準の方法で SELinux ポリシーを与えます。
policy_module(myapp, 1.0)
########################################
#
# 宣言
#
type myapp_t;
type myapp_exec_t;
init_daemon_domain(myapp_t, myapp_exec_t)
type myapp_data_t;
files_type(myapp_data_t)
type myapp_log_t;
logging_log_file(myapp_log_t)
########################################
#
# myapp_t ルール
#
allow myapp_t myapp_data_t:dir { read write add_name remove_name search };
allow myapp_t myapp_data_t:file { create read write append unlink open getattr setattr };
allow myapp_t myapp_log_t:file { create append read open getattr setattr };
logging_log_filetrans(myapp_t, myapp_log_t, file)policy_module(myapp, 1.0) で始まる形式は reference policy マクロ方式。RHEL の標準です。上記:
| 宣言 | 意味 |
|---|---|
type myapp_t | デーモンプロセスのドメイン type |
type myapp_exec_t | 実行ファイルの type |
init_daemon_domain(myapp_t, myapp_exec_t) | systemd が myapp_exec_t 実行ファイルを起動すると myapp_t ドメインに遷移 |
type myapp_data_t | データファイル type |
type myapp_log_t | ログファイル type |
files_type(...) | 一般ファイルシステムに置く type として登録 |
logging_log_file(...) | ログファイル type として登録 |
ファイルコンテキスト定義 #
/usr/sbin/myappd -- gen_context(system_u:object_r:myapp_exec_t,s0)
/var/lib/myapp(/.*)? gen_context(system_u:object_r:myapp_data_t,s0)
/var/log/myapp\.log.* -- gen_context(system_u:object_r:myapp_log_t,s0)(/.*)? はディレクトリ自身とその下すべてのパス。-- は通常ファイルのみ、-d はディレクトリのみ、省略するとすべて。
コンパイルとインストール #
reference policy マクロ (policy_module、gen_context など) を使うには reference policy のビルド環境が必要です。
$ sudo dnf install -y selinux-policy-devel$ ls /usr/share/selinux/devel/Makefile
/usr/share/selinux/devel/Makefile
$ make -f /usr/share/selinux/devel/Makefile myapp.pp
Compiling targeted myapp module
...
Creating targeted myapp.pp policy package
rm tmp/myapp.mod tmp/myapp.mod.fc$ sudo semodule -i myapp.pp
# インストール確認
$ sudo semodule -l | grep myapp
myapp
# ラベル適用
$ sudo restorecon -Rv /usr/sbin/myappd /var/lib/myapp /var/log/myapp.logこれで myappd デーモンが myapp_t ドメインで動き、自分のデータディレクトリとログファイルにのみアクセスするポリシーがシステムに永続登録されます。
モジュール削除 #
$ sudo semodule -r myappsemodule -r は myapp.pp ではなく モジュール名 を受け取ります。.pp 拡張子なしで。
type、attribute、allow の文法 #
type と attribute #
type はオブジェクトに付けるラベル自体、attribute は type の束です。
attribute web_server_domain;
typeattribute httpd_t web_server_domain;
typeattribute nginx_t web_server_domain;
# attribute 単位でルール — httpd_t と nginx_t 両方に適用
allow web_server_domain http_port_t:tcp_socket { name_bind name_connect };web_server_domain attribute に新しい Web サーバ type を追加するだけで、上の allow ルールが自動でその type にも適用されます。ポリシーをきれいに保つ核心テクニック。
allow ルールの形 #
allow <ソース type> <対象 type>:<class> { <permission ...> };- ソース type — 行為の主体 (通常はプロセスドメイン)
- 対象 type — 行為対象 (ファイル、ソケット、ディレクトリなど)
- class — オブジェクトの種類 (file、dir、tcp_socket、…)
- permission — 何を許可するか (read、write、execute、name_bind、…)
# myapp_t が自分のデータファイルを読み書き
allow myapp_t myapp_data_t:file { read write open getattr };
# myapp_t が自分のデータディレクトリにファイル作成
allow myapp_t myapp_data_t:dir { add_name write search };
# myapp_t が 8080 ポートにバインド
allow myapp_t http_port_t:tcp_socket name_bind;class 別に有効な permission 一覧は /etc/selinux/targeted/contexts/files/file_contexts と seinfo コマンドで確認します。
$ sudo dnf install -y setools-console
$ seinfo --class file -x # file class の全 permission
$ seinfo -t myapp_t # type 情報
$ sesearch -A -s httpd_t # httpd_t がソースの全 allow ルールブール値の深掘り #
中級 #1 で軽く見たブール値を再度見ます。ポリシーの中に if 分岐を埋め込んだスイッチ です。ポリシー自体を再ビルドせずにポリシーの動作を変えられます。
$ getsebool -a | head
abrt_anon_write --> off
abrt_handle_event --> off
abrt_upload_watch_anon_write --> on
...
# 特定のブール値
$ getsebool httpd_can_network_connect
httpd_can_network_connect --> off
# 変更 (永続)
$ sudo setsebool -P httpd_can_network_connect on
# 変更 (一時)
$ sudo setsebool httpd_can_network_connect on-P が永続適用。なしだと再起動で消えます。
自分のモジュールにブール値を埋め込む #
gen_tunable(myapp_can_network_connect, false)
tunable_policy(`myapp_can_network_connect',`
corenet_tcp_connect_all_ports(myapp_t)
')このポリシーをコンパイル/インストールすると myapp_can_network_connect というブール値がシステムに登録されます。
$ getsebool myapp_can_network_connect
myapp_can_network_connect --> off
$ sudo setsebool -P myapp_can_network_connect on運用者がポリシー再コンパイルなしに 1 行でポリシー動作を変えられるようにする標準テクニック。
デバッグフロー #
拒否が出たときに最後まで追う 1 サイクルを整理。
1. 拒否確認 #
# audit ログから直接
$ sudo ausearch -m AVC -ts recent
# より親切な解説 (setroubleshoot)
$ sudo dnf install -y setroubleshoot-server
$ sudo journalctl -t setroubleshoot --since "10 min ago"
# sealert で解析
$ sudo sealert -a /var/log/audit/audit.logsetroubleshoot が最も役立ちます。拒否の原因候補と一般的な解決策を自然言語で説明します。
2. dontaudit ルールで見えないとき #
デフォルトポリシーには騒がしい拒否を隠す dontaudit ルールが多いです。デバッグ中は一時的に無効化します。
$ sudo semodule -DB
# デバッグ完了後に再度有効化
$ sudo semodule -B3. Permissive ドメインに隔離 #
システム全体を Permissive に落とさず、疑わしいドメインだけ Permissive に:
$ sudo semanage permissive -a myapp_t
# 解除
$ sudo semanage permissive -d myapp_t
# 現在 Permissive のドメイン一覧
$ sudo semanage permissive -lこの状態で正常動作のフローを最後まで一度回し、溜まった拒否を audit2allow でモジュール化 → Enforcing に復帰。
4. モジュール作成と登録 #
$ sudo ausearch -m AVC -ts recent | audit2allow -M mywebapp_extra
$ less mywebapp_extra.te # 人による確認!
$ sudo semodule -i mywebapp_extra.pp5. 検証 #
$ sudo semodule -l | grep mywebapp
$ sudo semanage permissive -d myapp_t # Permissive 解除、Enforcing 復帰
# 運用フローを再度回して拒否が出ないことを確認よくある落とし穴 #
setenforce 0で済ませる — 最も多い事故。次回ブートで Enforcing に戻るので応急処置にすぎません。ポリシーモジュールに固める必要があります。- audit2allow の結果を確認なしにインストール — 攻撃者が起こした拒否までそのまま許可するリスク。常に
.teを人が読む。 setenforce 0のまま運用 — Permissive は拒否ログを残します (Disabled とは違う)。それでも本番で永続 Permissive は SELinux の意味を消すのと同じ。Disabledでブートしてから再度 Enforcing — ラベルが合わないファイルが溜まりシステムが正常動作しません。/.autorelabelを作って再起動が必要。- ブール値があるのに知らずポリシーモジュールを作成 —
httpd_can_network_connectのような標準ブール値で解ける問題はブール値で。モジュール作成前にgetsebool -a | grep <キーワード>で検索。 dontauditで拒否が見えない — デバッグ中はsemodule -DBで無効化、終わったらsemodule -Bで復元。restorecon漏れ — ポリシー登録だけしてラベルを付けないと効果なし。restorecon -Rv <パス>を必ずペアで。- モジュール名衝突 — 同名の既存モジュールは上書きされます。
myapp_extra、myapp_v2のような明示的な名前で。
覚えておくコマンド #
| 作業 | コマンド |
|---|---|
| 拒否確認 | sudo ausearch -m AVC -ts recent |
| 親切な解説 | sudo journalctl -t setroubleshoot / sealert -a |
| 拒否 → モジュール | audit2allow -M <name> (入力は ausearch 出力) |
| モジュール登録 | sudo semodule -i <name>.pp |
| モジュール削除 | sudo semodule -r <name> |
| モジュール一覧 | sudo semodule -l |
| dontaudit 無効/有効 | sudo semodule -DB / sudo semodule -B |
| ドメイン Permissive | sudo semanage permissive -a/-d <type> |
| ブール値表示/変更 | getsebool -a / sudo setsebool -P <bool> on |
| ラベル適用 | sudo restorecon -Rv <パス> |
| ポリシービルド | make -f /usr/share/selinux/devel/Makefile <module>.pp |
| seinfo 探索 | seinfo -t <type> / sesearch -A -s <type> |
まとめ #
- ポリシーファイルは
.te(ルール)、.fc(ラベルマッピング)、.if(インタフェース) — 小さなモジュールは.te1 つで十分。 - audit2allow — 拒否 → モジュールを自動生成。結果を人が確認して登録。新 type の導入は直接
.teで。 - コンパイルパイプライン —
selinux-policy-develの Makefile 1 行で.ppまで。semodule -iで永続登録。 - ブール値 — ポリシー内のスイッチ。標準ブール値で解ける問題はモジュールを作らずブール値で。
- デバッグフロー — 拒否確認 →
semodule -DBで dontaudit 無効化 →semanage permissive -a <type>で隔離 → 運用フロー → audit2allow でモジュール化 → Enforcing 復帰。 - 絶対に
setenforce 0で終わらせない — 常にモジュールに固める。
次回は SELinux の上に乗るより広いセキュリティ強化領域です。システムに起こるあらゆる変化を追跡する auditd、セキュリティ標準チェック自動化 OpenSCAP、政府/金融認証で要求される FIPS モードまで 1 サイクルで扱います。