LoginSignup
9
8

More than 5 years have passed since last update.

systemd サービス起動時の SELinux コンテキストを変更する (サボリ編)

Last updated at Posted at 2016-01-08

systemd では、ユーザー環境、依存関係などを設定したカスタムサービスを簡単に書くことができます。しかしデフォルトでは root 以外でカスタムサービスを動かしたとしても init_tunconfined_service_t といったデフォルトの高い権限で動作してしまうため、比較的重要なカスタムサービスが攻略されたときに SELinux による保護に頼ることができません。

それだけでなく、systemd のデフォルト環境は SELinux との組み合わせで思わぬ副作用をもたらします。

私が遭遇した具体例においては……

  1. 定期的にジョブを実行するスクリプトを作成し、rsync を用いてリモートネットワークのファイル (具体的には CentOS 7 の yum リポジトリ) を同期する
  2. このスクリプトを実行するために、cron 代わりに systemd タイマーとそれに関連付けたサービスを使用する
  3. rsync が正常実行できていない (以下はそのときの拒否ログ)
/var/log/audit/audit.log
type=AVC msg=audit(1452236628.817:54229): avc:  denied  { name_connect } for  pid=18508 comm="rsync" dest=873 scontext=system_u:system_r:rsync_t:s0 tcontext=system_u:object_r:rsync_port_t:s0 tclass=tcp_socket

この原因は、systemd デフォルトの SELinux 環境から rsync を起動すると rsync デーモンのためのドメインに 自動遷移してしまい、デフォルト設定では外部ネットワーク接続の権限を失うためです。後述するように SELinux の boolean (動的に一部のポリシーを変更できるスイッチ) を変更することでもこの問題に対処することは可能ですが、この類型の問題を (rsync 以外でも) 避ける一般的な解法ではありません。

一般的にこれを避けるためには、デフォルト以外のドメイン (コンテキスト) で systemd 用のサービスを動かす必要があります。この記事では "サボり編" として、SELinux に関する詳しい知識がなくとも比較的簡単に systemd カスタムサービスをカスタムコンテキストで動かす方法を紹介します。

systemd ユニットを記述する

systemd は SELinux との統合に対応しており、実は SELinuxContext というディレクティブを Service セクションで定義すると、サービスを指定された SELinux コンテキストで起動させることができます。

次のファイルは、以下の (私が実際に使っている) 状況を想定しています。

  • CentOS 7 の yum リポジトリは、SELinux ユーザーである user_u が関連付けられた1 sync_centos7 ログインユーザーで同期する
  • サボった点 : このユーザーのログイン後デフォルトコンテキストである user_u:user_r:user_t:s0 を起動に使用する
/etc/systemd/system/sync_centos7.service
[Unit]
Description=CentOS 7 Repository Synchronization Service
After=network-online.target

[Service]
Type=simple
User=sync_centos7
ExecStart=/home/sync_centos7/sync-wrapper.sh
SELinuxContext=user_u:user_r:user_t:s0

これで systemd はサービスを指定されたコンテキスト user_u:user_r:user_t:s0 で動作させようとします。これはユーザー sync_centos7 のログイン時権限と同じなので、このユーザーで通常ログイン2 した後に同じことができるなら、systemd ユニットから起動したコマンドも同じことができるという寸法です。

SELinux ポリシーを記述する

ただし、これだけではまだ動作しないことに注意してください。現状、次のような拒否ログとともにサービス開始に失敗します。

/var/log/audit/audit.log
type=AVC msg=audit(1452237017.133:54239): avc:  denied  { transition } for  pid=19015 comm="(apper.sh)" path="/home/sync_centos7/sync-wrapper.sh" dev="sda3" ino=2281633452 scontext=system_u:system_r:init_t:s0 tcontext=user_u:user_r:user_t:s0 tclass=process

これは systemd (init デーモン) が動作するドメインである init_t から指定したドメインである user_t への遷移に失敗していることを意味します。これを避けるには、init_t から指定したドメインに遷移できるようにカスタム SELinux ポリシーを記述します。手順は次の通り。

  1. [ポリシー開発機] selinux-policy-devel パッケージ (RHEL7 / CentOS 7 の場合) をインストールする
  2. [ポリシー開発機] 下に示す Makefilesystemd_customjobs.te を記述する (systemd_customjobs という名前は重複がない限り好きに変えて良い)
  3. [ポリシー開発機] make を実行する
  4. 生成された systemd_customjobs.pp を運用機にコピーする (運用機とポリシー開発機が別の場合)
  5. [運用機] systemd_customjobs.pp と同じディレクトリで、semodule -i systemd_customjobs.pp を実行する
Makefile
all:
    make -f /usr/share/selinux/devel/Makefile
.PHONY: all
systemd_customjobs.te
dnl SELinux ポリシーを記述する .te ファイル (M4 言語) において、dnl 以降はコメント (記述する必要はない)

dnl モジュール名とバージョン情報
policy_module(systemd_customjobs.te,1.0.0)

gen_require(`
    type init_t;
    dnl user_t 以外に遷移させる場合はここと……
    type user_t;
')

dnl ここを変更する。
allow init_t user_t:process { transition };

SELinux ポリシーのビルドにエラーが発生せず、semodule コマンドでポリシーを読み込めたら、作業は完了です。これであなたのカスタムサービスを user_t ドメインの下で動かすことが可能になりました。

さらにサボる - 特に制限されていない環境でサービスを動かす

特に SELinux による保護を求めておらず、SELinux と systemd のデフォルトコンテキストによる問題を避けたいだけなら、unconfined な (権限が狭められていない) ドメインで動かすのも良いかもしれません。その場合、上記の節における一部の文字列を適宜読み替えてください。

  • SELinuxContext は、unconfined_u:unconfined_r:unconfined_t:s0 (MLS ポリシーを使っていないデフォルト環境の場合)
  • ドメイン/タイプ として、user_t の代わりに unconfined_t を記述する (一般論としては、上記 SELinuxContext のコロンで句切られた 3 番目の項目)

もっと知りたい人のために

systemd のデフォルトコンテキストと SELinux の自動遷移

systemd の基本的なプロセスは、init_t というドメイン/タイプの下で動作しています。これは非常に強い権限を持つ一方、ポリシーにおいては各種のデーモンを適切に制限された環境で起動するために、複数の "自動遷移" ルールが記述されています。

この自動遷移は、もっと身近な例ではユーザーが passwd コマンドを起動するときに /etc/shadow ファイルへの書き込みアクセス権を持つ passwd_t ドメインに遷移するようなときに用いられ、RHEL7 / CentOS 7 を含む複数の SELinux ベース環境においては systemd の SELinuxContext に頼ることのないドメイン遷移を行うために使用されます。

rsync の場合、デーモン版である rsyncd.service のためか init_t から rsync_t というドメイン/タイプに (rsync 実行ファイルに付けられたラベルのタイプである rsync_exec_t を経由して) 自動遷移するルールが記述されており、systemd のデフォルト権限で起動されたサービスから rsync を起動すると、場合によっては rsync_t に遷移してしまいます。前述の通りこのドメイン/タイプにはデフォルトで外部ネットワーク接続のための権限がないため、普通にシェルスクリプトと同様に rsync を用いたリモート同期ができなくなる、というわけです。

SELinux の boolean

SELinux のポリシーにおいては、必要に応じてオンオフすることが可能な機能が boolean としてまとめられています。rsync ドメイン (rsync_t) に対してクライアント機能を有効にする rsync_client がそのような例で、その他にもメジャーなソフトウェアのマイナーな機能などは boolean で有効/無効を切り替えることが可能となっています。

例えば rsync_client boolean をオンにするには、次のようなコマンドを実行します。

setsebool -P rsync_client=1

ここで -P オプションは設定した内容を次回起動時にも引き継ぐオプション (これを指定しない場合再起動すると値が戻る) です。

さて、これを上記の記事で使わなかったのには幾つか理由があるのですが、ひとつの理由に、これを有効にすると rsync デーモン rsyncd.service においてもクライアント機能のための権限が有効になってしまい、rsync デーモンを併用するときに穴を自ら広げることになるからです 3

参考文献

  1. systemd.exec(5) - FreeDesktop.org (Man Pages)
  2. rsync_selinux(8) - Linux man page

  1. ログインユーザーを新規作成する際に SELinux ユーザー user_u を関連付けるには、useradd コマンドにおいて -Z user_u というオプションを指定します。ふたつの "ユーザー" の違いに惑わされないようにしましょう (SELinux ユーザーはアクセス制御の大まかな範囲を指定するためにあり、ひとつの SELinux ユーザーを複数のログインユーザーに割り当てることができます)。 

  2. ここでの "通常ログイン" は、例えば SSH や TTY (サーバーなどの物理画面に表示されている端末) を使うなどしてログインすることを想定しています (OpenSSH は SELinux コンテキストをログインユーザーのものに設定する機能をサポートしています)。sudo などを用いて別ユーザーにログインした場合、通常ログインと違って SELinux コンテキストが変化しません。 

  3. あとの理由は、最初に書いた通り SELinux boolean が一般的な解決策ではないことと、systemd 側の SELinuxContext という、記事にすると面白そうな解法を見つけたため。 

9
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
8