【実務・中級編】 RDP(ポート3389)およびSSH(ポート22)アクセスにおけるZTNAプロキシ制御 – ゼロトラスト&エンタープライズセキュリティ実践ガイド

境界防御の幻想と、ポート3389・22が孕む「永遠のゼロ」への脅威

おい、ちょっと聞いてくれ。先週、あるクライアントの情シスから青い顔をして電話がかかってきたんだ。「社外からVPN経由で接続しているはずの踏み台サーバーを踏み破られ、3389(RDP)ポート経由でランサムウェアが展開された」ってな。

原因を調査したら、まあ、いつものパターンだ。コロナ禍の特急対応でとりあえずVPNのライセンスを追加し、社内ネットワークと直結させた。グローバルIPから直接叩けるようにポートフォワーディングを設定していたり、VPNに一度入ってしまえば、社内LANのどのホストへもノーチェックで横移動(ラテラルムーブメント)できる「フラットな楽園」がそこにあったというわけだ。

これだから境界防御は恐ろしい。一度城壁(VPN)の内側に侵入されてしまえば、中世の城のように敵はフリーパスで王様(重要サーバー)の首を取り放題になる。

だからこそ、我々は「境界型防御」という幻想を捨て去り、ゼロトラスト(一切信頼せず、常に検証する)へシフトしなければならない。特に、インフラエンジニアの命綱であり、同時に攻撃者の最大のターゲットである TCP 22(SSH)と TCP 3389(RDP)の特権ポートへのアクセス制御は、今やVPNではなく ZTNA(Zero Trust Network Access) プロキシによって完全にリプレースされるべきだ。

今回は、PAM(特権アクセス管理)機能と密に連携し、ブラウザや監査ログ付きの専用クライアント経由でこれらシェルやデスクトップへの接続を安全に中継・記録する仕組みを、現場の泥臭い知見を交えて徹底的に解説しよう。

—

ZTNAプロキシがRDP/SSHの通信フローを根底から変える理由

従来のVPNは「ネットワーク層(L3/L4)」で接続を許可する。つまり、VPNに接続した瞬間にクライアント端末は社内LANの「一員」になり、ターゲットサーバーのIPアドレスとポートへ直接パケットが届いてしまう。

一方、ZTNAによるRDP/SSH制御は、通信を完全にアプリケーション層(L7)でインターセプトする。ユーザーはターゲットサーバーのIPを直接叩くことはできない。代わりに、以下のような洗練されたフローで通信が中継される。

[クライアント端末] 
   │ (1. HTTPS / WSS / 専用プロトコル)
   ▼
[ZTNA / PAM プロキシゲートウェイ]
   │ (2. 認証・認可・MFA・デバイスポスチャ検証)
   │ (3. 監査ログ記録 / セッション録画を開始)
   ▼
[ターゲットサーバー (SSH:22 / RDP:3389)]

通信シーケンスの裏側で何が起きているのか?

1. アイデンティティとデバイスの検証: ユーザーがアクセスを要求すると、ZTNAプロキシはまずIdP(Azure AD / Oktaなど)と連携し、多要素認証(MFA)や端末のセキュリティ状態(EDRが入っているか等)を厳格にチェックする。
2. セッションの確立(トランスポートの抽象化): 認証が通ると、クライアントとプロキシ間にTLS(HTTPSまたはWebSocket)によるセキュアなトンネルが張られる。ブラウザ経由であれば、RDPはHTML5(Canvas / WebGL)に変換され、SSHはXterm.js等のターミナルエミュレータ上に描画される。
3. プロキシによる終端と再接続: ターゲットサーバーへの実際の通信は、クライアントではなくZTNAプロキシ自身が代理で行う。これにより、サーバー側から見れば「信頼されたプロキシからの接続」となり、インターネットへ直接露出する必要が一切なくなる(ダークネットワーク化)。
4. 全パケット・操作の記録(PAM統合): SSHのキーストロークや、RDPでの画面遷移・ファイル転送は、すべてプロキシ上でキャプチャされ、後から監査できるようにセッションログや動画としてストレージに保存される。

—

実践:ZTNAプロキシ連携のためのAPI設計と設定例

現場でインフラを構築する際、ユーザーの権限管理やジャスト・イン・タイム(JIT)アクセスを自動化するために、プロキシの管理APIを叩く仕組みや、設定ファイルのチューニングが必要になる。

ここでは、オープンソースやエンタープライズ製品で一般的に採用されている構成を想定し、実務でそのまま使えるコード片を紹介しよう。

1. プロキシ接続用セッション発行API(Pythonによる実装例)

ユーザーがダッシュボードから「サーバーへの接続ボタン」を押した際、バックエンドでZTNAプロキシのAPIを叩き、一意のワンタイム・セッションID(トークン)を発行するコードだ。

import requests
import json

# ZTNAプロキシの管理APIエンドポイント
ZTNA_API_URL = "https://ztna-proxy.corp.internal/api/v1/sessions"
API_TOKEN = "adm_sec_token_xyz123456" # 実際の運用では環境変数やSecrets Managerから取得すること

def request_privileged_session(user_email, target_host, target_port):
    """
    指定されたターゲットへの一時的なプロキシセッションを発行する
    """
    headers = {
        "Authorization": f"Bearer {API_TOKEN}",
        "Content-Type": "application/json"
    }
    
    payload = {
        "user": user_email,
        "target": {
            "host": target_host,
            "port": target_port
        },
        "ttl_seconds": 3600, # セッションの有効期限は厳格に1時間に制限
        "recording_enabled": True # PAM監査用の全セッション記録を強制
    }

    try:
        response = requests.post(ZTNA_API_URL, headers=headers, data=json.dumps(payload), timeout=10)
        response.raise_for_status()
        
        session_data = response.json()
        print(f"[INFO] セッション発行成功: Session ID -> {session_data['session_id']}")
        return session_data['session_id']

    except requests.exceptions.RequestException as e:
        print(f"[ERROR] プロキシとの通信に失敗しました: {e}")
        return None

if __name__ == "__main__":
    # 例:データベース管理サーバーのSSH(22)へのアクセスを要求
    session_id = request_privileged_session(
        user_email="network-admin@corp.example.com",
        target_host="db-master-01.internal",
        target_port=22
    )

2. プロキシ側ルーティングおよびセキュリティポリシー設定例 (YAML)

プロキシ(Nginxベースや専用のセキュアゲートウェイなど)のルーティング設定ファイルにおいて、不要なプロトコルを完全に遮断し、特定のポートフォワーディングだけを安全に許可する設定のイメージだ。

version: "3.8"
proxy_gateway:
  listen_address: "0.0.0.0"
  tls_settings:
    cert_file: "/etc/ssl/certs/ztna_proxy.crt"
    key_file: "/etc/ssl/private/ztna_proxy.key"
    min_version: "TLSv1.3" # 古いプロトコルは問答無用で拒否

  # アクセス制御ポリシー(ACL)
  access_policies:
    - name: "allow-ssh-for-ops-team"
      protocol: "ssh"
      destination_port: 22
      allowed_groups:
        - "infrastructure-engineers"
      actions:
        - mfa_required: true
        - audit_logging: true
        - max_session_duration: 7200

    - name: "allow-rdp-for-support"
      protocol: "rdp"
      destination_port: 3389
      allowed_groups:
        - "helpdesk-tier2"
      actions:
        - mfa_required: true
        - clipboard_redirection: "read-only" # クリップボード経由のデータ持ち出しを制限
        - drive_mapping: false              # ローカルドライブの転送は禁止

特にRDPの場合、clipboard_redirection(クリップボード経由のコピー&ペースト)やdrive_mapping(ローカルPCのハードディスクをリモート側にマウントする機能)は、ランサムウェアの感染経路や情報漏洩の温床になりやすい。ZTNAプロキシ側でこれらをポリシーとして無効化できる点が、単なるVPNポートフォワーディングに対する圧倒的なアドバンテージだ。

—

現場で役立つ!トラブルシューティングと運用Tips

さて、机上の空論はここまでにして、実際に導入・運用する現場で直面しがちな「ハマりどころ」と、その処方箋をいくつか伝授しよう。

1. ブラウザベースRDP(Guacamole等)での日本語キーボードのズレ

ブラウザ経由でRDPを中継する際によくあるのが、キーボード配列がUS配列に勝手に固定され、@ や _ などの記号が全く意図した通りに入力できなくなるトラブルだ。

  • 処方箋: プロキシ側のRDP接続パラメータに、明確にキーボードレイアウト(例: ja-jp-qwerty)を指定する設定を噛ませること。クライアントのブラウザ言語を自動検出する設定になっている場合でも、OSやブラウザの差異でバグることが多いため、インフラ側で強制指定するのが最も確実だ。

2. アイドルタイムアウトによるセッション切断

「作業中にちょっと席を外して戻ってきたら、SSHの画面がフリーズしてセッションが切れていた」というクレームは必ずと言っていいほど発生する。ZTNAプロキシはセキュリティの観点から、無通信状態(Idle)が続くと容赦なくセッションをKillするようにデフォルト設定されていることが多い。

  • 処方箋: セキュリティポリシーのバランスを見極めること。本番環境の踏み台であれば15分〜30分のアイドルアウトは妥当だが、開発・検証環境であれば少し長めに取るか、SSHクライアント側で定期的なKeepAliveパケット(ServerAliveInterval 60 等)を送信するようあらかじめクライアント側設定を徹底させておく。

—

まとめ:これからのインフラエンジニアに求められるスキルセット

境界防御の時代は終わった。グローバルIPを晒したまま 3389 や 22 を待ち受けるサーバーをインターネット上に置くことは、自宅の玄関の鍵を開けっぱなしにして札束を置いておくようなものだ。

ZTNAプロキシを導入し、PAMと連携させて「誰が・いつ・どのサーバーの・どのポートへアクセスし、何を実行したか」を完全に可視化・制御・記録する体制を作ることは、もはや「意識の高いセキュリティ施策」ではなく、企業の存続に関わるインフラの基礎教養である。

「VPNがあるからうちは大丈夫」と思っているそこのあなた。今夜、社内ネットワークのログを一度見返してみるといい。そこには、あなたが想像もしないような通信が飛び交っているかもしれないのだから。

コメント

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