すべてのプロダクト
Search
ドキュメントセンター

Elastic Compute Service:PrivateLink によるパブリックネットワークへの公開の削減

最終更新日:May 16, 2026

Virtual Private Cloud (VPC) 間またはアカウント間のプライベートアクセスに PrivateLink を利用し、Server Load Balancer (SLB) で Proxy Protocol v2 を有効にすることで送信元 VPC とエンドポイントを識別して、きめ細かいアクセス制御を実現します。

セキュリティリスク

クラウド環境では、自社構築のデータベースプラットフォーム、AI 推論アプリケーション、Object Storage Service (OSS) などの Alibaba Cloud サービスは、利便性のために EIP またはパブリックエンドポイントで構成されることがよくあります。これにより、サービスがインターネットに直接公開され、次のようなセキュリティリスクが生じます:

  1. パブリックネットワーク攻撃のリスク:インターネットに公開されたサービスは、DDoS 攻撃、脆弱性スキャンと悪用、ブルートフォース攻撃の標的になり得ます。

  2. なりすましのリスク:従来のインターネットサービスは、ユーザー名とパスワード認証に依存します。認証情報が漏えいすると、攻撃者はどこからでも正規ユーザーになりすましてアクセスし、データの窃取や改ざんを行えます。パブリックアクセスでは、リクエストの送信元ネットワークを制限できません。

  3. データ漏えいの経路:社内従業員が個人のクラウドアカウントを使用し、OSS などの Alibaba Cloud サービスのパブリックエンドポイント経由で企業データを持ち出す可能性があります。このトラフィックはインターネットを経由するため、企業のネットワークポリシーを適用しにくくなります。

ベストプラクティス

PrivateLink を使用した同一ゾーン内での VPC 間アクセス

image

説明:ネットワークサービス (Web サービスなど) を VPC2 の Elastic Compute Service (ECS) インスタンスにデプロイします。VPC1 と VPC2 の間に PrivateLink 接続を作成すると、VPC1 からプライベート IP アドレス (エンドポイント) を介してサービスにアクセスできます。

エンドポイント (サービスコンシューマー) のゾーンは、エンドポイントサービス (サービスプロバイダー) のゾーンのサブセットである必要があります。たとえば、エンドポイントサービスがゾーン A とゾーン B にデプロイされている場合、エンドポイントはゾーン A またはゾーン B のいずれかにのみ作成できます。

主要手順

  1. 1. サーバー側 (サービスプロバイダー) の構成

    1. 1. ロードバランサーの作成:VPC2 で、PrivateLink をサポートする SLB インスタンス (プライベートネットワーク向けの Classic Load Balancer (CLB) または Network Load Balancer (NLB) など) を作成します。バックエンドサーバーグループとリスナーを設定します。

    2. 2. エンドポイントサービスの作成:PrivateLink コンソールでエンドポイントサービスを作成し、前の手順で作成した SLB インスタンスに関連付けます。

    3. 3. サービスホワイトリストの設定 (アカウント間アクセスのみ):VPC1 が別の Alibaba Cloud アカウントに属している場合は、そのアカウントの UID をエンドポイントサービスのサービスホワイトリストに追加します。

    4. 4. 接続リクエストの承認:クライアントが接続を開始した後、エンドポイントサービスの [接続] タブに移動し、保留中のリクエストを承認します。代わりに、接続を自動承認するようサービスを設定することもできます。

  2. 2. クライアント側 (サービスコンシューマー) の構成

    1. 1. エンドポイントの作成:VPC1 でエンドポイントを作成し、サーバー側で作成したエンドポイントサービスを選択します。

    2. 2. ネットワークの設定:エンドポイントに対して VPC1 の vSwitch とセキュリティグループを選択し、VPC1 内のどのリソースがアクセスできるかを制御します。

    3. 3. エンドポイントの詳細の取得:作成後、プライベートドメイン名と IP アドレスが生成されます。

    4. 4. アクセスの開始:サービスプロバイダーが接続を承認すると、VPC1 のアプリケーションは生成されたドメイン名または IP アドレスを使用して VPC2 のサービスにアクセスできます。

詳細については、「プライベートネットワーク経由で Alibaba Cloud サービスにアクセス」をご参照ください。

Proxy Protocol を使用したアクセス元 VPC とプライベート IP アドレスのアクセス制御

PrivateLink 経由でサービスを提供する場合、サーバーは各リクエストの送信元エンドポイントまたは VPC を識別し、それに応じてセキュリティルールを適用できます。たとえば、正当な VPC からのアクセスのみを受け付けるようにアカウントを制限できます。認証情報が漏えいした場合でも、未承認のネットワークからはサービスにアクセスできません。

PrivateLink を使用した同一ゾーン内での VPC 間アクセスを基に、サーバー側アプリケーションで送信元 VPC を識別し、アクセス制御を実装できます。

  1. 1. Proxy Protocol v2 の有効化:NLB リスナーで Proxy Protocol v2 を有効にし、VPC ID とエンドポイント ID を取得します。

    # Cloud Assistant CLI を使用して、指定したリスナーで Proxy Protocol v2 を有効にし、VPC ID とエンドポイント ID を取得します。
    aliyun nlb UpdateListenerAttribute \
         --ListenerId lsn-xxxxxxxxxxxxxxxx \
         --ProxyProtocolEnabled true \
         --ProxyProtocolV2Config '{"Ppv2VpcIdEnabled":true,"Ppv2PrivateLinkEpIdEnabled":true}' \
         --RegionId cn-hangzhou

    --ListenerId:Web サービス用に作成したリスナー ID。

    --ProxyProtocolEnabledtrue に設定して Proxy Protocol を有効にします。このプロトコルは、TCP ハンドシェイク後の最初のパケットでクライアント接続情報を渡します。バックエンドサービスはこのプロトコルをサポートする必要があります。詳細はステップ 2 を参照してください。サポートしていない場合、サービスが中断されます。

    --ProxyProtocolV2Config:Alibaba Cloud 独自の Type-Length-Value (TLV) フィールドを追加します。この例では、送信元 VPC ID と PrivateLink エンドポイント ID を取得します。image.pngimage.png次の表に、TLV の解析方法を示します。

    説明/内容

    元の内容

    プロトコルヘッダー

    Proxy Protocol の固定シグネチャ

    0d0a0d0a000d0a515549540a

    バージョン 2、PROXY コマンド

    21

    TCP/IPv4

    11

    後続のデータ長は 84 バイト

    0054

    アドレス情報

    送信元 IP:10.0.0.14

    0a00000e

    宛先 IP:10.0.0.15

    0a00000f

    送信元ポート:59074

    e6c2

    宛先ポート:80

    0050

    TLV1

    Type 03

    03

    4 バイト

    0004

    0764b56b

    0764b56b

    TLV2

    Type e1 (カスタムタイプ)

    e1

    24 バイト

    0018

    PrivateLink エンドポイント ID:

    ep-bp1i288487e586152d4b

    0265702d6270316932383834383765353836313532643462

    TLV3

    Type e1 (カスタムタイプ)

    e1

    26 バイト

    001a

    VPC ID:

    vpc-bp179qeke0wzo1mr8bxhl

    017670632d627031373971656b6530777a6f316d72386278686c

    TLV4

    04 (PP2_TYPE_NETNS)、ネットワーク名前空間を渡すために使用

    04

    6 バイト

    0006

    000000000000

    000000000000

  2. 2. Web サービスで Nginx を使用している場合は、Proxy Protocol をサポートするように設定します:

    (NGINX Plus R16 以降、またはオープンソース Nginx 1.13.11 以降が必要です。)

    server {
        listen 80 proxy_protocol;   # Proxy Protocol v1/v2 を有効にします。
        # その他の設定...
    }

    Nginx はポート 80 で Proxy Protocol TLV を自動的に解析しますが、標準版では TLV から IP アドレスを取得することのみサポートしています。

    Lua 拡張を使用して、Alibaba Cloud 独自の VPC ID と PrivateLink エンドポイント ID を処理します。

    次の nginx.conf の Lua プラグインは、TCP 接続を処理し、ソケットからパケットを読み取り、TLV バッファを解析します。

    解析したデータはリクエストコンテキストに保存されます。/login リクエストが行われると、コードは PrivateLink エンドポイント ID を検証します:

    -- http ブロックの前に共有メモリを定義します。
    lua_shared_dict tlv_cache 10m;
    
    stream {
      server {
        listen 80;  # proxy_protocol は有効にしません。以下の Lua コードで処理します。
        preread_by_lua_block {
           -- 1. 固定の 16 バイトのプロトコルヘッダーを読み取ります。
           local sock = ngx.req.socket(true)
           local header, err = sock:receive(16)
    
           if not header then
              ngx.log(ngx.ERR, "failed to receive proxy protocol header: ", err)
              return
           end
    
           -- 2. プロトコルシグネチャ (12 バイト) を確認します。
           if string.sub(header, 1, 12) ~= "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A" then
               -- Proxy Protocol でない場合、読み取ったデータをバッファに戻して元のリクエストを破損させないようにします。
                sock:setreused(header)
                      return
          end
    
          -- 3. プロトコルヘッダーの残りを解析します。
          local ver_cmd = string.byte(header, 13) -- バージョンとコマンド
          local family = string.byte(header, 14) -- アドレスファミリーとプロトコル
          local len = string.byte(header, 15) * 256 + string.byte(header, 16) -- 可変部分の合計長
          if len == 0 then
             -- アドレス情報または TLV 情報がありません。処理を終了します。
             return
          end
    
          -- 4. 可変長部分 (アドレスと TLV を含む) を読み取ります。
          local variable_part, err = sock:receive(len)
          if not variable_part or #variable_part < len then
             ngx.log(ngx.ERR, "failed to read variable part (address + tlvs): ", err)
             return
          end
    
          -- 5. アドレスファミリーに基づいてアドレス情報の長さを判定します。
          local addr_len = 0
          local protocol = family & 0x0F
          local address_family = family >> 4
    
          if address_family == 1 then -- AF_INET (IPv4)
              addr_len = 12
          elseif address_family == 2 then -- AF_INET6 (IPv6)
              addr_len = 36
          elseif address_family == 3 then -- AF_UNIX
              addr_len = 216
          end
    
          -- 6. アドレス情報と TLV データを分離します。
          local address_block = string.sub(variable_part, 1, addr_len) -- 必要に応じてアドレス情報を解析できます。
          local tlv_string = string.sub(variable_part, addr_len + 1)
    
          -- 7. TLV データを解析します。
          local tlvs = {}
          local pos = 1
          while pos <= #tlv_string do
              -- Type と Length のために少なくとも 3 バイト存在することを確認します。
              if pos + 2 > #tlv_string then
                  ngx.log(ngx.ERR, "malformed TLV data: not enough bytes for type and length")
                  break
              end
    
              local tlv_type = string.byte(tlv_string, pos)
              local tlv_length = string.byte(tlv_string, pos + 1) * 256 + string.byte(tlv_string, pos + 2)
          
              -- value の長さが境界を超えないことを確認します。
              if pos + 2 + tlv_length > #tlv_string then
                  ngx.log(ngx.ERR, "malformed TLV data: length exceeds available data")
                  break
              end
          
              local tlv_value = string.sub(tlv_string, pos + 3, pos + 2 + tlv_length)
                          
              table.insert(tlvs, {
                  type = tlv_type,
                  length = tlv_length,
                  -- バイナリ値を Base64 にエンコードして、保存と表示を容易にします。
                  value = ngx.encode_base64(tlv_value) 
               })
               pos = pos + 3 + tlv_length
         end
    
         -- 8. 解析した TLV を共有メモリにキャッシュします。
         if #tlvs > 0 then
            local cjson = require "cjson"
            local tlv_cache = ngx.shared.tlv_cache
            -- クライアント IP アドレスをキーとして TLV データを保存します。
            local client_ip = ngx.var.remote_addr
            local tlv_json = cjson.encode(tlvs)
            tlv_cache:set(client_ip, tlv_json, 60)  -- 60 秒間キャッシュします。
            -- オプション:stream レイヤーで使用できるように ngx.ctx にも保存します。
            ngx.ctx.proxy_protocol_tlvs = tlvs
         end
        }
        proxy_pass localhost:8080;   # 以降の HTTP リクエストを HTTP サーバーに渡して処理します。
      }
    }
    
    http {
        server {
            listen 8080;
    
            # /login リクエストを処理する専用ロケーションです。
            location /login {
                access_by_lua_block {
                    local cjson = require "cjson"
                    local tlv_cache = ngx.shared.tlv_cache
                    -- 共有メモリから TLV データを取得します。
                    local client_ip = ngx.var.remote_addr
                    local tlv_json = tlv_cache:get(client_ip)
                    if not tlv_json then
                       ngx.log(ngx.ERR, "No TLV data found for client: ", client_ip)
                       return ngx.exit(ngx.HTTP_FORBIDDEN)
                    end
                    local tlvs = cjson.decode(tlv_json)
                    -- 2 つ目の TLV (PrivateLink エンドポイント ID) の値を抽出します。
                    local privateLinkEndId = nil
                    if tlvs[2] then
                       privateLinkEndId = tlvs[2].value  -- Base64 エンコードされた値です。
                    end
                    -- 期待されるエンドポイント ID (Base64 エンコード) を定義します。
                    local expected_value = "AmVwLWJwMWkyODg0ODdlNTg2MTUyZDRi"  -- 例の値です。Alibaba Cloud 独自の TLV プレフィックス (0x02) を含む値の Base64 エンコード文字列です。実際の値に合わせて変更してください。
                    -- エンドポイント ID を検証します。
                    if privateLinkEndId ~= expected_value then
                       ngx.log(ngx.ERR, "Access to /login denied: TLV value mismatch. Expected '", expected_value, "', got '", tostring(privateLinkEndId), "'")
                       return ngx.exit(ngx.HTTP_FORBIDDEN)
                    end
                    -- 検証に成功した場合、リクエスト処理を継続します。
                }
                proxy_pass http://your_python_backend;
            }
    
            # その他すべてのリクエストを処理します。
            location / {
                proxy_pass http://your_python_backend;
            }
        }
    
        # Python バックエンドサービスの upstream 定義です。
        upstream your_python_backend {
            server 127.0.0.1:5000; # バックエンドプログラムはポート 5000 で実行されている想定です。
        }
    }

PrivateLink を使用した Alibaba Cloud サービスへのアクセス

一部の Alibaba Cloud サービスは PrivateLink アクセスをサポートしています。これにより、認可されたアカウント向けにプライベートネットワークアクセスを構成し、パブリックネットワークのセキュリティリスクを回避できます。

個人アカウントはデータ漏えいのリスク要因となります。企業アカウントを設定し、正当なプライベートネットワークからのアクセスのみを許可してください。これにより、認証情報が侵害された場合でも悪用を防止できます。

脅威シナリオ

  1. 1. Alibaba Cloud サービス用のエンドポイントの作成:PrivateLink コンソールで、OSS など、アクセスする Alibaba Cloud サービス向けにゲートウェイエンドポイントを作成し、企業の VPC とルートテーブルに関連付けます。

  2. 2. エンドポイントアクセスポリシーの設定:企業の Alibaba Cloud アカウントに属する RAM ID (ユーザーやロールなど) のみにアクセスを制限します。これにより、従業員の個人アカウントなど、他のアカウントがこのプライベートチャネル経由で OSS にアクセスすることを防止できます。

  3. 3. RAM ポリシーの設定 (任意):よりきめ細かい制御を行うには、RAM ユーザーまたはロール向けにポリシーを設定し、OSS へのアクセスが指定した VPC から発信されるように強制します。

コンプライアンス機能

VPC から Alibaba Cloud のパブリック IP アドレスへのアクセスの確認

VPC フローログを分析し、PrivateLink で最適化できる VPC から Alibaba Cloud のパブリック IP アドレスへのアウトバウンドトラフィックを特定します。

手順:

  1. 1. VPC フローログの有効化:コア業務が稼働する VPC でフローログを有効にし、ログを Simple Log Service (SLS) に転送します。

  2. 2. アウトバウンドトラフィックの分析:SLS コンソールで VPC フローログをクエリし、direction = out でアウトバウンドトラフィックをフィルタリングします。

  3. 3. フィルタリングと特定

    • 応答パケットのフィルタリング:分析に関係のないトラフィック (例えば、NTP のような管理プロトコル) や、既知のサービス応答トラフィックを除外し、ノイズを低減します。

    • Alibaba Cloud のパブリック IP アドレスの特定:ipip.net などのツールを使用して宛先 IP アドレス (dstaddr) の所有者を調べます。Alibaba Cloud に属するパブリック IP アドレスに注目してください。

    • 送信元 ECS インスタンスの特定:フローログの vm-id フィールドを使用して、アクセスを開始した ECS インスタンスを特定します。

ログ分析の例

これらのログを分析して、インターネット経由でクラウドリソースにアクセスしているインスタンスを特定します。

image.png

結果の検証:

  • 宛先パブリック IP アドレスが OSS や ECS などの Alibaba Cloud サービスのパブリックエンドポイントである場合は、PrivateLink エンドポイントに切り替えてください。

  • 宛先パブリック IP アドレスが、アカウント内の別の VPC にあるサービス (EIP や SLB など) を指している場合、この VPC 間通信は PrivateLink で最適化できます。