第4章 社内ネットワークセキュリティとリモートアクセス(実践:ZTNA・VPNの導入と展開)
学習目標
- 従来型の境界型VPN(城郭と堀モデル)が抱えるラテラルムーブメント(Lateral Movement、横展開)のリスクを説明し、NIST SP 800-207ゼロトラストアーキテクチャ(PE・PA・PEP)の重要原則と比較する。
- インバウンドファイアウォールポートを1つも開けないアウトバウンド専用コネクタ(
cloudflaredCloudflare Tunnel)の動作原理と高可用性構成を設計する。 - WireGuardベースのメッシュVPN(Tailscaleなど)におけるコントロールプレーン・データプレーンの分離、直接接続(NAT Traversal)とDERP中継、サブネットルーター(Subnet Router)、最小権限ACLを説明する。
- 全社端末への配布時におけるスプリットトンネリング(Split Tunneling:包含対除外モード)と家庭用ルーター帯域(
192.168.x.xなどのRFC1918)の衝突問題を診断し、解決策を策定する。 - 社内専用ドメインが外部のパブリックDNSに露出しないよう、スプリットホライズンDNS(Split-Horizon DNS)と条件付きフォワーダーを設計する。
- IdP連携(SSO・MFA・SCIMによる自動アカウント回収)とデバイスポスチャ(OSパッチ、ディスク暗号化、MDM/EDR)検査を組み合わせ、接続条件を強制する。
- 社内ZTNAのTLS検査ルートCA証明書を開発者のノートPCに配布する際、
NODE_TLS_REJECT_UNAUTHORIZED=0のようなセキュリティ無力化を行うことなくCLI・コーディングエージェント(NODE_EXTRA_CA_CERTS、SSL_CERT_FILE)が正常動作するよう構成する。
はじめに
AIサービスを作る会社の本当の宝物は、顧客向けの公開Webサイトだけにあるわけではありません。開発者が毎日接続する社内のステージングデータベース、プロンプトと評価データセットが入った内部管理ツール、自社ホストしているvLLM/Ollama推論サーバー、社内Wikiとデプロイパイプラインのダッシュボードは、すべて社内ネットワークの中にあります。在宅勤務する開発者、外注のデータラベラー、海外出張中のPMを、これらの社内資産へどのように接続させるべきでしょうか。
最も怠慢な方法は、社内サーバーにグローバルIPを付与し、ファイアウォールでポート(SSH 22、DB 5432、Web 8080)を開けたままパスワードだけをかけておくことです。これは世界中の自動化スキャナーに対して「私たちのサーバーをハッキングしてください」と宣伝しているようなものです。その次によくある方式が、20年以上使われてきた**従来型の境界型VPN(Virtual Private Network)**です。社内ファイアウォールにVPN装置を1台置き、社員がVPNクライアントで接続すると社内の内部ネットワークIPを1つ払い出す方式です。
ところがある日、外注作業者の個人ノートPCがマルウェアに感染した状態で社内VPNに接続しました。既存のVPNは「ひとたびVPNログインさえ通過すれば、社内網の中に入った身内」として扱っていたため、攻撃者は外注作業者が見るはずもない財務チームのファイルサーバーや本番データベースのポートまで、内部網を伝って横方向へ自由に歩き回り(ラテラルムーブメント、Lateral Movement)、会社を掌握してしまいました。泣きっ面に蜂で、セキュリティチームがこれを防ごうと次世代ZTNAエージェントを全社のノートPCへ一括インストールしたところ、翌朝には開発者全員が「自宅のWi-Fiプリンターが使えません」「社内ドメインが開きません」「Claude Codeやnpm install、pythonがUNABLE_TO_GET_ISSUER_CERT_LOCALLYという証明書エラーを出して全部止まりました!」と悲鳴を上げました。
この章は、まさにこの2つの問いに答えます。**「どうすればインバウンドポートを開けることなく、最小権限で社内リソースを守れるのか?」そして「実際の組織の開発者・役職員の端末にVPNやZTNAを導入・展開するときに噴出するネットワーク・DNS・証明書の衝突を、どうすればきれいに解決できるのか?」**です。
重要な概念
従来型の境界型VPNの限界とゼロトラスト(NIST SP 800-207)
従来型のネットワークセキュリティは、**「城郭と堀(Castle-and-Moat)」**モデルでした。会社の外側のインターネットは危険な場所であり、会社の内側の社内網(VPC、社屋LAN)は安全な城内であると仮定します。リモートワーカーは城門(VPNゲートウェイ)で身分証を見せて城内へと入ります。
このモデルが崩壊した理由は3つあります。
- 過度な暗黙的信頼とラテラルムーブメント(Lateral Movement):城門(VPN)を1度通過してしまえば、ユーザーの端末が社内ネットワーク層(L3/L4)自体に編入されます。マーケティングのインターンがWebダッシュボードを1つ見るだけでよいはずなのに、同じサブネットにある開発サーバーのSSHポートやデータベースポートをスキャンして突つくことができてしまいます。アカウントが1つ奪われただけで、城内全体が突破されます。
- VPN装置自体のインバウンド攻撃対象領域:リモート接続を受け付けるには、VPN装置がインターネットに向けてグローバルIPとインバウンドポート(例:UDP 500/4500、TCP 443)を常に開けておかなければなりません。実際、ここ数年で最も致命的だった企業侵害事故の相当数が、老朽化したVPNアプライアンスのゼロデイ脆弱性を突いて発生しました。
- クラウド・リモートワーク時代のヘアピン(Hairpinning)ボトルネック:社内資産はすでにAWSソウルリージョンやGCP東京リージョンに分散しているのに、社員のすべてのトラフィックを本社オフィスの物理VPN装置へ集めてから再びクラウドへ送ると(バックホーリング、Backhauling)、速度が遅くなり、VPN装置が単一障害点(SPOF)になります。
米国国立標準技術研究所(NIST)の**SP 800-207(Zero Trust Architecture)**標準は、この限界を克服するためにゼロトラストの核心命題を次のように定義しています。
「ネットワークの物理的な位置や論理的な帯域(社内網IPであるかどうか)だけで、いかなる主体に対しても暗黙的信頼(Implicit Trust)を付与しない。すべてのアクセスはネットワーク全体ではなく個別のリソース(アプリケーション・サービス)単位で、セッションごとに身元と端末の状態・コンテキストを検証し、最小権限でのみ許可する。」
NIST SP 800-207は、ゼロトラストシステムを3つの論理構成要素で説明しています。
| NIST SP 800-207の構成要素 | 役割 | 実務での対応例 |
|---|---|---|
| ポリシーエンジン (Policy Engine, PE) | ユーザーの身元(IdP)、端末の状態(MDM/EDR)、時間・位置・リスクシグナルを総合し、「このリクエストを許可するか拒否するか」の最終判断を下す頭脳 | Cloudflare Accessポリシー評価器、Tailscale/Oktaポリシー決定エンジン |
| ポリシー管理者 (Policy Administrator, PA) | ポリシーエンジンの決定に従い、実際の通信経路を開くか閉じるよう執行ポイントへ命令(トークン・セッションキーの発行または遮断)を伝達 | ZTNAコントロールプレーン、コーディネーションサーバー |
| ポリシー執行ポイント (Policy Enforcement Point, PEP) | ユーザー端末とリソースの間で実際にパケット・接続を立ち塞ぎ、検証されたセッションのみを通過させる関門 | エッジZTNAプロキシ、cloudflaredコネクタ、WireGuardピアインターフェース |
従来型VPNとZTNA(Zero Trust Network Access)の違いをひと目で比較すると、次のようになります。
| 比較基準 | 従来型の境界型VPN | ゼロトラストネットワークアクセス (ZTNA) |
|---|---|---|
| アクセス付与の単位 | ネットワーク/サブネット全体(例:10.10.0.0/16帯域への進入) |
個別のアプリケーション・ホスト・ポート単位(例:admin.internalのHTTPSのみ許可) |
| ラテラルムーブメントのリスク | 高い(内部網への進入後に隣接サーバーをスキャン可能) | 極めて低い(許可されていないリソースは存在自体が見えない) |
| インバウンドファイアウォールポート | VPNゲートウェイのグローバルIPおよびインバウンドポート開放が必須 | アウトバウンドトンネル使用時はインバウンド開放ポート0個 |
| 検証時点と条件 | 最初のトンネル接続時にID/パスワード(およびOTP)を1回確認 | リソースへのアクセスごとにSSO身元 + MFA + **端末セキュリティ状態(Device Posture)**を継続確認 |
アウトバウンド専用コネクタ:cloudflared(Cloudflare Tunnel)とインバウンドポートの排除
社内VPCやオンプレミスサーバー上で動いている内部管理Webアプリ(http://localhost:8080)や開発用SSHサーバーを、外部の許可された社員にだけ公開するには、ファイアウォールのインバウンドポートをどのように設定すべきでしょうか。正解は、**「インバウンドポートをただの1つも開けない」**です。
外から中へ入ってくるのに、どうしてインバウンドポートを1つも開けずに済むのでしょうか。秘密は、接続を結ぶ**向きを逆転させること(Outbound-only Connector)にあります。その代表格がCloudflareのcloudflared(Cloudflare Tunnel)**です。
動作原理を段階別にたどってみましょう。
- 社内サーバー(またはプライベートサブネットのコンテナ)のファイアウォール(Security Group)は、**インバウンドルールを完全に空にしておきます(Inbound allow:なし)。その代わり、外部インターネットへ出ていくアウトバウンド(Outbound、ポート443/7844 QUICまたはHTTP/2)**だけを許可します。
- 社内サーバーの中で軽量デーモンである
cloudflaredを実行すると、cloudflaredが先に社内側から外側(近くの複数のCloudflareエッジデータセンター)へ向かって暗号化されたアウトバウンドトンネル接続を4本結んで待機します。 - ここで外部の社員がブラウザから
https://staging-admin.example.comへアクセスします。 - リクエストはまずCloudflareエッジ(Cloudflare Access、すなわちPEP)に到着します。Cloudflareは、この社員が社内IdP(Google Workspace、Oktaなど)でログインしているか、MFAと端末状態を通過しているかを先に検査します。
- 検査を通過した正常なリクエストだけが、すでに社内の
cloudflaredが内側から外側へ向かって開いておいたそのトンネルを通って、社内サーバーのhttp://localhost:8080へと伝達されます!
この構造がもたらすセキュリティ上および運用上の利点は絶大です。
- グローバルIPとインバウンドポートの完全な消滅:社内サーバーはグローバルIP自体を持たなくても(NATゲートウェイ背後のプライベートサブネットであっても)動作し、インターネットからスキャンできる開いたポートが0個であるため、ポートスキャンや直接のDDoS攻撃が物理的に不可能です。
- 事前認証(Pre-authentication):認証されていない攻撃者のパケットはCloudflareエッジで即座に破棄されるため、社内サーバーのWebフレームワークまで届くことすらありません。
- 高可用性(HA)構成:単一の
cloudflaredプロセスが死ぬとトンネルが切れてしまうため、本番環境や中核の社内網では、異なるアベイラビリティゾーン(AZ)や2台以上のホスト上で同一のトンネルを駆動するcloudflaredレプリカ(Replica、コネクタ)を2つ以上立ち上げ、無停止の冗長化を構成します。
メッシュVPN(WireGuard・Tailscale):サブネットルーター、DERPリレーと最小権限ACL
Webアプリケーションや単一ホストはCloudflare Tunnelのようなリバースプロキシ型ZTNAで公開するのに向いていますが、開発者がローカルIDEから複数のマイクロサービス、内部gRPCサーバー、開発用Kubernetesクラスター、社内推論ノードへ、あたかも同じオフィスのLANにいるかのように低い遅延時間で接続しなければならないときは、**WireGuardベースの現代的なメッシュ(Mesh)オーバーレイネットワーク(例:Tailscale、Netbirdなど)**が大きな威力を発揮します。
従来の中央集中型VPN(Hub-and-Spoke)とメッシュVPNのアーキテクチャの違いは鮮明です。
| 区分 | 従来の中央集中型VPN(OpenVPN/IPsec装置) | 現代的なWireGuardメッシュVPN(Tailscaleなど) |
|---|---|---|
| トラフィック経路 | すべてのパケットが中央のVPNゲートウェイ1か所を経由する(ボトルネックが発生) | コントロールプレーン(鍵交換・ポリシー配布)だけを中央サーバーが担い、実際の暗号化データパケットは端末と対象サーバーが1対1で直接(Peer-to-Peer)通信 |
| 暗号化プロトコル | 重く複雑なレガシーネゴシエーション | 最新の軽量暗号化WireGuard(カーネル/高性能ユーザースペース、高速ローミング) |
| IP割り当て体系 | 接続するたびに変わるプライベートIPプール(10.8.0.x) |
端末・ノードごとに固定された**CGNAT帯域(100.64.0.0/10)**の固有IPおよびMagicDNS名を付与 |
メッシュVPNを実務に導入する際に必ず理解しておくべき3つの重要なメカニズムがあります。
- NAT通過(NAT Traversal)とDERPリレーフォールバック:2つのノードがそれぞれルーター(NAT)の背後にあっても、STUN/ICE技法でファイアウォールに穴を開け(UDP Hole Punching)、直接UDP接続を結びます。もし社内ファイアウォールやホテル・カフェのネットワークがUDPを完全に遮断していて直接接続が不可能な場合は、接続が切れる代わりに、**暗号化された状態のままHTTPS(TCP 443)ベースの中継サーバー(DERP、Designated Encrypted Relay for Packets)**を経由して通信を継続します。このときもエンドツーエンドのWireGuard暗号化キーは両端の端末だけが持っているため、中継サーバーであってもパケットの中身を見ることはできません。
- サブネットルーター(Subnet Router、バスティオンブリッジ):AWS RDSのマネージドデータベースや社内ネットワークプリンター、旧式の機器には、VPNエージェントプログラムを直接インストールすることができません。このとき、同じVPCのプライベートサブネット内に小さなLinuxインスタンス(またはコンテナ)を立ち上げてサブネットルーターに指定し、DBサブネット帯域(例:
10.50.10.0/24)だけを広告(--advertise-routes=10.50.10.0/24)させれば、エージェントをインストールできないRDSにもメッシュ網を通じて安全に到達できます(高可用性のため、サブネットルーターも2台以上で構成します)。 - タグ・ロールベースの最小権限ACL(Access Control Lists):メッシュVPNに接続されたからといってすべてのノードが互いに通信できるように放置すれば、従来型VPNのラテラルムーブメント問題が再発します。したがって、基本ポリシーは**全拒否(Default Deny)**とし、身元グループとサーバータグ(
tag:staging-db、tag:gpu-worker)を基準として、誰が(Source)どのタグの(Destination)何番ポート(Port)にだけアクセスできるかをJSON/HuJSONコードで厳格に定義します。
{
"acls": [
{
"action": "accept",
"src": ["group:backend-devs"],
"dst": ["tag:staging-db:5432", "tag:ai-inference:8000"]
},
{
"action": "accept",
"src": ["group:security-admins"],
"dst": ["tag:prod-bastion:22"]
}
]
}
このように宣言されたACLがあれば、バックエンド開発者はステージングDBの5432番ポートと推論サーバーの8000番ポートにだけパケットを送ることができ、同じネットワーク上にいる他の社員のノートPCや本番バスティオンの22番ポートにはパケットすら届きません。
スプリットトンネリング(Split Tunneling)と家庭用ルーター帯域(RFC1918)衝突の解決
社内VPN/ZTNAクライアントを全社員のノートPCに配布した初日、ヘルプデスクが麻痺する最もよくある原因は、ルーティング帯域の設計とスプリットトンネリング(Split Tunneling)のミスです。
トンネルのルーティング方式は、大きく2つに分かれます。
- フルトンネリング(Full Tunnel):社員のノートPCから出ていく**すべてのインターネットトラフィック(
0.0.0.0/0)**を社内VPNゲートウェイやセキュアWebゲートウェイ(SWG)へ送ります。セキュリティ統制は強力ですが、社員がYouTubeやビデオ会議(Zoom)、大容量のDockerイメージを受け取るときまですべて社内の関門を通るため、社内の帯域幅がパンクし、遅延時間が長くなります。 - スプリットトンネリング(Split Tunnel):社内リソースへ向かうトラフィックだけをトンネルに乗せ、一般の外部インターネットトラフィックは社員の自宅のインターネット回線から直接出ていくように分離します。設定方式には2つのモードがあります。
- 包含モード(Include Mode):指定した社内IP帯域(例:
10.200.0.0/16)や社内ドメインだけをトンネルへ送り、残りはすべてローカルへ流します。社内リソースへの接続が目的であるときに最もすっきりしており、衝突が少なくなります。 - 除外モード(Exclude Mode):基本的にトラフィックをトンネルへ送りつつ、ローカルLAN帯域やビデオ会議帯域など、指定した帯域だけをトンネルから除外します。
- 包含モード(Include Mode):指定した社内IP帯域(例:
ここで実務担当者を悩ませる悪名高い落とし穴が、まさに**RFC1918プライベートIP帯域の衝突(Overlapping Subnet Collision)**です!
ある会社がクラウドVPCを作る際、深く考えずに192.168.0.0/16や192.168.1.0/24(あるいはありがちな10.0.0.0/8全体)を社内VPC帯域として確保し、VPNの包含リストに入れたとしましょう。
在宅勤務する開発者の自宅のWi-Fiルーター、カフェのルーター、スマートフォンのテザリングの大多数も、デフォルト帯域として192.168.0.0/24や192.168.1.0/24(ゲートウェイ192.168.1.1)を使っています!
その結果、VPNをオンにした瞬間に2つの惨事のうちどちらかが発生します。
- ノートPCのOSのルーティングテーブルでより具体的なローカルサブネット経路が優先されると、開発者が社内サーバー(
192.168.1.50)へ送ったパケットがトンネルへ行かず、開発者の自宅リビングのルーターやスマートテレビへ飛んでいき、接続できなくなります。 - 反対にVPNクライアントが強制的に
192.168.1.0/24を横取りすると、開発者の自宅の無線プリンター、NAS、ローカル開発機器との接続がすべて切れるか、さらにはルーターのゲートウェイとの通信自体が途絶してしまいます。
したがって、社内インフラとVPN/ZTNAを設計するときは、次の帯域衝突防止の3原則を必ず守らなければなりません。
| 原則 | 誤った設計(衝突を誘発) | 正しい実務設計 |
|---|---|---|
| 1. 社内VPC CIDRの選定 | 家庭・ルーターのデフォルト値である192.168.0.0/24、192.168.1.0/24、10.0.0.0/24を使用、または10.0.0.0/8を丸ごとルーティング |
家庭用ルーターと絶対に重ならない上位/特定帯域(例:10.240.0.0/16、172.28.0.0/16)を必要なサイズだけ割り当てる |
| 2. スプリットトンネルの包含範囲 | 社内サーバーは/24が1つだけなのに、便宜上10.0.0.0/8全体をVPNトンネルへ引き込む |
実際に社内リソースが存在する精密なCIDR(10.240.10.0/24)のみをIncludeモードに登録 |
| 3. すでに帯域が重なったレガシー網の接続 | 全社員に自宅ルーターの設定を変えろと告知する(不可能) | IP直接ルーティングの代わりに、ドメインベースのZTNAプロキシ(Cloudflare Tunnel)、CGNAT(100.64.0.0/10)仮想アドレスマッピング、またはサブネット変換(NAT/4via6)で回避 |
スプリットホライズンDNS(Split-Horizon DNS)と社内ドメイン露出の防止
社内の開発データベースのアドレスがdb.staging.internal.example.com(プライベートIP 10.240.10.15)であるとしましょう。このドメインを社員が覚えやすいようにと、外部の誰もが照会できるパブリックDNS(Route 53 Public Hosted Zoneなど)にA 10.240.10.15としてそのまま登録してもよいでしょうか。
絶対に推奨しません。パブリックDNSに社内サーバー名とプライベートIP構造を書き込んでおくと、外部の攻撃者がDNS列挙(Zone Enumeration)や証明書の透明性ログ(Certificate Transparency Logs)を通じて、git-internal、payroll-db、vllm-gpu-01といった社内中核システムの存在と内部ネットワークトポロジー(帯域構造)を手に取るように把握できてしまうからです(偵察段階における情報露出)。
解決策は、**スプリットホライズンDNS(Split-Horizon DNS、またはSplit-Brain DNS)**です。質問してくる人が誰であるか(どの経路から尋ねてくるか)に応じてDNSの応答を変えるか、完全に分離する技法です。
- 内部専用ホストゾーンの分離:AWS Route 53のPrivate Hosted Zone(VPC内部でのみ応答)や社内専用のDNSリゾルバにのみ
*.internal.example.comレコードを登録します。外部のパブリックDNSからこのドメインを尋ねても、NXDOMAIN(存在しないドメイン)が返されます。 - ZTNA・VPNクライアントの条件付きDNSフォワーディング(Conditional DNS Forwarding / Split DNS):リモートワーカーのノートPCにインストールされたZTNA/VPNクライアントに対して、**「
*.internal.example.comで終わるドメインクエリだけをトンネル内側の社内DNSリゾルバ(例:10.240.0.2)へ送り、残りの一般ドメイン(google.com、naver.com)のクエリは普段使っているローカルDNSへ送れ」**と設定します。
このように構成すれば、社内網へ認証された社員だけが社内ドメインをプライベートIPへ名前解決でき、外部インターネットには社内ホスト名自体が露出せず、社員の普段のWebブラウジングのDNS速度も一切遅くなりません。
IdP連携(SSO・MFA)とSCIMによる退職者の自動回収
VPNやZTNAを導入するとき、「社内Wikiに共用VPNのID/パスワード(vpnuser / company1234!)や共用の.ovpn設定ファイルを上げておき、みんなでダウンロードして使わせる」というのは、セキュリティを放棄する行為です。誰がいつ接続したのかを監査(Audit)することもできませんし、社員が1人退職するたびに全社員のVPNパスワードを変えない限り、退職者が自宅から社内網へ入り続けることができてしまうからです。
したがって、エンタープライズのリモートアクセスは必ず**中央IdP(Identity Provider:Okta、Google Workspace、Microsoft Entra IDなど)**と一体で結ばれていなければなりません。OIDCやSAMLベースのSSO(Single Sign-On)とフィッシング耐性のあるMFA(WebAuthnパスキー、ハードウェアセキュリティキー)によって接続者の身元を検証し、人事(HR)・IdPシステムで退職や契約終了が処理された瞬間に、SCIM(System for Cross-domain Identity Management)プロトコルがZTNA/VPNアカウントを自動的に無効化し、現在開いているアクティブなトンネルセッションとトークンまで即座に強制失効(Revoke)するよう構成しなければなりません。
デバイスポスチャ(Device Posture)と端末セキュリティ状態の強制
身元(Identity)が正しいからといって、その端末(Device)まで安全であるとは限りません。社員が本人の正規のIDとMFAでログインしたとしても、接続に使った端末がセキュリティパッチの当たっていない自宅の共用PCであったり、アンチウイルス(EDR)がオフのままマルウェアに感染したノートPCであったりするなら、社内網の扉を開けてはなりません。
**デバイスポスチャ(Device Posture)**は、ZTNAクライアントが接続を結ぶ前とセッション維持中に、端末のセキュリティ健康状態をリアルタイムで点検してポリシーエンジン(PE)へ報告するメカニズムです。
| 統制の軸 | 必須構成要素 | 防ぎきる実際の脅威 |
|---|---|---|
| SSOと強力なMFA | OIDC/SAML連携 + フィッシング耐性MFA(WebAuthnパスキー、ハードウェアセキュリティキー) | 流出したパスワードやフィッシングサイトを通じたリモートアクセス乗っ取りの遮断 |
| SCIM自動プロビジョニング・デプロビジョニング | 人事/IdPシステムでの退職・休職処理時にSCIMプロトコルでZTNA/VPNアカウントを即座に自動無効化し、アクティブセッションを強制終了 | 退職者のアカウントが放置(Orphaned Account)されて社内リソースへアクセスし続ける事故の根本遮断 |
| デバイスポスチャ (Device Posture) | 接続許可前に端末のOS最新セキュリティパッチ有無、ディスク全体暗号化(FileVault/BitLocker)、画面ロック、会社MDM登録証明書、EDR(アンチウイルス)実行有無を検査 | 社員本人の正規アカウントであっても、セキュリティパッチ未適用の個人PCや感染端末から社内網へ接続することを遮断 |
| 短いセッション寿命と再認証 | トンネルトークンの有効期限周期(例:12〜24時間)および機密リソース(本番DB・SSH)アクセス時のStep-up再認証 | ノートPCの紛失や長期放置セッションを通じた無断アクセスの制限 |
端末ルートCA証明書の配布と開発者CLI・コーディングエージェントのTLSエラー解決
多くの企業がZTNAとセキュアWebゲートウェイ(SWG、例:Cloudflare Gateway TLS Inspection、Zscalerなど)を導入する際、外部へ出ていくHTTPSトラフィックの中に機密データの流出(DLP)やマルウェアがないか検査するため、**TLS復号検査(TLS Inspection)**を有効にします。
このときゲートウェイは、中間で社内専用の企業ルートCA(Enterprise Root CA)証明書を用いてWebサイトの証明書に新しく署名し直し、端末へと送ります。そのため、MDM(Jamf、Intuneなど)を通じて全社員のmacOSキーチェーン(Keychain)やWindows証明書ストアへ、この「社内ルートCA証明書」を信頼できるルートとして配布します。ChromeやSafariといったWebブラウザはOSの標準証明書ストアを読み取るため、何の問題もなく正常に開きます。
ところが、なぜ開発者たちのターミナルでは大騒ぎになるのでしょうか?
それは、Node.js、Python(requests、urllib、pip)、curl、git、Dockerコンテナ、そしてこれらを基盤として動く**AIコーディングエージェント(Claude Code、Codex CLIなど)の大半が、OSのシステムキーチェーンを読み取らず、独自の内蔵CAバンドルファイル(Mozilla CAリストなど)だけを信用する仕組みになっているからです!**その結果、開発者がターミナルでパッケージをインストールしたりコーディングエージェントを実行したりすると、社内ZTNAゲートウェイが提示した社内CA証明書を「見知らぬ機関が署名した偽の証明書」と判定し、UNABLE_TO_GET_ISSUER_CERT_LOCALLYやSELF_SIGNED_CERT_IN_CHAINというエラーを出して止まってしまいます。
このとき開発者が検索して最も頻繁に手を染めてしまう最悪の禁忌行為が、まさに次の設定です。
// 絶対禁止!(中間者攻撃に対するすべてのTLS検証を完全に無効化する危険な設定)
export NODE_TLS_REJECT_UNAUTHORIZED=0
git config --global http.sslVerify false
pip install --trusted-host pypi.org ...
curl -k https://...
NODE_TLS_REJECT_UNAUTHORIZED=0やsslVerify falseを有効にした瞬間、社内CAだけでなく、カフェのフリーWi-Fiでハッカーが差し出してくる偽の証明書まで、有無を言わさずすべて信用してしまいます。
正しい定石の解決策は、TLS検証をオフにすることではなく、各ランタイムとCLIツールに対して「社内ルートCA証明書ファイル(.pem)」を信頼リストへ正式に追加してあげることです。
// 定石の解決策:TLS検証は厳格に維持したまま、社内ルートCA (.pem) を信頼ストアへ追加
// 1) Node.jsおよびNodeベースのAIコーディングエージェント(既存の内蔵CAリストに社内CAを「追加」)
export NODE_EXTRA_CA_CERTS="/etc/ssl/certs/company-ztna-root-ca.pem"
// 2) Python (requests, pip, httpx) およびOpenSSL/curl系ツール
// 注意:SSL_CERT_FILEとREQUESTS_CA_BUNDLEはデフォルトバンドルを「置換」するため、
// 公的ルートCAバンドルと社内ルートCAを1つに結合した (Combined) PEMファイルを指定する!
export SSL_CERT_FILE="/etc/ssl/certs/combined-ca-bundle.pem"
export REQUESTS_CA_BUNDLE="/etc/ssl/certs/combined-ca-bundle.pem"
export CURL_CA_BUNDLE="/etc/ssl/certs/combined-ca-bundle.pem"
// 3) Gitクライアント
git config --global http.sslCAInfo "/etc/ssl/certs/combined-ca-bundle.pem"
ここに、試験でも実務でも極めて重要なディテールがあります。
NODE_EXTRA_CA_CERTSは文字どおりNodeの既存の公的CAリストに社内CAを**付け加える(Extra)**ため、社内CAファイル1つだけを指せば十分です。- 一方、
SSL_CERT_FILEやREQUESTS_CA_BUNDLEはデフォルトのCAリスト全体を**上書き置換(Override)してしまうため、ここに社内CA証明書たった1つだけが入ったファイルを指定すると、社内ゲートウェイを経由しない一般のHTTPS通信がかえってすべて壊れてしまいます!したがって、これらの変数には必ず既存の公的ルートCAバンドルの末尾に社内ルートCAを結合した統合バンドル(combined-ca-bundle.pem)を指定しなければなりません。また、証明書ピンニング(Certificate Pinning)を厳格に行う一部の外部バイナリやApple/Googleの公式アップデートドメインは、ZTNA管理コンソールでTLS復号例外(Do Not Inspectリスト)**として登録し、摩擦をなくす必要があります。
さらに深く
SSH秘密鍵の配布を伴わない短期SSH証明書(Short-lived SSH Certificates)とセッション監査
社内のLinuxサーバーやGPUクラスターへ接続する際、いまだに各開発者の~/.ssh/id_ed25519.pub公開鍵を受け取ってサーバーごとに~/.ssh/authorized_keysファイルへ手動で貼り付けているとしたら、2つの大きな穴が生じます。第1に、社員が退職したりノートPCを紛失したりしたとき、数百台のサーバーを回ってその社員の公開鍵を1つずつ消すのが困難です。第2に、複数の人が同じubuntuやec2-userアカウントで接続すると、誰がどのコマンドを叩いたのかを区別しにくくなります。
現代のZTNA環境(Cloudflare Access for Infrastructure、Tailscale SSH、HashiCorp Vault SSH CA)では、静的な公開鍵ファイルの代わりに短期SSH証明書(Short-lived SSH Certificates)または身元連携SSHを使用します。
- サーバーには個々の開発者の公開鍵を1つも入れず、ただ組織のSSH CA公開鍵(
TrustedUserCAKeys)たった1つだけを登録します。 - 開発者がサーバーへ接続しようとすると、ZTNAクライアントがSSOとMFA、デバイスポスチャを確認したうえで、有効期間がわずか数時間(例:4〜8時間)の短期SSH証明書をその場で発行してくれます。
- 有効期間が過ぎれば証明書は自動的に失効するため回収すべき鍵自体が残らず、接続ログやコマンド監査記録には
ubuntuだけでなく実際のSSOメールアドレス(minsu@company.com)が正確に刻まれます。
開発者の生産性とセキュリティ統制のバランス:全社ZTNAの段階的ロールアウト戦略
どれほど優れたZTNAソリューションであっても、ある朝突然全社員に告知を1行送ってフルトンネル(Full Tunnel)とTLS復号を一斉にオンにしてしまえば、その日のうちに会社のすべての開発と営業が止まります。失敗のない社内導入・展開は、必ず次の4段階の段階的ロールアウトに従います。
| 段階 | 対象および構成 | 重点点検指標 |
|---|---|---|
| 第1段階:インバウンド排除とWebリソースのZTNA化 | 端末エージェントのインストールなしでも使えるブラウザベースのリソース(社内Wiki、管理画面)から先にcloudflaredトンネル + SSO(Access)の背後へ配置し、グローバルインバウンドポートを閉鎖 |
外部ポートスキャン露出0件、ブラウザSSOログイン成功率 |
| 第2段階:インフラ・パイロットグループの端末インストール (Split Tunnel) | セキュリティチーム・プラットフォームエンジニア10〜20名の端末にエージェントをインストール。**包含モード(Include Split Tunnel)**で社内の非衝突CIDR(10.240.0.0/16)とSplit-Horizon DNSのみを接続 |
家庭用ルーター・プリンターとの衝突0件、DERPフォールバックおよびサブネットルーターの遅延時間 |
| 第3段階:開発者CAバンドルの自動化およびポスチャ警告モード | MDMで社内ルートCAと統合PEMバンドル・シェル環境変数(NODE_EXTRA_CA_CERTSなど)をスクリプトで先に配布。デバイスポスチャは即時遮断の代わりに監査/警告(Audit/Log)モードで運用し、未準拠端末へ事前案内 |
CLI・Docker・コーディングエージェントの証明書エラー0件、ポスチャ充足率98%以上への到達 |
| 第4段階:全社拡大、ポスチャ遮断への切り替えおよびレガシーVPN廃棄 | ポスチャルールを強制遮断(Enforce)に切り替え、SCIM自動回収連携を検証したあと、既存のレガシーVPN装置の電源とインバウンドポートを永久に落とす | 未認証/未準拠端末の接続遮断100%、レガシーVPN装置の撤去完了 |
実務の場面
ガオンロボティクス:在宅勤務中のホームネットワーク麻痺と社内ドメイン露出を解決したSplit Tunnel・DNS再設計
物流ロボット制御AIを開発する「ガオンロボティクス」は、社内GPUサーバーと開発データベースのセキュリティを強化するためメッシュVPNを導入し、開発者80名のMacBookにインストールした。ところが配布当日の午後、在宅勤務中の開発者の半数から「VPNをオンにすると自宅のWi-Fiルーターの設定ページや無線プリンター、ローカルのロボットシミュレーター(192.168.0.x)が全部動かなくなる」という不満が噴出した。同時にセキュリティチームは、外部のバグバウンティ研究者から「パブリックDNSを照会してみたところ、gpu-h100-01.gaon.ioやhr-db.gaon.ioといった社内サーバー名とプライベートIPの一覧が外部から丸見えになっている」という通報を受けた。
インフラチームが確認してみたところ、2つの設計ミスがあった。第1に、過去のオンプレミスサーバールームの帯域が192.168.0.0/24だったという理由で、サブネットルーターが192.168.0.0/16全体をVPNへルーティングするよう設定されており、開発者の自宅のルーター帯域(192.168.0.0/24)と正面衝突していた。第2に、社内専用サーバーのAレコード群を、便宜上外部のパブリックDNSゾーンにそのまま書き込んでいた。
チームは週末のうちにネットワークとDNSを次のように是正した。
- 非衝突帯域への切り替えおよび精密なInclude Split Tunnel:クラウドVPCの帯域は
10.220.0.0/16に整理し、旧サーバールームの機器群はVPNの固有CGNATアドレス(100.64.x.x)および専用の非衝突サブネットルーティングでのみアクセスするように変えて、192.168.0.0/16の経路広告を完全に削除した。スプリットトンネリングは、10.220.0.0/16と100.64.0.0/10だけがトンネルを通る包含(Include)モードに固定した。 - Split-Horizon DNSの構成:外部のパブリックDNSから社内専用の
Aレコード40件余りを全量削除した。その代わりに社内VPC内部のPrivate DNSにのみ*.corp.gaon.ioを登録し、VPNクライアントのSplit DNS機能によってcorp.gaon.ioドメインのクエリだけをトンネル内部のDNSリゾルバへ転送するよう設定した。 - 最小権限ACLの適用:すべての開発者がすべてのサーバーのすべてのポートへアクセスできていたデフォルト許可ルールを消し、ロボットファームウェアチームはシミュレーションサーバーに、AIモデルチームはGPUクラスター(
tag:gpu-cluster)にのみアクセスできるようロール別のACLを分離した。
月曜日の朝、在宅勤務の開発者たちのローカル機器とプリンターはVPNをオンにした状態でも完璧に動作し、外部インターネットから社内サーバー名を照会しても何の情報も露出しないようになった。
ヌリクラウド:ZTNA導入時に起きたコーディングエージェントのSSLエラーと退職者セッション遮断
エンタープライズAIソリューションを構築する「ヌリクラウド」は、ISMS-Pおよび顧客企業のセキュリティ監査基準を満たすため、Cloudflare Zero Trust(Tunnel + Access + Gateway TLS検査)を導入した。社内の管理Webページ群はcloudflaredアウトバウンドトンネルへ移し、AWSセキュリティグループのインバウンドポートをすべて閉じることに成功した。
しかし、開発者のノートPCでZTNAクライアントをオンにしてTLS検査を有効化した途端、開発チーム全体が立ち往生してしまった。Chromeブラウザではすべてのサイトが問題なく開いたが、ターミナルで回すNode.jsサーバー、Pythonのpip/poetry、そして開発者が毎日使うAIコーディングエージェントCLIが外部LLM APIを呼び出すたびに、Error: unable to get local issuer certificateを出して失敗したのである。一部の急いでいる開発者が.zshrcにexport NODE_TLS_REJECT_UNAUTHORIZED=0を入れ始めたため、セキュリティチームに非常事態宣言が出された。
セキュリティエンジニアとプラットフォームチームは、直ちに標準端末構成パッケージを作成してMDMで配布した。
| 問題箇所 | 一時しのぎ(禁止) | ヌリクラウドの標準解決策 |
|---|---|---|
| Node.jsおよびコーディングエージェントCLI | NODE_TLS_REJECT_UNAUTHORIZED=0(TLS検証の無力化) |
MDMで配布された社内ルートCAパスを指すよう、標準シェルプロファイルにexport NODE_EXTRA_CA_CERTS="/Library/Application Support/NuriZTNA/nuri-root-ca.pem"を設定 |
| Python・curl・Gitツール | pip --trusted-host、git http.sslVerify=false |
システム標準CAバンドルと社内ルートCAを結合したnuri-combined-ca-bundle.pemを自動生成し、SSL_CERT_FILE、REQUESTS_CA_BUNDLE、git http.sslCAInfoに指定 |
| 証明書ピンニング (Pinning) アプリ | ZTNAエージェント自体を終了させてしまう | Apple OSアップデート、Dockerレジストリの一部など厳格な証明書ピンニングを行うドメインを、ZTNAコンソールのDo Not Inspect(復号例外)リストへ正式登録 |
| 外注人材の契約終了時の権限回収 | 退職の1週間後に手動でアカウントを整理 | 社内IdP(Okta)とZTNAをSCIMで連携させ、人事システムで退職処理された瞬間に進行中のトンネルセッションとAccessトークンが1分以内に自動失効するよう構成 |
この措置により、開発者はただの1行もセキュリティバイパスオプションを使うことなく普段どおりコーディングエージェントとパッケージマネージャーを使えるようになり、セキュリティ監査の端末統制・退職者即時回収の項目も満点で通過した。
間違えやすいポイント
- **「社内VPNに接続したユーザーのIPは社内帯域(
10.x.x.x)なのだから、すべての社内サーバーのファイアウォールをその帯域全体へ開けておいても安全である。」**これこそが従来型の境界型VPNにおける最大の弱点である、暗黙的信頼とラテラルムーブメント(Lateral Movement)のリスクです。端末が1台侵害されただけでも内部網全体が突破されるため、ゼロトラストの原則に従って身元・端末状態を検証し、必要なリソース・ポートにのみ最小権限(ACL)でアクセスを開けなければなりません。 - **「外部から社内Webサーバーへ接続させるには、Cloudflare Tunnelを使うとしてもルーターやセキュリティグループでインバウンド443番ポートは必ず開けなければならない。」**誤りです。
cloudflaredは社内サーバーの中から外側のCloudflareエッジへ向かって先にアウトバウンド接続を結んでおき、そのトンネルを通じてリクエストを受け取るため、社内ファイアウォールのインバウンドポートは1つも開ける必要がありません。 - **「社内VPC帯域として
192.168.0.0/16や192.168.1.0/24を使えば、馴染み深くて管理しやすい。」**家庭用ルーターやカフェのWi-Fiの大多数が192.168.0.0/24または192.168.1.0/24を使っているため、在宅勤務者がVPNをオンにした瞬間にローカルネットワークと社内帯域が衝突し、社内サーバーやローカル機器への接続が不能になります。 - **「社内専用サーバーのドメインも、プライベートIP(
10.x.x.x)を指しさえしていれば外部からは接続できないのだから、パブリックDNSに登録しておいても何の問題もない。」**プライベートIPへ直接接続はできなくても、パブリックDNSに社内ホスト名とIP帯域構造を露出させると、攻撃者に内部資産リストとネットワーク地図をタダで教えてあげることになってしまいます。必ずSplit-Horizon DNS(プライベートDNS + 条件付きフォワーディング)で隠さなければなりません。 - **「社内ZTNAの導入後にNode.jsやPythonでSSL証明書エラーが出たら、
NODE_TLS_REJECT_UNAUTHORIZED=0やverify=Falseを有効にして解決する。」**これは証明書検証自体をオフにしてしまい、中間者攻撃に対して無防備にさらす致命的なセキュリティ違反です。必ず社内ルートCA証明書をNODE_EXTRA_CA_CERTSや統合バンドル(SSL_CERT_FILE、REQUESTS_CA_BUNDLE)に登録して、正規の検証を通過させなければなりません。 - **「退職者のメールアカウントさえ消せば、すでにノートPCでつながっているVPNトンネルも自動的に即座に切れる。」**長期証明書や別個のローカルアカウントを使うVPNでは、メールが消されても既存のトンネルや証明書が期限切れになるまで生き残ることがあります。IdPとZTNAをSSO・SCIMで連携させ、短いセッション周期と即時セッション失効(Revoke)が作動するように構成しなければなりません。
ひと目でわかるまとめ
| 重要テーマ | 試験と実務で覚えておくべき重要文 |
|---|---|
| 従来型VPN vs ZTNA | 従来型VPNはネットワーク進入後のラテラルムーブメント(Lateral Movement)に脆弱である。ZTNA(NIST SP 800-207)は位置ではなく身元・ポスチャに基づき個別リソースのみを最小権限で許可する |
アウトバウンドトンネル (cloudflared) |
社内からエッジへ先にアウトバウンド接続を結び、インバウンド開放ポート0個でサービスをつなぐ。本番環境は2つ以上のコネクタレプリカでHAを構成する |
| メッシュVPN (WireGuard・Tailscale) | コントロールプレーンとデータプレーン(P2P、塞がれたらDERP暗号化中継)を分離し、エージェント未設置リソース(RDSなど)はサブネットルーターでつなぎ、タグベースACLでポートを制限する |
| スプリットトンネルとRFC1918衝突 | 家庭用ルーター帯域(192.168.0.0/24、192.168.1.0/24)と重ならない社内CIDR(10.240.0.0/16など)を使い、必要な帯域のみをIncludeモードでルーティングする |
| Split-Horizon DNS | 社内専用ドメイン(*.internal)はプライベートDNSにのみ登録し、ZTNAクライアントの条件付きフォワーダーでのみクエリして外部露出を防ぐ |
| IdP・SCIM・デバイスポスチャ | SSO + フィッシング耐性MFAで身元を確認し、OSパッチ・ディスク暗号化・MDM/EDRポスチャを強制し、退職時はSCIMでアカウントもセッションも即座に自動回収する |
| 端末CA証明書とCLIツール | NODE_TLS_REJECT_UNAUTHORIZED=0などの検証無効化は絶対禁止。NODE_EXTRA_CA_CERTS(追加)と統合PEMバンドルを指すSSL_CERT_FILE・REQUESTS_CA_BUNDLEを配布する |
公式ドキュメントで確認する
- NIST — SP 800-207 Zero Trust Architecture:ゼロトラストの7大原則とPE・PA・PEP論理アーキテクチャ標準。
- Cloudflare Zero Trust — Cloudflare Tunnel (
cloudflared):アウトバウンド専用トンネルの動作原理と高可用性レプリカ構成。 - Cloudflare Zero Trust — Split Tunnels & Local Domain Fallback:Include/Excludeスプリットトンネリングと社内DNSの条件付きルーティング。
- Cloudflare Zero Trust — Install Root Certificate for Developers:Node.js、Python、Gitなどの開発者ツールへ社内ルートCAを登録する公式手順。
- Tailscale Docs — How Tailscale Works, Subnet Routers & ACLs:WireGuardメッシュデータプレーン、DERP中継、サブネットルーターとACL設計。
セルフチェック
4-1. 従来型の境界型VPNとNIST SP 800-207に基づくゼロトラストネットワークアクセス(ZTNA)が「認証通過後に付与するアクセス範囲」においてどのように異なるかを比較し、端末1台が侵害されたときに従来型VPNで発生する代表的な拡散リスクを書きなさい。
4-2. 社内のプライベートサブネットにある管理Webサーバーを外部の許可された社員にだけ公開しようとするとき、cloudflared(Cloudflare Tunnel)を使用すればなぜ社内ファイアウォール(セキュリティグループ)のインバウンドポートを1つも開けなくても外部から接続できるのか、接続確立の向きから説明しなさい。
4-3. 社内のクラウドVPC帯域を192.168.1.0/24で作成し、全社員のノートPCへVPNを配布したところ、在宅勤務中の社員たちが社内サーバーへ接続できなくなったり自宅の無線プリンターが切れたりする現象が発生した。この現象の原因と、根本的なネットワーク帯域設計の解決策を説明しなさい。
4-4. 社内専用の開発データベースドメイン(db.dev.internal.corp)を外部のパブリックDNSにプライベートIPで登録しておいたときのセキュリティ上の問題点を説明し、これを解決するスプリットホライズンDNS(Split-Horizon DNS)の構成を2文で説明しなさい。
4-5. 社内ZTNAのTLS検査(TLS Inspection)を有効にしたあと、開発者のノートPCでNode.jsベースのAIコーディングエージェントとPythonのrequestsが証明書エラーで失敗するとき、なぜNODE_TLS_REJECT_UNAUTHORIZED=0を使ってはいけないのかを説明し、Node.jsとPythonにそれぞれ設定すべき正しい環境変数名とファイル構成の違いを書きなさい。
4-6. 設計課題:外注の開発者15名に対し、社内のステージングPostgreSQL(エージェントを直接インストールできないAWS RDS、ポート5432)へのアクセス権限のみを付与し、他の社内サーバー(SSH 22など)にはアクセスできないようにし、契約終了時には即座に接続を断ちたい。メッシュVPNまたはZTNAにおける(1)RDS接続の構成要素、(2)ポート・対象制限の手段、(3)退職時の自動回収プロトコルをそれぞれ何によって設計すべきか記述しなさい。
実習課題
実習
社内ZTNA・メッシュVPN移行設計図と最小権限ACLを作成する(50分)
- 現在社内に次の3つの内部資産があると仮定する:
- 社内運用管理Web(
http://10.240.10.20:3000、ブラウザからのみ接続) - ステージングRDS PostgreSQL(
10.240.20.50:5432、エージェントのインストール不可) - AI推論GPUサーバー(
10.240.30.10、SSH 22およびAPI 8000)
- 社内運用管理Web(
- 各資産をインバウンドグローバルポートの開放なしに接続するため、
cloudflaredトンネルとメッシュVPNサブネットルーターのどちらをどのように配置するか(高可用性レプリカを含む)、表形式で設計する。 group:ops(運用管理Web)、group:backend(RDS 5432 + 推論API 8000)、group:ml-infra(GPUサーバーSSH 22 + API 8000)の3グループに対して最小権限のみを許可するACLルール(JSON形式)を作成する。
完了基準: 3つの資産すべてでグローバルインバウンドポートが0個であり、RDSはサブネットルーター(またはプライベートトンネル)を通じて接続され、ACLにおいてgroup:backendがGPUサーバーの22番ポート(SSH)へアクセスできないよう分離されていること。
実習
全社端末ZTNA配布チェックリストと開発者CA環境スクリプトを作成する(40分)
- 在宅勤務者のホームネットワーク(
192.168.0.0/16)と衝突しないよう、スプリットトンネリング(Includeモード)に入れるCIDRリストとSplit-Horizon DNSドメイン(*.internal.company.io)のフォワーディングルールを定義する。 - 接続許可前に検査するデバイスポスチャ(Device Posture)の必須項目4つと、IdP連携(SSO、MFA、SCIM)の条件を記述する。
- 社内ルートCA(
company-root-ca.pem)をシステム標準バンドル(/etc/ssl/cert.pem)と結合してcombined-ca-bundle.pemを作成し、Node.js、Python、curl、GitがTLS検証をオフにすることなくこの証明書を信頼するよう設定するmacOS/Linuxシェルスクリプト(setup-dev-ca.sh)を作成する。
完了基準: スクリプト内にNODE_TLS_REJECT_UNAUTHORIZED=0や-kといったセキュリティ無力化コードが一切なく、NODE_EXTRA_CA_CERTSと統合バンドルベースのSSL_CERT_FILE/REQUESTS_CA_BUNDLE/http.sslCAInfoが正確に区別されていること。