一つのホストに二人の顧客を置くのは、サーバーブロックを一つ増やし、ポートをもう一つ開けるだけのように聞こえる。しかし顧客が実際に所有するものをすべて並べると、それは別のプロジェクトになる。リスナー、トンネルプール、アイデンティティ、信頼ファイル、ルート、失効のスケジュールである。そのどれか一つを飛ばせば、二人目の顧客は一人目の一部を静かに引き継いでしまう。顧客ごとにカードを作り、最初のクライアントが接続する前に埋めておく。

顧客ごとに完全なリソースカードを割り当てる

セット全体をまとめて予約する。公開ポート、トンネルのサブネット、設定パス、ログの出力先、サービスインスタンス名、デバイスである。これらを一つのグループとして名付けることが、一人目の顧客のプールやインターフェースを残したままの稼働ファイルを複製するという定番の失敗を防ぐ。値はあくまで例であり予約済みのポートではないので、ホストがすでに使っているものを確認する。サービスが起動することと、顧客の受け入れが完了していることは別である。

カードは設定のそばに置き、値が変わるたびに更新する。現実から離れた文書は、何もないより悪い。

信頼材料は各顧客の内側に保つ

顧客ごとに別の発行機関を持てば、境界を考えやすくなり、署名鍵をインターネットに面したホストの外に置ける。共通のルートでも動くが、共通のアンカーは二つのサービスを一つのメンバーシップドメインにはしない。各リスナーがどのチェーンを受け入れ、どのクライアントアイデンティティを許可するかを決め、両方を書き留める。何かがつながるまで、試行錯誤でチェーンから証明書を外してはいけない。

サーバー自身の秘密鍵は顧客ごとの保護されたファイルに置くものであり、設定と一緒に複製されるテンプレートディレクトリには置かない。

プロセスだけでなくネットワークも分ける

二つのポートで動く二つのプロセスは、分離しない限り一つのホストと一つのルーティングテーブルを共有し続ける。設計を信じる前に、何が待ち受けているかを確認する。

ss -lunp | grep -E '1194|1195'
openssl verify -CAfile customer-a/ca.crt customer-a/client.pem

リスナーの確認では、どのプロセスが各公開ポートを所有しているかが分かり、検証では、クライアント証明書が意図したアンカーにつながるかが分かる。どちらの出力も、AのクライアントがBのサブネットに到達できないことを証明しない。だから送信元と宛先のポリシー、戻りルート、意図的に共有するサービスを定義する。ネットワーク名前空間やコンテナはルーティングの文脈を分け、偶発的な混在を減らすが、それだけでは完全なテナント境界ではない。脅威モデルが求めるなら、より強い分離の仕組みを選ぶ。

引き渡しの前に失効を届ける

失効に配布経路がなければ、証明書の配備は完了しない。受け入れの一環として、CRLの発行者と有効期間を確認する。

openssl crl -in customer-a/crl.pem -noout -lastupdate -nextupdate
systemctl status openvpn@customer-a

CRLの検査では、リストがいつ発行され、いつ失効するかが分かり、サービスの状態では、それを読むインスタンスが動いていることが確認できる。新しい失効がなくても、発行ポリシーのスケジュールでリストを更新する。期限切れのCRLはすべてのクライアントを拒否しかねない。読む側が部分的な書き込みに遭遇しないよう、完全なファイルを公開し、配備の前に署名を検証する。発行、エンドポイントの運用、廃止の担当者を記録し、Aの停止がBを乱さないようにする。

次に受け入れの組を実行する。許可されたクライアントは自分のサービスに到達し、Aの資格情報はBのリスナーに拒否される。これがテストとして表した境界である。

関連記事