おい、ちょっと聞いてくれ。
お前たちのチーム、いまだに「社内ネットワークに入れたから安全」なんて幻想を抱いてはいないだろうか?
VPNのゲートウェイにしがみつき、VPNを張った瞬間に社内ニッチなサーバーへ全通ししてしまうような古い境界防御モデル。あれはもう「セキュリティのゾンビ」だ。一度外側を突破されたら、内側はガラ空きという豆腐構造をいつまで続けるつもりだ。
ゼロトラストの基本は「Never Trust, Always Verify(決して信用せず、常に検証せよ)」。そして、その理念をインフラレベルで具現化する最大の主役が ZTNA(Zero Trust Network Access) だ。
今回は、そのZTNAの心臓部である PDP(Policy Decision Point:ポリシー決定ポイント) と PEP(Policy Enforcement Point:ポリシー実行ポイント) のアーキテクチャ分離について、現場の泥臭い知見を交えて徹底的に解説してやろう。Web APIの設計やインフラ運用に携わるエンジニアなら、明日から使える知識になるはずだ。
—
1. なぜ「脳(PDP)」と「腕(PEP)」を切り離す必要があるのか?
従来のプロキシやファイアウォールは、通信の「判定(ルールの照合)」と「遮断・転送(パケットの処理)」をひとつの箱(ボックス)の中で同時にやっていた。一見すると効率的だが、これではクラウドネイティブな分散環境や、マイクロセグメンテーションの波には太刀打ちできない。
そこでゼロトラストアーキテクチャ(NIST SP 800-207などでも定義されている)では、機能をきれいに二分する。
- PDP(Policy Decision Point / 脳): アクセス要求を受け取り、「こいつは通していいのか?」をコンテキスト(ユーザーの身元、デバイスの健全性、時刻、場所など)に基づいて判定する頭脳。
- PEP(Policy Enforcement Point / 腕): ユーザーと保護対象リソースの間に立ち、PDPからの指令(トークンやポリシー)に従って、実際にトラフィックを遮断したり、プロキシしたりする門番。
この分離がなぜ実務上重要か? それは 「スケーラビリティ」 と 「関心の分離」 だ。
判定ロジックの変更(IDaaSの連携ルール変更など)で、重いトラフィックをさばくゲートウェイ(PEP)側の設定をいじる必要がなくなる。PEPはひたすらパケットやリクエストをフォワード・ドロップし、重たい認可処理はPDP(またはOIDCプロバイダなど)に丸投げできる。この疎結合な設計こそが、モダンなインフラの要なのだ。
—
2. 通信フロー:パケットとトークンが駆け抜ける裏側
実際の現場で、リクエストが飛んでからリソースにたどり着くまでのシーケンスを追ってみよう。ここはデバッグの時に頭に叩き込んでおくべきポイントだ。
[Client (User)] ---> (1) リクエスト送信 ---> [PEP (Gateway/Proxy)]
|
(2) 認可問い合わせ
v
[PDP (Policy Engine)]
|
(3) 判定 & トークン発行
v
[Client (User)] <--- (4) 401/403 or リダイレクト --- [PEP]
(必要に応じてIdPで認証)
1. リクエストの検知: クライアントが保護されたWeb API(例: https://api.example.com/v1/users)へリクエストを投げる。この手前には必ずPEP(リバースプロキシやサイドカー)が立ちふさがっている。
2. アクセスのインターセプト: PEPはリクエストに有効な認可トークン(Authorization: Bearer <JWT>など)が含まれているか確認する。
3. PDPへの問い合わせ(または局所検証): PEPは自己完結でトークンを検証するか、あるいはPDPへ「このユーザーにこの権限はあるか?」と問い合わせる。
4. ポリシー決定: PDPはユーザー属性、デバイスのMDM状態、リスクスコアを評価し、PEPへ「許可(Allow)」または「拒否(Deny)」の指示(メタデータ付与など)を返す。
5. エンフォースメント: PEPはその指示に従い、バックエンドのオリジンサーバーへリクエストを流すか、即座に 403 Forbidden を返す。
—
3. プロトコルインターフェースとパラメーターの実際
PDPとPEPの間、そしてクライアントとPEPの間では、どのようなプロトコルが使われているのか。現場でよく遭遇する標準的な仕様を覗いてみよう。
認証・認可の主役:OAuth 2.0 / OIDC と JWT
クライアントとPEPの間では、主にHTTP/HTTPSが使われ、ヘッダーに JWT(JSON Web Token) が載せられる。PEPはこのJWTの署名(RS256など)を検証し、ペイロードに含まれるクレーム(Claims)をチェックする。
JWTペーロードの具体例
{
"iss": "https://auth.example.com/",
"sub": "user_123456",
"aud": "https://api.example.com/",
"exp": 1717152000,
"scp": ["read:users", "write:users"],
"device_status": "compliant",
"risk_score": "low"
}
scp(スコープ)やdevice_statusといったパラメーターをPDPが評価し、PEPが「write:usersを持っていて、かつdevice_statusがcompliant(準拠)でなければ弾く」というポリシーを実行するわけだ。
PDP-PEP間の通信規格(Open Policy Agent / OPA の例)
現代のクラウドネイティブなZTNA環境では、ポリシーエンジンとして OPA(Open Policy Agent) が使われることが多い。PEP(Envoy Proxyなど)は、外部のPDPであるOPAに対して、HTTP(REST)やgRPCでJSONを投げて判定を仰ぐ。
PEPからPDPへの問い合わせ(Payload)の例:
{
"input": {
"method": "GET",
"path": ["v1", "users"],
"headers": {
"authorization": "Bearer eyJhbGciOi..."
},
"client_ip": "203.0.113.50"
}
}
これに対してPDP(OPA)は、Regoというポリシー言語で記述されたルールに基づき、以下のようなレスポンスを返す。
PDPからの応答(Response)の例:
{
"result": {
"allow": true,
"context": {
"tenant_id": "tenant-A"
}
}
}
PEPはこの allow: true を受け取って初めて、バックエンドへの転送を実行する。この分離によって、ポリシーの複雑なビジネスロジック(Regoのコード)をPEPのネットワークコードから完全に切り離すことができるのだ。
—
4. 実装ハンズオン:Envoy(PEP)と簡易PDPの連携を想定したコード
インフラエンジニアとして、この動きを体感するために、Python(FastAPIなど)で簡易的なPDPを作り、クライアントからのリクエストをシミュレーションしてみよう。
以下のコードは、PEPの役割を持つクライアントスクリプトが、PDP(あるいは認証サーバー)に対してアクセス権を検証し、APIを叩く一連の流れを示す実用的なスニペットだ。
Pythonによる検証クライアント(PEP/Client側シミュレーション)の例
import requests
import sys
# PDP/認可サーバーのエンドポイント
PDP_INTROSPECT_URL = "https://pdp.example.com/v1/evaluate"
TARGET_API_URL = "https://api.example.com/v1/sensitive-data"
def access_protected_resource(auth_token: str, device_id: str):
"""
PEPの挙動を模した検証プロセス。
リクエストを転送する前に、PDPへアクセス権を問い合わせる(あるいはトークンを検証する)。
"""
headers = {
"Authorization": f"Bearer {auth_token}",
"X-Device-ID": device_id
}
print("[PEP] PDPへアクセス許可を問い合わせ中...")
# 1. PDPへの問い合わせ(ポリシー評価)
try:
decision_response = requests.post(
PDP_INTROSPECT_URL,
json={"token": auth_token, "device_id": device_id},
timeout=3.0
)
decision_response.raise_for_status()
except requests.exceptions.RequestException as e:
print(f"[ERROR] PDPとの通信に失敗しました: {e}", file=sys.stderr)
return False
decision = decision_response.json()
# 2. PDPからの判定結果(Allow/Deny)に基づくエンフォースメント
if decision.get("allow") is True:
print("[PEP] アクセス許可 (Allow)。バックエンドへ転送します。")
# バックエンドAPIへのリクエスト実行
api_response = requests.get(TARGET_API_URL, headers=headers)
print(f"[Backend] 応答ステータス: {api_response.status_code}")
print(f"[Backend] ペイロード: {api_response.text}")
return True
else:
reason = decision.get("reason", "Unknown policy violation")
print(f"[PEP] アクセス拒否 (Deny): {reason}", file=sys.stderr)
return False
if __name__ == "__main__":
# テスト用のダミージェットトークンとデバイスID
dummy_token = "eyJhbGciOiJSUzI1NiIs..."
my_device_id = "dev-9876-xyz"
access_protected_resource(dummy_token, my_device_id)
—
5. 現場のシニアが教える! トラブルシューティングの勘所
最後に、実務でPDPとPEPの分離アーキテクチャを運用する際によくハマる「現場の罠」と、そのデバッグ手法を伝授しておこう。
1. レイテンシーの悪化(ネットワークホップの増加)
- *症状*: すべてのリクエストでPEPがPDPに同期問い合わせ(Synchronous Call)を行うため、APIのレスポンスタイムが数ミリ秒〜数十ミリ秒悪化する。
- *対策*: PEP側でトークンの有効期間(TTL)や判定結果を ローカルキャッシュ(LRUキャッシュなど) する仕組みを入れること。ただし、失効した権限が即座に反映されない(伝搬遅延)トレードオフがあるため、キャッシュのTTLは数秒〜数十秒程度に抑えるのが鉄則だ。
2. 分散トレーシング(OpenTelemetry等)の欠落によるデバッグ難民
- *症状*: 「なぜかユーザーAが 403 エラーを食らっている」という問い合わせが来たとき、PEPで弾かれたのか、PDPのポリシーで落とされたのか、ログが散らばっていて原因追跡に何時間もかかる。
- *対策*: PEPとPDPの間で必ず
X-Request-IDや W3C Trace Context(traceparentヘッダーなど)を伝搬させ、すべてのログにトレースIDを紐づけろ。これがないゼロトラスト環境は、暗闇でボウガンを撃つようなものだ。
3. フェイルオープン(Fail-Open)かフェイルクローズ(Fail-Closed)かの設計ミス
- *症状*: PDP(ポリシーサーバー)が障害で落ちたとき、PEPはどう振る舞うべきか?
- *対策*: ゼロトラストの思想の根幹は「疑わしきは罰する(Never Trust)」。PDPがダウンして応答しない場合、PEPは安全側に倒して フェイルクローズ(即座にアクセス遮断) するのが基本だ。ただし、ミッションクリティカルなシステムでは可用性とのジレンマになるため、インフラ要件とセキュリティ要件でしっかりとステークホルダーと合意を形成しておけ。
—
境界防御の時代は終わった。これからは、ネットワークのどこにリソースがあろうとも、PDPとPEPが緻密に連携し、一瞬一瞬のコンテキストを検証し続ける世界が標準になる。
このアーキテクチャの本質を理解していれば、どんな新しいZTNA製品やクラウドベンダーのサービスが出てきても、裏側で何が起きているのか手に取るようにわかるはずだ。
さあ、古いVPNの呪縛を断ち切り、本当の意味でのゼロトラストなインフラを構築してやろうぜ。
コメント