サービスは起動するのに、ログは tun デバイスを開けないと訴え、ルートはいつまでも現れません。手元のシェルで capsh --print を実行すると cap_net_admin はすぐそこにあり、失敗はあり得ないように見えます。シェルとユニットで違うのは特権の遷移であり、カーネルがケイパビリティを渡す気の有無ではありません。以下の四つの確認を順に行い、食い違いを説明する最初の確認で止めてください。

欠けたケイパビリティと隠れたデバイスを切り分ける

ログの行だけでは二つの失敗は同じに見えます。CAP_NET_ADMIN を持たないプロセスはトンネルのインターフェースを作成も設定もできず、サンドボックスが /dev/net/tun を隠しているプロセスはそもそも開けません。ケイパビリティを足しても 二つ目の失敗は直りません。

何かを編集する前に、デバイスがサービスの名前空間の中に存在するかを確認します。PrivateDevices=yes は tun ノードを完全に取り除く一般的な堅牢化の行であり、制限の強い ProtectSystem は設定用のパスを、権限エラーに見える形で読み取り専用にできます。どちらもトンネルに触れるログ行を出すため、ケイパビリティのせいにされます。まずノードが存在して開けることを確かめ、それから次へ進みます。

どのケイパビリティ集合を見ているのかを把握する

Linux は許可・実効・継承・境界・アンビエントの各集合を保持し、非 root ユーザーの exec を越えて残るのはアンビエント集合です。バイナリ上のファイルケイパビリティは exec の時点で許可と実効の権利を渡せますが、連鎖のどこかがその受け渡しを妨げていない間だけです。

ここで sudo が人を惑わせます。sudo -u 経由で実行したコマンドは新たな特権遷移を始め、アンビエント集合は失われるため、root なら通る同じ確認がそこで落ちます。サービスマネージャーもまた別の遷移であり、ユニットがそう書いていない限りアンビエントは何も与えません。

ケイパビリティはラッパーではなくユニットで与える

デバイスを作成しルートを設定する必要があるトンネルの最小限のサービスセクションは次のとおりです。

[Unit]
Description=OpenVPN client, office profile
After=network-online.target

[Service]
Type=notify
User=openvpn
Group=openvpn
NoNewPrivileges=yes
AmbientCapabilities=CAP_NET_ADMIN
CapabilityBoundingSet=CAP_NET_ADMIN
ExecStart=/usr/sbin/openvpn --config /etc/openvpn/office.conf

AmbientCapabilities はプロセスの起動前に権利をアンビエント集合へ入れ、CapabilityBoundingSet はその一覧の外にあるものをプロセスに保持させません。NoNewPrivileges=yes は setuid やファイルケイパビリティによる特権獲得を遮断し、ユニットが明示的に与えるケイパビリティはそのまま有効です。CAP_NET_BIND_SERVICE はサービスが低いポートを束縛する必要があるときだけ加え、フォーラムのスレッドがそうしたからという理由だけで CAP_SYS_ADMIN を加えてはいけません。

ユニットがバイナリを sudo で包んでいるなら、ラッパーを外します。systemd はすでに User= を適用しており、その余分な層がアンビエント集合を空にしている最有力の理由です。

ユニットファイルではなく実行中のプロセスで検証する

マネージャーが実際に適用した内容を読み、次にそれが起動したプロセスを読みます。

unit=openvpn-client@office
systemctl show -p User -p AmbientCapabilities "$unit"
systemctl show -p MainPID --value "$unit" | xargs -r getpcaps
grep -E '^Cap(Eff|Amb)' /proc/$(systemctl show -p MainPID --value "$unit")/status

ユニットがケイパビリティを列挙しているのに CapEff にそれが無いなら、drop-in が設定を上書きしているか、ExecStartPre の手順が環境を変えています。本体ファイルが正しいと決めつける前に、後から読まれるファイルが静かに勝つのだから、drop-in ディレクトリでユニット名を検索してください。ユニットとプロセスの両方がケイパビリティを示しているのにトンネルが失敗するなら、境界集合を広げるのではなくデバイスとパスの確認に戻ります。広げると本当の原因が隠れ、サービスは以前より強い特権を抱えたままになります。

起動後に特権を落とすほうを選ぶ

OpenVPN は特権付きで起動し、その後は自身の user と group のオプションで非特権アカウントへ降りられ、persist-key と persist-tun がトンネルを引き続き使える状態に保ちます。この構成はプロセスが権利を持つ時間を狭め、常時ケイパビリティを持つ非特権のデーモンとして全体を起動するより適していることがよくあります。

どの方式を選んでも、対話的な root シェルでは動くのに起動時に失敗するサービスはたいていケイパビリティ、デバイス、ファイル権限のいずれかの違いなので、ユニットを再起動して再起動後に同じ確認を繰り返してください。ユニットの設定をプロセスのケイパビリティ出力の隣に記録し、パッケージ更新や drop-in が境界を動かしたかどうかを次の確認者が分かるようにします。

関連記事