【実務・中級編】 ゼロトラスト移行期におけるレガシー認証方式(Kerberos/NTLM)のZTNA側でのラップ手法 – ゼロトラスト&エンタープライズセキュリティ実践ガイド

レガシーな社内アプリを「ゼロトラスト」の波に乗せる:Kerberos/NTLMプロキシ翻訳の実戦技術

「クラウドネイティブだ、ゼロトラストだ、明日から社内ネットワークの境界は消滅する!」――そう意気込んでモダンなIDプロバイダ(IdP)やZTNA(Zero Trust Network Access)製品を導入したはいいものの、現場のインフラエンジニアの前に必ず立ちはだかる巨大な壁があります。

そう、社内の片隅でひっそりと、しかし会社の命運を握る重要データ(や、なぜか誰も改修できない社内ニッチな人事・会計システム)を抱え続ける「レガシーなオンプレミスアプリ」たちです。

こいつらは、現代的なOIDC(OpenID Connect)やSAMLなんてしゃれたプロトコルは話せません。要求するのは、Windowsのドメイン認証を前提としたKerberosか、あるいは往年のNTLMです。

「じゃあ、このアプリのためにVPNを残すか……?」

ちょっと待ってください。ゼロトラストの思想において、「古いから」という理由で境界防御を部分的に残すのは、城壁に開けた勝手口の鍵をかけ忘れるようなものです。
今回は、現代のZTNAゲートウェイがどのようにしてこれらのレガシー認証をモダンに「ラップ」し、安全にクラウド側へと統合しているのか。そのプロキシ翻訳のメカニズムと、実務で使える設定・検証のノウハウを徹底的に解説します。

—

1. 境界防御の幻想と、レガシー認証のジレンマ

これまでのエンタープライズネットワークは、「社内LAN(内側)にいる=信頼できる」「インターネット(外側)=危険」という境界防御モデルで成り立っていました。社内LAN内では、WindowsマシンがActive Directory(AD)に対して密に通信を行い、チケットを発行させ、NTLMやKerberosでシームレスに認証を通していました。

[社内PC] --(Kerberos/NTLM)--> [AD]
[社内PC] --(生トークン/憑歌)--> [社内レガシーアプリ]

しかし、リモートワークが当たり前になり、ゼロトラストアーキテクチャへ移行する現在、この「社内LANという安全神話」は崩壊しています。ZTNAは「一切信頼しない(Never Trust, Always Verify)」を掲げ、たとえ社内からであっても、デバイスの健全性やユーザーのコンテキストを毎回検証することを求めます。

ここで問題になるのが、KerberosやNTLMの仕様上の特性です。

  • Kerberos: KDC(Key Distribution Center / 也就是 Active Directory)との間で厳密な時刻同期とネットワーク疎通が必要。また、ブラウザ経由でのトランスポートにはSPNEGO(Simple and Protected GSS-API Negotiation Mechanism)が絡み、マルチホップのプロキシを越えるのが非常に厄介。
  • NTLM: チャレンジ・レスポンス方式の古いプロトコルであり、リプレイ攻撃への脆弱性や、クレデンシャルのハッシュ値がネットワークを流れるリスクを内包。

これらをそのままゼロトラストのエッジ(ZTNAゲートウェイ)の外側に露出させるわけにはいきません。だからこそ、「エッジ側でモダン認証(OIDC/SAML + 多要素認証)を受け止め、バックエンドのレガシーアプリに対してはKerberos/NTLMに翻訳して代理送信(プロキシ認証)する」というアプローチが必要不可欠になるのです。

—

2. ZTNAによるプロキシ翻訳・プロキシ認証の仕組み

ZTNAの次世代プロキシ(リバースプロキシ機能を持つエッジノード)は、ユーザーとレガシーアプリの間に立ち、認証プロトコルを華麗にトランスポート(翻訳)します。

通信の全体像とデータが流れるシーケンスは以下の通りです。

[エンドユーザー(社外)]
       │
       │ ① HTTPS (OIDC/SAML + MFA)
       ▼
[ZTNA エッジプロキシ] ──(ユーザー検証完了)
       │
       │ ② SPNEGO / Kerberos (S4U2Self / S4U2Proxy) または NTLM Pass-Through
       ▼
[レガシーオンプレミスアプリ (IIS / Apache等)]

通信シーケンスの裏側

1. モダン認証の強制: ユーザーがブラウザからZTNA保護下のレガシーアプリにアクセスすると、ZTNAプロキシがリクエストをインターセプトし、IdP(Azure AD / Entra ID, Oktaなど)へリダイレクト。MFAを含めた厳格な認証を行わせます。
2. セッション確立: 認証が成功すると、ZTNAプロキシはセッションCookieやJWTを受け取り、ユーザーの身元(UPN: User Principal Nameなど)を把握します。
3. プロキシ認証(翻訳)の実行:

  • Kerberosの場合: ZTNAプロキシは、事前にAD上に用意された「サービスプリンシパル(SPN)」や、委任(Delegation)の仕組み(S4U: Service-for-User)を利用して、ユーザーに代わってKerberosチケットを取得(または生成)し、Authorization: Negotiate ヘッダーに包んでバックエンドアプリに投げる。
  • NTLMの場合: ZTNAプロキシがバックエンドとの間でNTLMハンドシェイク(Type 1, 2, 3メッセージ)を代行、あるいはあらかじめマッピングされたサービスアカウントの資格情報を用いてNTLM認証をクリアする。

この仕組みにより、エンドユーザーは「安全なOIDC/SAML」でログインしているにもかかわらず、バックエンドのレガシーアプリから見れば「いつもの社内ドメインユーザーからのKerberos/NTLMリクエスト」として処理されるわけです。

—

3. 実践:Nginx / Envoy または ZTNAプロキシ設定の要所

実務において、このプロキシ翻訳を構築する際によく使われるのが、エンタープライズ向けのプロキシ(Envoy Proxyなど)や、各社ZTNAゲートウェイ(Cloudflare Access, Palo Alto Prisma Access, Zscaler ZPAなど)のApp Connectorの仕組みです。

ここでは、概念を理解するために、プロキシがバックエンドへKerberos(SPNEGO)をフォワード・翻訳する際の代表的な設定概念と、デバッグ時に確認すべきHTTPヘッダーの構造を見てみましょう。

リクエストヘッダーの変遷

クライアントからのリクエストがZTNAプロキシを通過する際、次のようにヘッダーが書き換えられます。

① クライアント ➔ ZTNAプロキシ(モダン認証済み)

GET /hr-system/dashboard HTTP/1.1
Host: legacy-app.internal.corp
Cookie: ztna_session_token=eyJhbGciOi... (IdP発行のJWT)

② ZTNAプロキシ ➔ レガシーバックエンドアプリ(Kerberos/NTLMに翻訳)

GET /hr-system/dashboard HTTP/1.1
Host: legacy-app.internal.corp
Authorization: Negotiate YIICXAYJKoZIhvcSAQICAQB... (KerberosチケットのBase64エンコード)
X-Forwarded-For: 203.0.113.50
X-Forwarded-User: j.doe@corp.local

デバッグの現場から:よくあるハマりどころ

現場でこの構成を組むとき、ネットワークエンジニアを絶望させるエラーコードがいくつかあります。代表的なものを対策と共にご紹介します。

  • HTTP 401 Unauthorized の無限ループ
  • 原因: バックエンドアプリが WWW-Authenticate: Negotiate を返した際、ZTNAプロキシが適切にチケットを付与できていない、あるいはADのKDCとの時刻ズック(Clock Skew)が5分以上発生している。
  • 対策: プロキシサーバーとADコントローラーの間でNTPが正確に同期しているかを真っ先に確認してください。Kerberosは時刻にシビアです。
  • SPN(Service Principal Name)のミスマッチ
  • 原因: バックエンドのIISやTomcatがバインドされているホスト名と、プロキシがリクエストを送っている Host ヘッダーの値、そしてADに登録されたSPNが一致していない。
  • 対策: setspn -L [サービスアカウント] コマンドを叩き、正しいSPN(例: HTTP/legacy-app.internal.corp)が登録されているか確認しましょう。

—

4. コード&設定例:API経由での動作検証とテスト

インフラレイヤーだけでなく、Web API設計やアプリ側からこのプロキシ挙動をテスト・検証するためのPythonスクリプトのサンプルです。ZTNAプロキシの手前、あるいはプロキシの振る舞いをデバッグする際に、どのようなHTTPリクエストを模擬すべきかの参考にしてください。

以下のスクリプトは、requests-gssapi(PythonでKerberos認証を行うためのライブラリ)を用いて、プロキシ認証を伴うレガシーAPIエンドポイントを叩くテストコードの例です。

import sys
import requests
from requests_gssapi import GSSAPIAuth

def test_legacy_app_via_proxy(target_url, user_principal=None):
    """
    ZTNAプロキシまたはレガシー認証プロキシを経由して
    Kerberos (SPNEGO) 認証が必要なエンドポイントをテストする関数
    """
    print(f"[*] ターゲットURLへの接続テストを開始します: {target_url}")

    # GSSAPI (Kerberos) 認証オブジェクトの生成
    # ※ 事前にkinit等で有効なKerberosチケットを取得している前提です
    auth = GSSAPIAuth()

    try:
        # HTTPリクエストの送信(Authorization: Negotiate が自動付与される)
        response = requests.get(
            target_url,
            auth=auth,
            timeout=10,
            verify=True # 本番環境では適切なCA証明書を指定
        )

        print(f"[+] ステータスコード: {response.status_code}")
        print(f"[+] レスポンスヘッダー (Server): {response.headers.get('Server')}")
        
        if response.status_code == 200:
            print("[SUCCESS] レガシーアプリとのKerberos認証プロキシ経由の通信に成功しました!")
            print(f"[-] レスポンスの一部: {response.text[:200]}...")
        elif response.status_code == 401:
            print("[WARNING] 401 Unauthorized: 認証チケットが無効か、プロキシの委任設定に問題があります。")
            print(f"[-] WWW-Authenticateヘッダー: {response.headers.get('WWW-Authenticate')}")
        else:
            print(f"[-] 想定外のステータスコードです: {response.status_code}")

    except requests.exceptions.RequestException as e:
        print(f"[ERROR] ネットワークまたは通信エラーが発生しました: {e}", file=sys.stderr)

if __name__ == "__main__":
    # 検証用エンドポイントの指定(ZTNAプロキシのURL、またはレガシーアプリ直のURL)
    TARGET_ENDPOINT = "https://ztna-edge.corp.local/hr-system/api/v1/status"
    
    test_legacy_app_via_proxy(TARGET_ENDPOINT)

curlによる簡易デバッグコマンド

Pythonを書くまでもなく、コマンドラインでサクッとKerberos/NTLMのフォワード動作やプロキシの応答(WWW-Authenticate ヘッダーの中身など)を確認したい場合は、以下の curl コマンドが非常に強力です。

# `--negotiate` オプションと `-u :` を使うことで、OSの現在の認証コンテキスト(Kerberosチケット)を利用してリクエストを飛ばせます
curl -iv --negotiate -u : https://ztna-edge.corp.local/hr-system/

実行結果の標準エラー出力(-v による詳細ログ)の中に、< HTTP/1.1 401 Unauthorized と共に < WWW-Authenticate: Negotiate や < WWW-Authenticate: NTLM が返ってきているか、そしてクライアント側がチケットを乗せて再リクエスト(Authorization: Negotiate ...)を送れているかを追うことで、プロキシ翻訳のどこでボルトが緩んでいるかが手に取るように分かります。

—

5. まとめ:レガシーとモダンの架け橋を渡るために

ゼロトラストへの移行は、一朝一夕で完了するような甘いプロジェクトではありません。「古いアプリだから改修できない」「動いているから触りたくない」という現場の事情は、エンジニアなら誰もが共感するところでしょう。

しかし、だからといってVPNという「万能の抜け穴」を放置し続けることは、セキュリティの観点から許されません。

今回解説したKerberos/NTLMのプロキシ翻訳・ラップ手法は、レガシーアプリ側が一行たりともコードを変更することなく、エッジのZTNAゲートウェイの力だけでモダンなアイデンティティ管理の世界へと連れ出すための、実務における極めて有効な延命・統合策です。

プロキシの仕組み、SPNの整合性、そして時刻同期という「泥臭いポイント」をしっかりと押さえれば、境界防御から真のゼロトラストアーキテクチャへの移行は必ず成功します。

さあ、古いアプリの呪縛をプロキシの力で綺麗に包み込み、セキュアでモダンなネットワークインフラをあなたの手で構築しましょう!

コメント

タイトルとURLをコピーしました