Node.js
SELinux

SELinuxで安心安全なnode.jsを目指していく

More than 5 years have passed since last update.

node.jsをVPSで公開する際に気になったのがセキュリティ。

このTipsではSELinuxでnode.jsを保護しつつ実行する方法を調べ、設定した過程を載せています。

自分は特別SELinuxに明るいわけではないので、おかしな点があれば教えていただけると非常に助かります。


SELinuxについて

設定にあたって、SELinuxのポリシーはtargetedポリシーの環境でテストしました。

SELinuxの基本や各種ファイルの編集・生成方法については、次のページが分かりやすく参考になります。

このTipsで脈絡なく使われる"semodule"コマンドや"restorecon"コマンドやアクセス設定の方法の説明も上記のページで解説されています。

モジュールパッケージの作成方法等、SELinuxへ設定を加える方法が一通り説明されているので、一度目を通す事をお勧めします


node.jsの設定

インストールに関しては、特別何かする必要は無いと思います。自分は適当なページを参考にし、naveでインストールしました。

実行するスクリプトは、後でアクセス権を設定するために常に特定のディレクトリに置くようにします。このTipsでは"/usr/local/nodejs_scripts"というディレクトリを作成し、その中に実行するjsファイルとして"server.js"を配置しました。

jsファイルの内容はWeb上で良く見かける"Hello world"なコードとしました。


下準備

適当なディレクトリを作成し、そのディレクトリ中でlocal.teファイルとlocal.fcファイルを作成し設定を行います。

makeを実行してモジュールパッケージを作成できるように、"/usr/share/selinux/devel/Makefile"をコピーしておきます。


まずはドメインの設定から

node.jsのプロセスに独自のドメインを付与するための設定を"local.te", "local.fc"に書きます。

ドメインは何でもいいのですが、このTipsでは"nodejs_t"として記述します。

また、モジュール名は"my_nodejs"に設定しておきます。


local.te

policy_module( my_nodejs, 1.0 )

type nodejs_exec_t;
files_type( nodejs_exec_t )

type nodejs_t;
domain_type( nodejs_t )

type nodejs_scripts_dir;
files_type( nodejs_scripts_dir );

type nodejs_scripts_file;
files_type( nodejs_scripts_file );

role unconfined_r types nodejs_t;
domain_entry_file( nodejs_t, nodejs_exec_t )
unconfined_domtrans_to( nodejs_t, nodejs_exec_t )


自分がこれを初めて書いた時は"role unconfined_r types nodejs_t"を書かずにやったのですが、これがないとエラーが出ます。

ちゃんと理解できていないのですが「BドメインはAロールに属する」というような設定を"role A types B"で行う必要があるようです。


local.fc

/root/.nave/installed/0.9.2/bin/node gen_context( system_u:object_r:nodejs_exec_t, s0 )

/usr/local/nodejs_scripts gen_context( system_u:object_r:nodejs_scripts_dir, s0 )
/usr/local/nodejs_scripts/(.*) -- gen_context( system_u:object_r:nodejs_scripts_file, s0 )

タイプの設定として"local.fc"を編集します。"nodejs_exec_t"を付与するファイルは、node.jsのバージョンやnaveのインストール方法によって多少変わるかもしれません。

"local.fc"ではnode.jsによって読み込まれるjsファイルと、それを含むディレクトリに対してそれぞれ"nodejs_scripts_file", "nodejs_scripts_dir"を設定しておきます。

node.jsが必要とするファイルに特定のタイプを付与し、node.jsへそのタイプへのみのアクセスを許可すれば、その他のファイルへの不要なアクセスを制限することができます。


ドメインの設定の適用と、アクセス設定の前準備

ドメインのための設定が終わったら"local.te"と"local.fc"からモジュールパッケージを作成し、"semodule"コマンドを用いて組み込みます。

途中でエラーが出たら設定ファイルを確認します。これまでの設定内容ですと、セミコロンの位置等の単純な記述ミスが原因となっている場合が多いです。

# make

# semodule -i local.pp

モジュールを組み込んだら、ファイルのタイプを付与し直します。対象は"local.fc"においてタイプが設定されているファイル・ディレクトリとなります。

今回はnode.jsの実行ファイルと、jsファイルとそれを含むディレクトリに対してタイプを付与したので、それぞれに対して"restorecon"コマンドを実行します。

# restorecon -F /root/.nave/installed/0.9.2/bin/node

# restorecon -RF /usr/local/nodejs_scripts

次に実行時のエラーログを記録するための設定を行います。

まず"setenforce 0"を実行しSELinuxをPermissiveモードで動作させます。これでnode.jsの動作に影響なくエラーログを録る事が出来ます。

次に"semanage dontaudit off"を実行します。これによって実行時のエラーログの全てを出力させることができます。これを実行しない場合、エラーログは出ていないのにnode.jsが動作しないという状況になり、ハマります。

# setenforce 0

# semanage dontaudit off

以上の準備が整ったらnode.jsを実行することができます。実行し、ページにアクセスし応答があることを確認しましょう。実際に応答するまでを実行しないと、エラーログが録りきれません。

SELinuxはPermissiveで動作しているので、何か問題があるとすればjsファイルの方ですからそちらを修正しましょう。

# node /usr/local/nodejs_scripts/server.js &


エラーログからアクセス設定を行う。

これまでの設定がうまくいっていれば、エラーログが"/var/log/audit/audit.log"に数十行程出力されているはずです。

中には普通のログも混じっているのでエラーログからアクセス設定を行う場合は"grep"等でエラーログのみを抜き出す方が良いでしょう。

# grep denied /var/log/audit/audit > error.log

アクセス設定は"local.te"に記述します。

アクセス設定は特に難しいことはありません。エラーログにはドメイン"nodejs_t"がどのタイプのファイルに対するどのような処理が失敗したかが書いてあるので、それに従い機械的にアクセス設定を記述します。

"audit2allow"コマンドを用いて自動でアクセス設定を出力させることもできます。自分は手作業でやりましが、今回程度であれば手作業でも1時間程度でした。

アクセス設定を"loca.te"に記述し、出来上がった"local.te"は自分の環境で次のようになりました。


local.te

policy_module( my_nodejs, 1.0 )

require {
type user_devpts_t, http_port_t, node_t, locale_t, setfiles_t;
};

type nodejs_exec_t;
files_type( nodejs_exec_t )

type nodejs_t;
domain_type( nodejs_t )

role unconfined_r types nodejs_t;
domain_entry_file( nodejs_t, nodejs_exec_t )
unconfined_domtrans_to( nodejs_t, nodejs_exec_t )

type nodejs_scripts_dir;
files_type( nodejs_scripts_dir );

type nodejs_scripts_file;
files_type( nodejs_scripts_file );

allow nodejs_t user_devpts_t:chr_file { read write append ioctl };
allow nodejs_t nodejs_t:process { execmem };
allow nodejs_t nodejs_scripts_dir:dir { search getattr };
allow nodejs_t nodejs_scripts_file:file { getattr read open ioctl };
allow nodejs_t nodejs_t:tcp_socket { create setopt bind listen accept read write };
allow nodejs_t http_port_t:tcp_socket { name_bind };
allow nodejs_t node_t:tcp_socket { node_bind };
allow nodejs_t nodejs_t:capability { net_bind_service };
allow nodejs_t locale_t:file { read open getattr };
allow nodejs_t setfiles_t:process { rlimitinh siginh noatsecure };


出来上がったら"make"コマンドで再度モジュールパッケージを作成します。

出来たモジュールパッケージを"semodule"コマンドで組み込み、新しく作成したアクセス設定を有効にします。

新しく作成したアクセス設定でもまだエラーログが記録される場合は、再度"local.te"を編集し、モジュールパッケージを作成し反映させましょう。

エラーログがなくなるまで、これを繰り返します。


完成!

エラーログが出なくなれば、アクセス設定の完成です!SELinuxをEnforcingモードに変更し問題なくnode.jsが動くことを確認しましょう!

# setenforce 1


終わり

設定できた勢いでバーッと書いたのですが、正直これでいいのか疑問です。

本当に必要最低限のアクセス設定だけなので、node.jsでライブラリとかを使うとまた違った問題とかが出てくるかもしれません。

何かベストプラクティス的な設定を知っている方、「こんなの安全じゃねーよ」っていう方は是非教えてください。