RHEL 上級 #4 SELinux 上級 — ポリシー作成と audit2allow

読了 9分

中級 #1 SELinux 入門 でモード、ラベル、AVC 拒否、audit2allow で解く流れまで見ました。この記事はその上の 1 階です。audit2allow が作る成果物の正体、足りないときに直接 .te ファイルを書いてコンパイルする手順、ポリシーモジュールをシステムに永続登録して次回ブート後も生き残らせる流れまで 1 サイクルで扱います。拒否が見えるたびに setenforce 0 で逃げず、最後まで追ってモジュールに固めるのが目標です。

RHEL 上級 シリーズでこの記事の位置:

ポリシーファイルの正体 — .te / .fc / .if #

SELinux ポリシーモジュールは次の 3 種類のソースファイルで構成されます。

ファイル何を入れるか
*.teType Enforcement — allow ルール、type 宣言
*.fcFile Contexts — どのパスにどのラベルを付けるか
*.ifInterface — 他モジュールから呼び出せるマクロ (任意)

.te が核心です。.fcrestorecon が参照するラベルマッピング、.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 の構造 #

mywebapp.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_overridesetuid のような capability 拒否もそのまま許可してしまいます。人による確認は必須。

.te を直接書いてコンパイル #

audit2allow の結果では足りない、または新 type を導入したいときは直接書きます。

最初のモジュール — sample app ポリシー #

仮のシナリオ。社内デーモン myappd/var/lib/myapp/ にデータを書き、/var/log/myapp.log にログを残します。標準の方法で SELinux ポリシーを与えます。

myapp.te
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 として登録

ファイルコンテキスト定義 #

myapp.fc
/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_modulegen_context など) を使うには reference policy のビルド環境が必要です。

開発パッケージのインストール
$ sudo dnf install -y selinux-policy-devel
ビルド — Makefile 1 行
$ 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 myapp

semodule -rmyapp.pp ではなく モジュール名 を受け取ります。.pp 拡張子なしで。

type、attribute、allow の文法 #

type と attribute #

type はオブジェクトに付けるラベル自体、attribute は type の束です。

attribute 例
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_contextsseinfo コマンドで確認します。

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 が永続適用。なしだと再起動で消えます。

自分のモジュールにブール値を埋め込む #

myapp.te にブール値追加
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.log

setroubleshoot が最も役立ちます。拒否の原因候補と一般的な解決策を自然言語で説明します。

2. dontaudit ルールで見えないとき #

デフォルトポリシーには騒がしい拒否を隠す dontaudit ルールが多いです。デバッグ中は一時的に無効化します。

dontaudit 一時無効化
$ sudo semodule -DB

# デバッグ完了後に再度有効化
$ sudo semodule -B

3. Permissive ドメインに隔離 #

システム全体を Permissive に落とさず、疑わしいドメインだけ Permissive に:

特定ドメインだけ Permissive
$ sudo semanage permissive -a myapp_t

# 解除
$ sudo semanage permissive -d myapp_t

# 現在 Permissive のドメイン一覧
$ sudo semanage permissive -l

この状態で正常動作のフローを最後まで一度回し、溜まった拒否を audit2allow でモジュール化 → Enforcing に復帰。

4. モジュール作成と登録 #

audit2allow → モジュール
$ sudo ausearch -m AVC -ts recent | audit2allow -M mywebapp_extra
$ less mywebapp_extra.te         # 人による確認!
$ sudo semodule -i mywebapp_extra.pp

5. 検証 #

正しく入ったか
$ 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_extramyapp_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
ドメイン Permissivesudo 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 (インタフェース) — 小さなモジュールは .te 1 つで十分。
  • 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 サイクルで扱います。

X