全部產品
Search
文件中心

Elastic Compute Service:使用PrivateLink減少非必要的公網通訊

更新時間:Nov 01, 2025

在跨VPC訪問服務、跨帳號訪問服務的情境中,可以使用PrivateLink通過私網訪問服務替代通過互連網(公網)訪問,縮小公網暴露面,更好地保障網路安全。使用LB服務面向私網提供服務時可以啟用ProxyProtocol v2擷取請求真實來源VPC ID、PrivateLink終端節點ID,有助於更好地實現網路存取控制。

安全風險

在雲環境中,服務(如自建的資料庫中台、AI推理應用)或雲產品(如Object Storage Service)為了便於訪問,常常會配置公網IP(EIP)或公網端點(Endpoint)。這種做法雖然便捷,但本質上是將服務直接暴露在開放的互連網上。這會帶來嚴峻的安全風險:

  1. 公網攻擊風險:任何暴露在公網的服務都可能成為攻擊目標,面臨DDoS攻擊、應用漏洞掃描與利用、密碼暴力破解等威脅。

  2. 身份冒用風險:傳統的互連網服務主要依賴帳號密碼進行認證。一旦憑證泄露,攻擊者可以從全球任何地方冒用合法身份進行非法訪問,竊取或篡改資料。公網訪問模式無法對請求的“網路來源”進行有效限制。

  3. 資料外泄通道:內部員工可能會利用個人云帳號,通過雲端服務(如OSS)的公網端點,將企業核心資料從內部網路上傳並外泄。由於通訊發生在公網,企業側的網路原則難以有效管控。

最佳實務

使用PrivateLink實現同可用性區域跨VPC訪問

說明:在VPC2中使用ECS部署某網路服務(如Web服務),在VPC1和VPC2間建立PrivateLink,使得在VPC1中可以通過內網地址(終端節點)訪問網路服務。

注意:使用PrivateLink的前提是終端節點(服務使用方)與終端節點服務(服務提供者)必須同一可用性區域,或者終端節點部署的可用性區域是終端節點服務資源部署可用性區域的子集(如終端節點服務部署在可用性區域A、B,終端節點只能選擇A或B)

核心步驟

  1. 服務端(服務提供者)配置

    1. 建立負載平衡:在VPC2中建立一個支援PrivateLink的Server Load Balancer執行個體(如私網類型的CLB或NLB),並配置好後端伺服器組和監聽。

    2. 建立終端節點服務:進入私網串連控制台,建立一個終端節點服務,並將其關聯到上一步建立的Server Load Balancer執行個體。

    3. 佈建服務白名單(僅跨帳號需要):如果VPC1屬於不同的阿里雲主帳號,必須將該帳號的UID添加到終端節點服務的白名單中。

    4. 接受串連請求:等待用戶端發起串連後,在終端節點服務的“串連”標籤頁中,找到待處理的串連請求並“允許”它。(也可在建立服務時設定為自動接受串連)。

  2. 用戶端(服務使用方)配置

    1. 建立終端節點:在VPC1中,建立一個終端節點。在建立過程中,選擇服務端建立的終端節點服務。

    2. 配置網路:為終端節點選擇位於VPC1內的交換器和安全性群組,以控制VPC1內哪些資源可以訪問該終端節點。

    3. 擷取訪問地址:建立成功後,終端節點會產生一個私網網域名稱和IP地址。

    4. 發起訪問:等待服務端接受串連後,VPC1內的應用即可使用此網域名稱或IP來訪問VPC2中的服務。

詳細操作,請參見通過私網訪問雲端服務

使用Proxy Protocol 控制可訪問的VPC及其內網IP

通過PrivateLink提供服務,服務端能夠識別網路請求的真實來源(具體來自哪個終端節點或私人網路VPC)。因此服務可以設定安全規則,限制某使用者帳號僅能在某合法私網中使用(即僅接受合法VPC的訪問),即使帳號不慎泄露,也可以杜絕從互連網或非法網路使用該帳號訪問服務。

使用PrivateLink實現同可用性區域跨VPC訪問的基礎上,讓服務端應用能夠識別請求來自哪個VPC,並基於此實現存取控制。

  1. 開啟Proxy Protocol v2:在服務端的NLB監聽上開啟Proxy Protocol v2,並訂閱VPC ID和終端節點ID。

    # 使用阿里雲CLI為指定監聽開啟Proxy Protocol v2並訂閱VPC ID和終端節點ID
    aliyun nlb UpdateListenerAttribute \
         --ListenerId lsn-xxxxxxxxxxxxxxxx \
         --ProxyProtocolEnabled true \
         --ProxyProtocolV2Config '{"Ppv2VpcIdEnabled":true,"Ppv2PrivateLinkEpIdEnabled":true}' \
         --RegionId cn-hangzhou

    --ListenerId :是建立監聽ID,即為例子中Web服務建立的監聽。

    --ProxyProtocolEnabled:true表示開啟ProxyProtocol協議。注意:該協議會在TCP握手後的第一個包中傳遞用戶端串連資訊,因此必須後端服務支援該協議(見步驟2配置),否則服務會中斷。

    --ProxyProtocolV2Config:添加阿里雲定義的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

    03類型

    03

    4位元組

    0004

    0764b56b

    0764b56b

    TLV2

    e1類型(自訂類型)

    e1

    24位元組

    0018

    PrivateLink終端節點ID:

    ep-bp1i288487e586152d4b

    0265702d6270316932383834383765353836313532643462

    TLV3

    e1類型(自訂類型)

    e1

    26位元組

    001a

    VPC ID:

    vpc-bp179qeke0wzo1mr8bxhl

    017670632d627031373971656b6530777a6f316d72386278686c

    TLV4

    04 (PP2_TYPE_NETNS),用於傳遞網路命名空間

    04

    6位元組

    0006

    000000000000

    000000000000

  2. 假設Web服務使用Nginx提供Web服務,可以按如下配置支援Proxy Protocol:

    (Nginx Plus R16及以後版本或開源Nginx 1.13.11及以後版本支援Proxy Protocol v2)

    server {
        listen 80 proxy_protocol;   #啟用proxy Protocol v1/v2協議
        # 其他配置...
    }

    以上方式,nginx會自動處理80連接埠的proxy Protocol協議解析TLV,但原始nginx僅支援從TLV中提取ip地

    址,對於阿里自訂的VPC ID、PrivateLink終端節點ID需要lua擴充來自行處理。

    以下程式碼範例是在nginx.conf中編寫Lua外掛程式處理TCP串連,從socket中讀取TCP串連後收包,並解析TLV緩

    存進請求的上下文中。在/login請求時判斷PrivateLink終端節點ID是否符合預期。Lua程式碼範例如下:

    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. 根據地址族(family)確定地址資訊的長度
        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
            -- 確保至少有3個位元組 (Type, Length)
            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,
                -- 將二進位的value編碼為Base64,便於儲存和查看
                value = ngx.encode_base64(tlv_value) 
             })
             pos = pos + 3 + tlv_length
       end
    
       -- 8. 將解析出的TLV緩衝到ngx.ctx中
       -- ngx.ctx對於每個請求都是唯一的,是傳遞資料的標準方式
      if #tlvs > 0 then
          ngx.ctx.proxy_protocol_tlvs = tlvs
          -- 樣本:如果你想在日誌中記錄它,可以在http部分的log_format中使用$proxy_protocol_tlvs變數
          -- 需要在http塊中定義 `lua_set $proxy_protocol_tlvs 'return ngx.var.proxy_protocol_tlvs_json';`
          -- 和 `preread_by_lua_block` 中 `ngx.var.proxy_protocol_tlvs_json = cjson.encode(ngx.ctx.proxy_protocol_tlvs)`
      end
      }
      proxy_pass localhost:8080;   #後面的http請求給http server處理
      }
    }
    
    http {
        server {
            listen 8080;
    
            # 處理 /login 請求的專用 location
            location /login {
                access_by_lua_block {
                    local expected_value = "some value"
                    -- 從Nginx變數中擷取stream層傳遞過來的第2個TLV值 (Base64編碼的)
                    local privateLinkEndId = ngx.var.pp_tlv2_value
                    
                    -- 如果不符合預期則拒絕
                    if received_value ~= expected_value then
                        ngx.log(ngx.ERR, "Access to /login denied: TLV value mismatch. Expected '", 
                                expected_value, "', got '", received_value, "'")
                        return ngx.exit(ngx.HTTP_FORBIDDEN)
                    end
                    -- 驗證通過,請求將繼續被處理
                }
                
                # 驗證通過後,將請求轉寄給後端的Python程式
                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訪問雲端服務

目前部分雲端服務已經支援通過PrivateLink訪問規避公網訪問風險,1)配置私網中允許使用的合法帳號,避免

使用個人帳號外傳企業資料的風險。2)配置企業帳號只能從合法私網訪問,避免帳號泄露後在外部被利用的

風險。

  1. 為雲端服務建立終端節點:進入PrivateLink控制台,為需要訪問的雲端服務(如OSS)建立一個網關終端節

    點。將其關聯到企業內部的VPC和路由表。

  2. 配置終端節點存取原則:在終端節點的策略中,限制只允許來自企業主帳號下的RAM身份(使用者或角

    色)訪問。這可以防止其他帳號(如員工個人帳號)通過這個私網通道訪問OSS。

    在終端節點的存取原則中限制只允許企業主帳號下的所有帳號訪問雲端服務。

  3. 配置RAM策略(可選增強):為進一步加強管控,您可以在RAM中為子使用者或角色配置策略,例如,強制

    要求他們對OSS的訪問必須來自指定的VPC。

合規能力

檢查:是否存在從VPC訪問阿里雲公網地址的情況

在進行配置前,首先需要排查環境中是否存在可以被PrivateLink最佳化的公網通訊。核心思路是分析VPC流日誌,找

出從VPC內部主動訪問阿里雲公網IP的流量。

執行步驟:

  1. 開啟VPC流日誌:為核心業務所在的VPC開啟流日誌功能,並將日誌投遞到Log ServiceSLS中。

  2. 分析出向流量:在Log Service中,查詢VPC流日誌,篩選出方向為out(出向)的流量。

  3. 過濾與定位

    • 過濾回包流量:為減少幹擾,可以過濾掉目的連接埠小於1024的流量以及其他已知的正常服務回包流量。

    • 識別阿里公網IP:對日誌中的目的IP地址(dstaddr)進行歸屬地查詢(例如使用ipip.net等工具)。重點關

      注歸屬於阿里雲的公網IP。

    • 定位源頭ECS:根據流日誌中的vm-id欄位,可以精確定位到發起訪問的ECS執行個體。

日誌範例分析:

通過分析這類日誌,可以識別出哪些執行個體正在通過公網訪問其他雲上資源。

image.png

結果判斷:

  • 如果查詢到的目的公網IP是某個阿里雲服務(如OSS、ECS)的公網端點,強烈建議切換為使用PrivateLink終端

    節點訪問。

  • 如果目的公網IP是自己賬戶下的另一個VPC中的服務(如EIP+SLB),這屬於跨VPC通訊,是使用PrivateLink

    進行最佳化的理想情境。