パッケージのサービスを jail で実行する:専用アカウント、root 権限なし、許可したネットワークのみ
QPKG パッケージのサービススクリプトは root として実行されます。サービスビルダーの Start in a jail ブロックを使うと、起動するプログラムはサンドボックス内の専用アカウントで実行されます。ケーパビリティはなく、取得もできません。危険なシステムコールのフィルターと専用ネットワークが適用されます。この例ではページを配信し、サンドボックス内で許可される操作をそのページに表示します。
- プロファイル実行内容と見える範囲
- ブロックアカウント、フォルダー、jail
- ビルドbvs-jail と bwrap を同梱
- 署名ケース 1、その後インストール
- 確認ページが実行環境を報告
画像は Bucket & Code Server IDE 1.0.0-49 のものです。例は QTS 6.1 のテスト用 NAS でビルド、署名、インストール、確認を行いました。アカウント名は ola です。
01jail が提供するもの
- サービスはプロファイルで指定したアカウントで実行されます。root では実行されず、ケーパビリティはなく、
NoNewPrivsが設定されます。seccomp フィルターはマウント、新しい名前空間、カーネルモジュール、bpf、io_uring、ptraceなど、カーネルの脆弱性攻撃に使われる呼び出しを拒否します。 - アプリは読み取り専用、データフォルダーは読み書き可能で、専用の
/tmp、/proc、/devと NAS の glibc が見えます。他の共有、/etc/config、他のパッケージは見えません。 - ネットワークは専用のループバックです。各
inboundポートは NAS から内部ポートへ中継されます。外向き接続は HTTP プロキシを通り、プライベートアドレス、ループバック、NAS 自身のアドレスへの接続は拒否されます。 - メモリ上限、CPU の配分比率、プロセス数上限は jail 全体に適用されます。
パッケージには jail 自体も含まれます。bin/bvs-jail と静的リンクの bin/bwrap(bubblewrap)は、ビルド時に各アーキテクチャ用に用意されます。
02始める前に
- New project で作成した QPKG プロジェクト、Toolchains ページの QDK、Ubuntu 24.04 のビルド環境が必要です。設定手順は 自分専用の App Center を参照してください。この例ではサービスのプリセット Empty を使いました。
- QNAP が発行したコード署名証明書のファイルが必要です。App Center がインストールできるのは署名済みパッケージだけです。
03プロファイル
プロファイルはパッケージ内の JSON ファイルです。この例ではプロジェクト内の shared/etc/jail/jailbox.json に置き、パッケージのインストールフォルダー内へ etc/jail/jailbox.json としてインストールします。
# shared/etc/jail/jailbox.json
{
"name": "jailbox",
"user": "jailbox",
"app": "@QPKG_ROOT@",
"data": ["@QPKG_DATA@"],
"services": [
{"name": "checks", "cmd": ["@QPKG_ROOT@/bin/busybox", "sh", "@QPKG_ROOT@/share/jailbox/checks.sh", "@QPKG_ROOT@", "@QPKG_DATA@"], "oneshot": true},
{"name": "web", "cmd": ["@QPKG_ROOT@/bin/busybox", "httpd", "-f", "-p", "127.0.0.1:8080", "-h", "@QPKG_DATA@/www"], "wait_port": 8080}
],
"inbound": [{"listen": "0.0.0.0:19975", "port": 8080}],
"egress": {},
"limits": {"memory_mb": 256},
"stop_seconds": 10
}
@QPKG_ROOT@と@QPKG_DATA@は、NAS 上で初めて確定するインストールフォルダーとデータフォルダーを表します。起動のたびに実際のパスをコピーへ書き込みます。このコピーは root だけが変更でき、jail はこれを使用します。不明なキーは拒否されます。appは読み取り専用でバインドされます。この例ではパッケージの BusyBox を使えるようにインストールフォルダー全体を指定しています。実際のアプリでは、たとえば@QPKG_ROOT@/appのようにアプリ自身のツリーを指定します。servicesは順番に起動します。oneshotはまず最後まで実行されます。wait_portはサービスが待ち受けを始めてから次のサービスを起動します。少なくとも 1 つのサービスは実行を続けます。inboundは NAS のポート 19975 を内部ポート 8080 へ中継します。egressは127.0.0.1:3128でプロキシを提供し、公開アドレスのポート 80 と 443 への接続を許可します。allowリストで指定した宛先は、アドレスにかかわらず接続できます。limits.memory_mbは指定しなければ 2048 です。stop_secondsは jail がサービスの停止を待つ時間です。
最初のサービスは、パッケージに shared/share/jailbox/checks.sh として含まれる次のスクリプトを実行します。サンドボックスで許可される操作を調べ、2 番目のサービスが配信するフォルダーへ結果を書き込みます。
#!/bin/sh # Runs inside the jail and writes what the sandbox allows into the page folder. R=$1 D=$2 B=$R/bin/busybox V=$($B dirname "$D") { $B grep -E '^(Uid|CapEff|NoNewPrivs|Seccomp):' /proc/self/status if $B touch "$R/probe" 2>/dev/null; then echo 'write the install folder: allowed'; else echo 'write the install folder: refused'; fi if $B ls /etc/config >/dev/null 2>&1; then echo 'read /etc/config: allowed'; else echo 'read /etc/config: refused'; fi echo "what $V holds here: $($B ls -A "$V" | $B tr '\n' ' ')" echo "network interfaces: $($B tail -n +3 /proc/net/dev | $B cut -d: -f1 | $B tr -d ' ' | $B tr '\n' ' ')" } >"$D/www/checks.txt" echo 'Served from inside the jail.' >"$D/www/index.html"
04ブロック
プロジェクトページの Service and package カード、Blocks タブで、At start の下の Add a block を使い、次の順に 5 個のブロックを追加します。
- Data folder on the volume、名前
jailbox:フォルダー$QPKG_DATAを用意します。 - Create an account、名前
jailbox:プロファイルで指定したアカウントです。 - Create a folder、
$QPKG_DATA/www、所有者jailbox:jailbox、モード750:サービスが書き込む唯一のフォルダーです。 - Start in a jail、ID
jailbox、プロファイル$QPKG_ROOT/etc/jail/jailbox.json。停止時の待ち時間は、プロファイルのstop_secondsに 15 を加えた値以上にします。この例では既定値の 90 で十分です。
- Wait for a port、
19975:中継ポートが応答すると起動処理が完了します。
At a status check の下に、ID jailbox を指定した Is it running? と 19975 を指定した Is the port open? を置くと、サービススクリプトの status コマンドで jail の状態を確認できます。Save は各ファイルの差分を表示し、Commit は変更をプロジェクトのリポジトリへ記録します。
05ビルド、署名、インストール
プロジェクトページの Build は QDK の qbuild を実行します。ログには、jail 用としてパッケージに含まれるファイルが表示されます。
証明書がなければパッケージは未署名でビルドされ、プロジェクトページにはテスト配備とリポジトリに署名済みパッケージが必要と表示されます。
- 証明書のファイル
certificate、private_key、ca_certsをプロジェクトフォルダーのqpkg.cfgの隣へ置き、再ビルドすると、署名済みパッケージが生成されます。テンプレートの.gitignoreにより、これらのファイルは git に入りません。CI ではリポジトリのシークレットから取得します。詳しくは 自分専用の App Center を参照してください。 - 署名済みパッケージは、プロジェクトページの Test deploy(管理者)、App Center の Install Manually、または NAS 自身の App Center リポジトリからインストールします。
06隔離されていることを確認する
http://<NAS address>:19975/ を開くと、中継ポート経由で「Served from inside the jail.」と表示されます。同じ場所の checks.txt には最初のサービスの確認結果が入っています。
# http://192.168.1.20:19975/checks.txt
Uid: 222 222 222 222
CapEff: 0000000000000000
NoNewPrivs: 1
Seccomp: 2
write the install folder: refused
read /etc/config: refused
what /share/CACHEDEV1_DATA holds here: .qpkg jailbox
network interfaces: lo
- ボリュームのフォルダーに見えるのは、
.qpkg配下のインストールフォルダーとデータフォルダーという、パッケージ自身のフォルダーだけです。共有や他のパッケージは見えません。 - 管理者の SSH セッションでサービスの
grep -E '^(Uid|CapEff|NoNewPrivs|Seccomp):' /proc/<pid>/statusを実行しても同じ結果が得られます。jail を外側から監視する 2 つの bubblewrap プロセスにもケーパビリティはなく、NoNewPrivsが設定されますが、フィルターは適用されません。 - jail のログはデータフォルダー内の
log/jailbox.logです。各サービスの起動、ポート、停止が記録されます。データフォルダーの所有者が root の場合も記録されます。サービスが書き込めるのは、Create a folder ブロックがそのアカウントへ割り当てたフォルダーだけです。
07停止、削除、残るもの
/etc/init.d/jailbox.sh stopまたは App Center での停止により、サービスは逆順で終了し、jail はended (0)を記録します。そのアカウントのプロセス、cgroup、待ち受けポートは残りません。- パッケージを削除すると jail が停止し、インストールフォルダーとサービススクリプトが削除されます。アカウントとデータフォルダーは削除するブロックがないため残ります。At removal の Your own shell lines で一緒に削除できます。
- jail の実行時フォルダー
/var/run/bvs-jail/jailboxは、次の起動で書き直されるまで残ります。QTS のメモリファイルシステム上にあるため、次の再起動で消えます。