確立済みの Linux VPN を使うブラウザは /dev/net/tun を開きません。ソケットを開き、そのソケットのパケットがどのインターフェースから出るかはカーネルが決めます。トンネルを作成したプロセスは、そのトラフィックを運ぶプロセスとは別の仕事をしています。この二つの役割を分けておけば、権限の疑問の多くは問われる前に答えが出ます。

ソケットからトンネルまでパケットを一つ追う

普通のアプリは connect や send を呼び、カーネルが宛先へのルートを探します。選ばれたルートがトンネルのインターフェースを指していれば、パケットは VPN デーモンが読むデバイスのキューに入ります。デーモンはそれを暗号化し、物理インターフェースから送り出します。この説明の矢印の一つ一つが、権限の疑問に答えられる場所です。

その経路のどこにも、アプリがデバイスを知っている必要はありません。デーモンがファイルディスクリプタを保持してインターフェース操作を行い、アプリは通常のネットワーク API しか見ません。

二つの半分を一度描いてみれば、権限の疑問は置き場所が分かりやすくなります。上半分はアプリの世界で、下半分はデーモンのものです。

誰がデバイスを必要とし、誰がそうでないかを決める

TUN インターフェースの作成や接続には、デバイスへのアクセスに加えて それを設定する権限 が必要です。そのため、サポート対象の VPN サービスはネットワーク管理権限で開始するか、特権ヘルパーを使います。その要件はデーモンのものです。ブラウザ、データベースクライアント、ファイル転送ツールについては何も述べていません。その権限を必要とするサービスは、自分の設定手順のために必要なのであって、クライアントのためではありません。

ルートとポリシーが整えば、普通のプロセスは通常のソケット呼び出しで出来上がった経路を使います。デバイスノードも root も必要ありません。

デバイスの権限を変えることは、アプリに宛先へのアクセスを与える方法ではありません。デバイスの境界を別の場所に移すだけです。

アプリが実際に動く名前空間を確認する

コンテナや二つ目のネットワーク名前空間は、そのプロセスに別のインターフェース群とルートテーブルを与えます。ホスト上のトンネルはそこに自動では現れず、デバイスの権限をいくら変えてもそれは変わりません。アプリはその名前空間が提供するものを通ってルーティングするだけです。だからこそコンテナイメージがホストのトンネルを偶然継承することはありません。

確認するには ip netns list と ip -n NAME route を使うか、プロセスの名前空間リンクを調べます。答えはたいてい一つのコマンドで見えます。

同じホスト上の二人のユーザーに、ポリシーで別の経路を与えることもできます。それはネットワークの判断であり、デバイスの所有の証拠ではありません。

権限とネットワークポリシーを分ける

ソケット以上のものを必要とするツールもあります。パケットの捕捉には生のソケットが必要で、ルートの変更には管理権限が必要です。これらは別の許可を伴う別の操作であり、一方を与えることを VPN 利用の一律の要件と読むべきではありません。

独自のトンネルを実装するプログラムは三つ目のケースです。デーモンの役割を引き受けるので、本当に TUN を開きます。だからデーモンの権限が必要なのです。

権限を名付ける前に、操作を名付けましょう。この二つは見た目より分けやすいのが普通です。

二つのアカウントで分離を検証する

トンネルをサービスとして接続し、次にアプリを一般ユーザーとして実行して、プライベートリソースに届くことを確認します。そのユーザーのシェルから ip route get を宛先に対して実行し、どのインターフェースが選ばれたかを見ます。ルートが正しいのに接続が失敗するなら、問題は特権ではなくポリシーです。

結果として得られる設計は、明示的な境界を持つ最小権限です。一つのコンポーネントが設定とトンネルのリソースを所有し、ワークロードは通常のアカウントを保ち、ネットワークポリシーが何を通せるかを決めます。すべてを root で動かすと、何も説明しないままそれらの役割が崩れます。

関連記事