序
プロプライエタリソフトウェアのお世話になっている人で、 FlexNet (旧FLEXlm) のお世話になっている方も多いだろう。私の周りだとCAD周りのライセンスで利用例が多い。これを Rocky Linux 9 で SELinux Enforcing の環境でデーモンとして動かす方法をまとめた。申し訳ないが時間の都合でELF interpreterの問題の解説は今回はパスしてSELinux絡みのみだ。巷だと SELinux を無効化したり Permissive モードにしている説明もあるが、ライセンスサーバーである FlexNet はネットワークに露出しているソフトウェアのため、大きなリスクを生み出すアタックサーフェイスである。万一 FlexNet に脆弱性があった場合に備えて SELinux による影響緩和措置をとる必要がある。
なお、 Red Hat Enterprise Linux 9 でもほぼ変わらないはずである。
TL;DR
これを使え、そしてフィードバックをくれ。
前提事項
Rocky Linux インストール直後と同様、同梱された SELinux Targeted ポリシーを用いているものとする。
パッケージの事前準備を行う
dnf install selinux-policy-devel rpm-build
ポリシー構築
SELinux のポリシーはざっくり「どのプロセスにどのような動作を許すか」を指定するものである。これを分解してポリシーに落とし込むために、事前に以下のことを決める必要がある。
- どのプロセスにどのラベル(ドメイン)を付けるか
- どのように上記ドメイン遷移を行うか
- どのファイルにどのラベルを付けるか
- ドメイン毎にどのような動作を許すか
ここまで分解すればほとんどそのままポリシーに落とし込める。このうち、「どのように上記ドメイン遷移を行うか」に関しては SELinux の仕組みをきちんと理解する必要がある。以下の記事が大変参考になる。
なお、 SELinux において「タイプ」は動作主体であるSubject(プロセス等)と動作対象であるObject(ファイル等)の両方に付与され、前者の場合はプロセスが属するドメイン名となる。概念とその名前が少々ややこしいが、本記事ではプロセスについたラベルをドメインと称して書いていきたい。
ここでざっくりとプロセスのドメインに関して説明をする。 SELinux では基本的に新たに立ち上がったプロセスは親プロセスと同じドメインで動作する。もちろんこのままだと全プロセス同じドメインになってしまうので、切り替える仕組みが存在する。
まずは手動切り替えである。これは SELinux の setexeccon
関数を呼ぶことでドメインを遷移を行う方式であり、アプリケーションが任意のタイミングでドメイン遷移を行うことができる方式である。任意のとは言ったが、普通に考えると fork
した直後 exec
前に実行するのが通常となる。システム管理者目線でこの手順を用いる代表的な事例が systemd サービスファイルに SELinuxContext を指定する手法であり、設定ファイルで起動プロセスのコンテキストを制御できる。以下の記事も参考にされたい。
ここで重要なことは 「どのようなドメイン遷移ができるかは自由ではない」 という点である。これが SELinux の大きな特徴になるが、たとえ親プロセスが root 権限で動いていたとしても、子プロセスをどのドメインに遷移させられるかはポリシーにより強制される。これにより例えば特権昇格の脆弱性により root 権限に昇格されたとしても、その動作を制限することで影響を緩和できる。有名な Dirty Pipe 脆弱性と絡めた解説は下記記事が参考になる。
もう一つの方法が Transition Rule による自動遷移であり、 Rocky Linux 内臓のポリシーで主に使われているドメイン遷移である。これはポリシーに、あるドメインに属するプロセスが特定のラベルが付与されたファイルからプロセスを立ち上げるとどのドメインに遷移するということをあらかじめ設定しておく方法であり、前述の関数を呼び出したりせずともドメイン遷移が行われる。例えば init_t
ドメインで動くsystemd が httpd_exec_t
のラベルが付いたApacheの実行ファイルを実行すると、そのApacheは httpd_t
のドメインで実行されるなどがある。
さて、これらタイプやドメイン遷移をポリシーにしていく必要があるのだが、これらをゼロから考える必要はない。ある程度決まったソフトウェアの動作パターンというのがあるため、それを FlexNet に当てはめていく方法が素直だ。さて、その上でいくつか選択肢があるので以下に示す。
なお、下記 RedHat のドキュメントもぜひ参考にしていただきたい。
無制限
いきなりポリシーもへったくれもないが、 FlexNet に無制限の動作を許す方法を示す。RHEL系の Targeted ポリシーでは init_t
ドメインのプロセス(systemd)が bin_t
のラベルを持った実行ファイルを起動すると unconfined_service_t
という制限がないドメインで立ち上がる。これを反映するために、例えばFlexNetのメインの実行ファイルが /opt/flexnet/lmgrd
に格納されている場合には以下のコマンドを実行し、 systemd から立ち上げると制限なしのモードで動作する。
semanage fcontext -a -t bin_t "/opt/flexnet/lmgrd"
restorecon -R -v /opt/flexnet/lmgrd
ただ、この状態だと万一 FlexNet の脆弱性を突かれたとしても、 SELinux が何かの緩和策になることはない。
Standard Init Daemon 向けポリシーモジュールの生成
FlexNet の動作を適切に制限するために Standard Init Daemon 、すなわち標準的な init (systemd) から立ち上げられるデーモン向けポリシーをひな形として、 FlexNet 向けにカスタマイズする方法を示す。生成時に対象実行ファイルとラベルやドメインの基本名を指定する必要があるので、まずは今回の事例の FlexNet のファイル構成を示す。
- /usr/local/bin/lmgrd (file) : FletNet本体
- /usr/local/bin/lmutil (file) : FletNet管理用コマンド
- /usr/local/lib/flexnet/ (dir) : Vendor ファイル類を置くディレクトリ
- /usr/local/etc/flexnet/ (dir) : ライセンスファイルを置くディレクトリ
FlexNetを利用する場合にはすべてのファイルをまとめて1つのディレクトリに放り込むことが多いと思うが、File Contextを単純で使いやすい状態に保つために、ディレクトリ構造の方に手を入れた。
モジュールの名前や各種ラベル類の接頭辞は flexnet
とする。
ここで最初に述べた検討事項を改めて列挙する。
- どのプロセスにどのラベル(ドメイン)を付けるか
- どのように上記ドメイン遷移を行うか
- どのファイルにどのラベルを付けるか
- ドメイン毎にどのような動作を許すか
自動生成される Standard Init Daemon のポリシーではこれらの項目は以下のとおりである。
一つ目に関し、起動したデーモンに、ポリシーモジュールで定義するドメイン flexnet_t
を付与する。
二つ目に関し、 flexnet_t
ドメインへの遷移は init_t
ドメインのプロセスから flexnet_exec_t
のラベルが付いた実行ファイルを起動したときに自動で行われる。
三つ目に関し、コマンドラインで指定した実行ファイルに flexnet_exec_t
のラベルを付ける。
四つ目に関し、項目が多いので説明は略すが fork
して子プロセスを作ったり、 signal
を送ったり、 etc_t
のファイル(/etc
のデフォルトタイプ)を読み込んだりなどなど、文字通り標準的なデーモンが必要とする機能が許可されている。そしてこの四つ目こそ決めることが多く、複雑で、そして重要なために一番頭を抱える項目だろう。
ざっくり Standard Init Daemon の概観をとらえたところで、実際に生成されるポリシーを見てみよう。下記、コマンドでひな形を生成できる。なお、作業ディレクトリは /root/flexnet_selinux
としている。
sepolicy generate --init /usr/local/bin/lmgrd --name flexnet
下記出力が得られ、その通りいくつかのファイルが生成される。丁寧に説明までついている。
以下のファイルが作成されました:
/root/flexnet_selinux/flexnet.te # 強制ファイルの記入
/root/flexnet_selinux/flexnet.if # インターフェイスファイル
/root/flexnet_selinux/flexnet.fc # ファイルコンテキストファイル
/root/flexnet_selinux/flexnet_selinux.spec # スペックファイル
/root/flexnet_selinux/flexnet.sh # セットアップスクリプト
ざっくりファイルの役割を説明する。
flexnet.te
はポリシーの本体ともいえる内容であり、使うタイプの宣言や許可内容を含む。4つの検討事項のうち
- どのプロセスにどのラベル(ドメイン)を付けるか
- どのように上記ドメイン遷移を行うか
- ドメイン毎にどのような動作を許すか
の3つが含まれる。
flexnet.if
は Interface Call というポリシー記述に用いるマクロを書いていくものである。今回は触れない。
flexnet.fc
は「どのファイルにどのラベルを付けるか」を記述したものである。 restorecon
などで relabel するときの挙動を決定する。
flexnet_selinux.spec
はRPMにおけるspecであり、SELinuxポリシーをRPMで配布する際に使用する。ポリシー運用のための便利ツールの一環としてとりあえずとらえておいてほしい。
flexnet.sh
はポリシーをコンパイルしてマニュアルを自動生成し、RPMを生成するというワークフローのスクリプトである。とりあえずこれを実行すれば配布可能なRPMが出てくる。
まずは単純な flexnet.fc
から見る。
/usr/local/bin/lmgrd -- gen_context(system_u:object_r:flexnet_exec_t,s0)
シンプルにコマンドラインで指定したファイルに flexnet_exec_t
のラベルを付与している。なお gen_context
はマクロで、中身はMLS等ポリシーの条件によって記述を調整するものである。ここでは説明を省略する。典型的な targeted
ポリシーの場合はこのうち flexnet_exec_t
のみ気にしていればまず問題ない。
次に対となるポリシー本体の flexnet.te
を見てみよう
policy_module(flexnet, 1.0.0)
########################################
#
# Declarations
#
type flexnet_t;
type flexnet_exec_t;
init_daemon_domain(flexnet_t, flexnet_exec_t)
permissive flexnet_t;
########################################
#
# flexnet local policy
#
allow flexnet_t self:process { fork signal_perms };
allow flexnet_t self:fifo_file rw_fifo_file_perms;
allow flexnet_t self:unix_stream_socket create_stream_socket_perms;
domain_use_interactive_fds(flexnet_t)
files_read_etc_files(flexnet_t)
auth_use_nsswitch(flexnet_t)
logging_send_syslog_msg(flexnet_t)
miscfiles_read_localization(flexnet_t)
sysnet_dns_name_resolve(flexnet_t)
冒頭の Declarations
以下の部分が文字通り宣言を書いた領域である。 type TYPE_NAME;
で TYPE_NAME
のタイプを宣言し、ポリシーでそのタイプの挙動を書くことができる。ここでは前述のファイルに付与する flexnet_exec_t
と、それが起動したときのドメイン flexnet_t
を宣言している。ラベルもドメインもポリシー上では等しくタイプであるのは人によってはややこしく感じるかもしれない。
その直後に init_daemon_domain
は init daemon (すなわち init_t
のプロセス)が第2引数のラベルが付いたファイルを起動すると第1引数のドメインに自動遷移することを示すマクロである。
なお、マクロ定義自体は以下のページで確認できる。掘っていくと type_transition
がこの自動遷移のコアとなる記述であることがわかるのだが、それだけではまともに動かないので結構いろいろ記述する必要がある。ご興味のある方は追ってみるとよいだろう。
その下にある permissive TYPE_NAME;
はそのドメインで動くプロセスを permissive
モードに切り替えられることを意味している。
semanage permissive -a TYPE_NAME
を実行すると、システム全体は Enforcing モードであっても、該当ドメインで動くプロセスは監査ログを残すだけで実際の挙動は制限を受けないようになる。これはポリシー開発やデバッグで大変役に立つ。
ここまでで
* どのプロセスにどのラベル(ドメイン)を付けるか
* どのように上記ドメイン遷移を行うか
* どのファイルにどのラベルを付けるか
まで記述できている。残りの flexnet local policy
以下が「ドメイン毎にどのような動作を許すか」である。大半がマクロで書かれているが、その名前からなんとなく何を許可しているのかわかるのもあるかと思う。その他文法事項は下記ページを参考にするとよい。なお、今ここで書いているのは「Module Policy」であるという点にはご留意いただきたい。Base Policyなどと比べると若干機能に制限がある。
さて、ポリシーには必要に応じて基本的には許可する allow
をひたすら書いていく形になる。これは「Access Vector Rules」と呼ばれるSELinuxの中核的なルール記述の一つであり、だれが何に対して何をできるのか、禁止するのかなどを記載する。一つのルールを記述する構文を以下に示す。
rule_name source_type target_type : class perm_set;
通常は rule_name
には allow
すなわち、許可を示すが、監査ログ付きの auditallow
とか、監査ログを残さずに拒否する dontaudit
などもある。詳細はドキュメントを参考に。 source_type
はその該当ルールでの動作主体のタイプを指定する部分で、プロセスのドメインで指定する。なお、例えば { httpd_t mysqld_t }
のようにかっこでくくって複数指定することもできる。 target_type
には動作対象のタイプを指定する。この対象はプロセスやファイルやディレクトリ、あらゆる可能性が入る。SELinuxでは例えばTCPやUDPのポートにもタイプを付けられる。sourceと同様複数指定が可能だ。なお self
は source_type
で示した自分自身を指す。 class
にはそのルールで対象とする対象(object)のクラス、例えば file
だったり blk_file
だったりを指定する。そして最後の perm_set
と動作内容、すなわちAccess Vectorを指定する。これも複数指定が可能である。
習うより慣れろということで、この辺りは実例を見た方がいいだろう。
FlexNet関連ファイルと方針
今の初期状態の場合、 FlexNet の本体プロセスには flexnet_t
のタイプが付いているが、それが読み込むライセンスファイルの類は初期状態のままなので、例えばそれに var_t
などのラベルが付いていた場合、システムに数多ある var_t
と区分したアクセス制御ができない。単に動作させるがけなら audit2allow
コマンドを走らせると、監査ログから必要なポリシーを作ってくれるが、それだと最小権限とはかけ離れた状態となってしまう。特にファイル読み込み関係はシステム中に誰もが読み込めるファイルが多すぎて統制を効かせづらいのできちんと制限しよう。
ここでファイル一覧を再掲する。
- /usr/local/bin/lmgrd (file) : FletNet本体
- /usr/local/bin/lmutil (file) : FletNet管理用コマンド
- /usr/local/lib/flexnet/ (dir) : Vendor ファイル類を置くディレクトリ
- /usr/local/etc/flexnet/ (dir) : ライセンスファイルを置くディレクトリ
今のところつけるラベルが決まっているのは一つ目のFlexNet本体だけで flexnet_exec_t
を付けることとなっている。
二つ目の /usr/local/bin/lmutil
は、大抵のユースケースを考えるとコマンドラインから実行されるため、今回のように targeted
ポリシーの場合どんなラベルを付けても unconfident
で実行され、制限がかからない。ただ、もしかすると起動スクリプトからたたく人もいるかもしれない、とのことで、本体と同様 flexnet_exec_t
としておこう。
/usr/local/lib/flexnet/
にある各種ファイル類は FlexNet を利用するソフトウェアベンダーが作る実行ファイル類を置くことになるため、これらはFlexNet本体から読み込み、起動をされることになる。単なるファイル読み込みよりも多くの権限を行使する対象なので、これらには flexnet_vendor_exec_t
という新しいタイプを付けることとしよう。
/usr/local/etc/flexnet/
に置くライセンスファイルは読み込みの類しかしないので、これらも独立して flexnet_conf_t
のラベルを付ける。
さて、勘のいい読者は「ログファイルはどうするんだ?」と思っただろう。今回はログファイルは標準出力経由でsystemdに記録させる。SELinuxに限らず、セキュリティを考えるとログファイルの扱いは大変難儀するもので、ソフトウェアが許するのであれば、デーモンが直接ログファイルを書き込むという動作は避けたい。なぜなら、ログファイルにはそのプロセスが起動したときの情報や他の人の過去のアクセス記録など漏れるとまずい情報が記録されていることがあるが、直接書き込みを前提とすると read
を与えざるを得ず、万一プロセスが乗っ取られると過去のログも丸っと持っていかれるからだ。書き込めるけど読み込めない、というのがログのセキュリティとしては望ましい姿であり、最もお手軽なのが、標準出力をsystemdなどでリダイレクトさせる方法だ。ということで、systemdの方に
StandardOutput=append:/var/log/flexnet.log
StandardError=append:/var/log/flexnet.log
という感じで指定することでログを記録していくアプローチを前提とする。
そして、もう一つ大事なものが一時ファイルの類だ。FlexNetは重複起動防止(と思われる機能)等のためなどに /var/tmp
や /dev/shm
に一時ファイルの類を作成する。(一時とは言うけど、プロセス終了後も残っているが…。) 一時ファイルはこれまたセキュリティ上大変ややこしいもので、SELinuxがない状態のLinuxでも sticky bit を使ったりすることで権限を特殊な形で管理している。一時ファイルの難しさは一時ファイル用のディレクトリ( /tmp
等)には誰でもファイルなどを作れてほしいが、実際に作られたものに対しては、作成したプロセス(やユーザー)以外にその読み込みも書き込みも名前変更も許可したくない点にある。
SELinuxという観点から見ると、リソースに対して何か考えるというよりは、ドメインが何をできるかという基軸になるため視点が反転し、特定のドメインのプロセスが一時ファイル用のディレクトリに作成したファイル類に別のラベル、今回は flexnet_tmp_t
や flexnet_tmpfs_t
を付与し、それ以外の一時ファイルには一切アクセスできないようにする、という話になる。これは序盤で出てきたドメイン自動遷移と同じ枠組みであり、今度はプロセスのドメイン(タイプ)ではなくファイルのタイプを自動させることになる。ポリシー記述においてもドメイン自動遷移と同じ type_transition
によってこのタイプ遷移は設定可能である。これに関しては後ほど便利なマクロを紹介するが、要は「特定のドメインが特定の場所に作ったファイル類に自動でラベルを付けられる」ということを認識してもらえばよい。
ポリシーのカスタマイズ
それでは具体的なポリシーのカスタマイズに入る。まずはファイルのタイプに関して定義した flexnet.fc
に関してみていく。
/usr/local/bin/lmgrd gen_context(system_u:object_r:flexnet_exec_t,s0)
/usr/local/bin/lmutil gen_context(system_u:object_r:flexnet_exec_t,s0)
/usr/local/lib/flexnet(/.*)? gen_context(system_u:object_r:flexnet_vendor_exec_t,s0)
/usr/local/etc/flexnet(/.*)? gen_context(system_u:object_r:flexnet_conf_t,s0)
ものすごくシンプルに、前述の方針を記述したのみである。ここは見たままで説明はいらないだろう。
次に本体ともいえる flexnet.te
だ。こちらはある程度長いので適宜 GitHub の方を見てほしい。
新しくファイル向けに定義した2つのタイプは
type flexnet_vendor_exec_t;
files_type(flexnet_vendor_exec_t)
type flexnet_conf_t;
files_type(flexnet_conf_t)
でファイル用のタイプだよと示している。 files_type
マクロの中身はファイル向けのタイプにつける attribute を付与するものだが、とりあえずこうするものだと認識しておけば問題ない。なお、tmpやtmpfsのファイル類には追加で attribute が必要であり、下記部分のように使うマクロが少々異なる。
type flexnet_tmp_t;
files_tmp_file(flexnet_tmp_t)
type flexnet_tmpfs_t;
files_tmpfs_file(flexnet_tmpfs_t)
後は許可を増やしたり減らしたりとなる。基本的にはいったんポリシーをインストールして restorecon
したのちに semanage permissive -a flexnet_t
をし、FlexNetを動かしてみたのちに semanage permissive -d flexnet_t
でもとに戻した後、 allow2audit なり目視なりで確認になる。ただ、雑に allow2audit を使うと何が何の挙動のものなのか全くわからないポリシーができるので、ある程度理解してあたりを付けるのが望ましい。
corenet_tcp_bind_all_unreserved_ports(flexnet_t)
corenet_tcp_connect_all_unreserved_ports(flexnet_t)
corenet_tcp_bind_all_ephemeral_ports(flexnet_t)
corenet_tcp_connect_all_ephemeral_ports(flexnet_t)
allow flexnet_t self:tcp_socket { accept listen };
ここはネットワーク接続を許可するための記述である。 FlexNet はTCPでLISTENすることで機能させるが、そのポート番号は設定次第である。本当はもっとポートを絞り込みたいのだが、 Module policy では portcon による新たなポート定義を作ることができないため、システムポートを除外した 1024番以降をすべて許可している。なお、SELinuxでのポート定義は
semanage port -l
で確認できる。今回許可しているのは下記2つである。
unreserved_port_t tcp 61000-65535, 1024-32767
ephemeral_port_t tcp 32768-60999
allow flexnet_t self:unix_stream_socket { accept };
おそらくプロセス間通信と思われる挙動を許可する。
kernel_read_network_state(flexnet_t)
storage_getattr_fuse_dev(flexnet_t)
FlexNetはライセンスによってはホストが持つネットワークインターフェースのMACアドレスを使って認証する。そのあたりの挙動を許可する。なお、読み取りのみ許可である。
can_exec(flexnet_t, flexnet_vendor_exec_t)
名前通りだが、 flexnet_vendor_exec_t
を起動可能にする。なお、ドメイン遷移に関しては何も記述していないので、新たに起動されたプロセスも flexnet_t
である。
allow flexnet_t flexnet_conf_t:file { getattr open read };
allow flexnet_t flexnet_conf_t:dir { open read getattr search };
allow flexnet_t flexnet_vendor_exec_t:file { open read map };
allow flexnet_t flexnet_vendor_exec_t:dir { search };
設定ファイル、ベンダー実行ファイルそれぞれの読み込み許可類である。監査ログから抽出した。
files_tmp_filetrans(flexnet_t, flexnet_tmp_t, { file dir })
manage_dirs_pattern(flexnet_t, flexnet_tmp_t, flexnet_tmp_t)
manage_files_pattern(flexnet_t, flexnet_tmp_t, flexnet_tmp_t)
fs_tmpfs_filetrans(flexnet_t, flexnet_tmpfs_t, { file dir })
manage_dirs_pattern(flexnet_t, flexnet_tmpfs_t, flexnet_tmpfs_t)
manage_files_pattern(flexnet_t, flexnet_tmpfs_t, flexnet_tmpfs_t)
ここは一時ファイル類のテンプレートと言っても良い記述であり files_tmp_filetrans
、 fs_tmpfs_filetrans
でそれぞれ tmp_t
、 tmpfs_t
のラベルが付いたディレクトリ内に flexnet_t
が作成したファイルとディレクトリが flexnet_tmp_t
、 flexnet_tmpfs_t
に自動遷移し、これらのタイプに一時ファイル用の attribute を付けるマクロだ。中身は type_transition
を含むがその他属性も含めてまとめてやってくれる便利なものだ。なお、作ったファイルに何ができるかは別途指定する必要があり、 manage_dirs_pattern
、 manage_files_pattern
で基本的になんでもできるようにしている。(たしかfileのmapだけできなかったはず) 一時ファイルは実質アプリケーション内部のような扱いになることが多いだろう。
ポリシーのビルド
ポリシーのビルド方法は flexnet.sh
を読むとわかる。ビルド自体は Makefile を指定して make を呼ぶ。
make -f /usr/share/selinux/devel/Makefile flexnet.pp
そして、それを semodule
をインストールしたのちに、 sepolicy manpage
で自動でマニュアルを生成できる。RPMにしたければ、 spec を調整してビルドすればよい。
まあでも、下記リポジトリで dnf
を使ってサクッとインストールできる方法を記載しているので、ぜひ使って皆様の環境で動くか試してほしい。そしてできればバグレポートが欲しい。よろしくお願いいたします。
おまけ : systemd サービスファイル
今回のポリシーでの前提をもとにサービスファイルの例を置いておく。必要に応じてカスタマイズして使ってほしい。
[Unit]
Description=FlexNet Licensing Service
After=network-online.target
[Service]
User=flexnet
WorkingDirectory=/usr/local/lib/flexnet
ExecStart=/usr/local/bin/lmgrd -z -c "/usr/local/etc/flexnet/license01.dat:/usr/local/etc/flexnet/license02.dat"
Type=simple
StandardOutput=append:/var/log/flexnet.log
StandardError=append:/var/log/flexnet.log
SuccessExitStatus=15
[Install]
WantedBy=multi-user.target