雲開發 CloudBase(Tencent CloudBase,TCB)是騰訊雲提供的雲原生一體化開發環境和工具平台,為開發者提供高可用、自動彈性擴縮的後端雲服務,包含計算、存儲、托管等 Serverless 化能力,可用于雲端一體化開發多種端應用(小程式、公衆号、Web 應用、Flutter 用戶端等)。
本文詳細介紹了雲開發的網關架構設計遷移曆程,為什麼從雙層架構演變成單層架構,對業界有較強的參考作用。
01
引言
「雲開發網關」是以 Envoy 為底座的一款面向 APP、微信/企業微信小程式、公衆号 H5/web 的雲開發安全接入網關,提供雲開發私有鍊路、流量治理、弱網加速等能力,為應用提供安全、穩定、雲原生的接入。
安全鍊路是「雲開發網關」(以下簡稱網關)的核心能力,網關使用了私密鍊路為使用者流量安全、反爬等提供了底層支援。
1.1 HTTPS 存在的問題
全球使用 HTTPS 網站已經超過了 9 成,HTTPS 本身使用 TLS 對業務請求的流量已經進行加密,以現有的 TLS1.2/1.3 的加密強度和目前的算力來講,暴力破解可以說幾無可能。那麼網關使用私密鍊路再次對業務流量加密,是否有必要呢?其實是有必要的,針對 HTTPS 攻擊者可以使用 MITM 來擷取用戶端和伺服器傳入流量。
通過 MITM 來解密 HTTPS 的流量,需要用戶端去信任中間人頒發的第三方根證書;而安裝根證書本身有一些門檻。是以,在常見的攻擊方式中,攻擊者通常并不會使用這種方式。一是這種攻擊條件通常需要攻擊者和被攻擊者在同一個區域網路下,二是信任攻擊者的根證書需要被攻擊者配合,同時也需要管理者或者 root 權限。
使用 MITM 苛刻條件使這種方式在實際的攻擊中并不常見,更多的則是使用 Msfvenom 生成載荷,來誘騙被攻擊者執行,進而擷取機器的權限。既然 MITM 很難直接被利用,是不是在我們業務場景就可以忽略這種安全風險呢?在一般的業務中,确實可以認為使用 HTTPS 就已經達到了安全的需求,而在一些對安全場景要求較高的領域,隻使用 HTTPS 還是不夠的。比如,電商平台的價格、挂号平台的号源資訊;競對可以通過 MITM 方式實時監聽友商的商品價格,做到自己平台價格的實時調整,進而保證自己的低價優勢;黃牛可以實時查詢号源,在放号後第一時間進行挂号等等。
1.2 針對 MITM 的措施
既然 MITM 本身對業務來說存在一定的風險,通常情況下該怎麼避免呢?
- 使用 mTLS 進行雙向認證。
- 使用 SSL Pinning 做域名和證書的綁定校驗。
- 檢查使用者網絡是否使用代理或者 VPN(銀行 APP 常用)。
- 對業務再做一層加密,使用私密鍊路進行傳輸。
mTLS 的本質是用戶端和服務端證書的雙向認證,在 APP 場景下通常可以這麼解決,然而真實的業務一般都要求全端支援,H5/Web、微信小程式的場景下,并沒有權限擷取到證書的資訊。SSL Pinning 同樣有類似的問題。無論 mTLS 或者 SSL Pinning 在校驗證書的時候通常都依賴作業系統提供的系統 API 進行校驗,而系統 API 很容易使用 Xposed、Frida 等工具進行繞過。通過檢查使用者代理的方式同樣也不那麼可靠,其判斷的依據仍然依賴系統 API,同樣,攻擊者也可以使用 Tun 虛拟網卡的方式繞過。
使用私密鍊路對業務資料進行一次加密,可以很好的解決上面的問題,而且具有更好的相容性和擴充性;即使是多端的場景,使用同一套解決方案也可以很好的處理。私密鍊路除了帶來鍊路的安全性,也可以隐藏服務端的真實業務,一些自動化爬蟲和攻擊腳本由于不清楚私密鍊路的具體協定,對應的請求會被網關直接拒絕掉。
02
雙層架構設計
經過網關的流量都是 HTTP (L7 層)流量,一個标準的 HTTP 請求包含:請求行(Request line)、請求頭部(Request Header)、請求消息體(Request Body)三個部分;HTTP 傳回包含:響應狀态(Status line)、響應首部(Response Header)、響應消息體(Response Body)三個部分。
在實際業務中,一些客戶會使用 URL 參數來實作自己的簽名等鑒權敏感資訊,還有一些客戶會将敏感資訊放到請求的頭部中去。如果隻對請求的消息體進行加密,使用者的鑒權資訊仍然可能被攔截和篡改。這就要求網關不但要保護請求的消息體,也要對請求的頭部和請求行進行保護;同樣的對于業務的傳回響應狀态、響應首部、響應消息體也要進行保護。
如果直接對請求的各個部分進行加密處理,加密後直接轉發到網關,網關以同樣的方式進行解密,似乎就可以解決面臨的問題。但是,直接加密轉發的請求并不是一個标準的 HTTP,那麼請求的流量從 L7 層也就降級到了 L4 層處理。針對 APP 這種場景使用 L4 也十分合理,不過 H5/Web 的場景卻受到了限制,現代浏覽器仍然不支援 Raw Socket 的連接配接,這就會導緻 APP 的設計和 H5/Web 的架構很難統一。
2.1 業務流量封裝
無論 APP,H5/Web 還是微信小程式,都支援 HTTP 的請求。這就要求我們的架構設計,底層也需要基于 HTTP 來實作。出于安全性的考慮,又需要對業務的請求行、請求頭部、請求消息體進行加密,那麼使用 HTTP in HTTP 的傳輸方式就更加合适。将業務的請求行資訊、頭部和消息體經過加密後放到私密鍊路的消息體後再進行轉發,再結合一些序列化方式(比如:Protocol Buffers)來壓縮請求資料,即可以保證較高的性能,又可以有較小傳輸長度。
針對不同類型用戶端,可以采用分發 SDK 的方式內建到業務。業務的用戶端使用 SDK 去調用 HTTP 請求,由 SDK 來完成請求的加密。除此之外,業務的 SDK 還可以添加埋點資訊,在出現業務故障時,結合日志、告警機制可以更及時的發現問題。
2.2 早期架構設計
轉發到網關的流量需要解密後才能做進一步的處理,是以在早期的設計方案中。最先考慮的也是添加一層加解密子產品的方式來處理。對應設計為:
用戶端 HTTP -> 網關 SDK -> 加解密子產品 -> 網關叢集(底層 Envoy,通常對應 DownStream)-> 回源業務服務(通常稱為 Upstream)
加解密子產品需要大量的 CPU 運算來處理業務的請求,是以在部署的時候更适合叢集部署,隻要配置合理的 HPA 就基本可以滿足業務的需要。除了 CPU 外,還需要考慮一些特殊場景,比如:秒殺的場景下,業務服務可能因為負載增加,導緻請求的耗時增加;耗時的增加也意味短時間内連接配接數的積累,而短時間的請求數可能會進一步增加,最終可能會導緻鍊路的某一環超過負載而徹底拒絕服務。Upstream 的耗時增加可能會帶來災難性的後果,針對這種場景,加解密子產品到網關叢集的請求需要做池化處理,要盡可能的複用連接配接;同樣也要配置合适的逾時時間和連接配接保持時間,對于已經失敗的請求要快速失敗來優化這種場景。對于超出連接配接池最大數量的請求,是直接拒絕還是移除掉較早的連接配接,同樣需要結合業務實際的場景來考慮。
兩層的架構很好的适應了早期的業務場景,不過也存在一些缺陷:
- 加解密子產品缺少必要健康檢查和全死全活邏輯
- 加解密子產品監控資訊不夠完善,業務名額需要主動注冊 Promtheus;添加新的名額需要重新釋出服務
- 增加了資源成本和維護成本
在雙層架構的場景下,加解密子產品充當整個鍊路的第一跳,在高并發場景下是首當其沖的。不過加解密的性能、連接配接數并不是其主要的瓶頸;一方面加解密子產品采用 Go 協程來處理每個請求,其性能可以有很好的保證;另外,Go C10k 早就不是問題,反而是加解密子產品作為用戶端請求時,其 IP 固定,基于連接配接的四元組可知,請求的本地端口可能因為異常情況而占滿,導緻無法建立新的請求,不過有上述的池化保證,也不需要太過擔心。
03
單層架構設計
網關的底層采用了開源的 Envoy 來進行流量的轉發,而 Envoy 本身就有豐富的監控資訊以及完善的健康檢查邏輯。那麼是否可以将加解密子產品合并到 Envoy 呢?完全可以,不過一些技術難點需要解決。在雙層架構中,Envoy 處理的流量就是業務的流量,是以可以根據某些頭部做集中式限頻,動态的增加和删除某些頭部,或者根據某些資訊添加風險等級。
在單層架構中,Envoy 實際處理的流量是 HTTP in HTTP 的外層流量,即私密鍊路的流量,是以需要解決以下問題:
- Envoy 怎麼內建加解密子產品?
- Envoy 對每個請求,怎麼解析出業務流量後;覆寫私密鍊路的請求,即将私密鍊路流量替換成業務流量?
- 如何保證請求後的解密流程在限頻等邏輯之前執行,傳回後的加密流程在限頻等邏輯之後執行?
除此之外,由于采用 HTTP in HTTP,還要考慮原業務的 cookie、跨域資訊在内層流量:
- 怎樣保證私密鍊路情況下,業務 Set-Cookie 可以正常執行。
- 如何正确處理業務跨域頭部(比如:Access-Control-Allow-Origin、Access-Control-Allow-Headers 等)等等。
單層架構也是一個雲開發各類網關統一架構演進的方向,是以除了要考慮私密鍊路的場景,針對一些公網直接通路以及 WebSocket 的場景也要進行相容。
3.1 Envoy 的攔截器
Envoy 提供了多種攔截器(Envoy Filter),可以動态的過濾、修改、監聽某些字段,通過 Envoy Filter 可以實作更為複雜的業務邏輯。比較常用的攔截器有 Lua Filter、External Processing Filter 等。
Lua Filter 本身實作較為輕量,經常用于處理一些簡單的業務場景。不過,由于 Envoy 本身是多 Worker 線程處理機制,每個 Worker 都有自己的 Lua 執行環境,這也就導緻 Lua Filter 沒有真正意義上的全局變量。另外,Lua Filter 在處理每個請求的時候都是同步執行的,如果需要執行一些網絡 IO 操作,就會導緻 Envoy 的性能大幅下降。是以,網關隻會在少量修改請求或者對性能要求極高的時候才會結合使用 Lua Fitler。
External Processing Filter(以下簡稱 gRPC 攔截器) 提供了 gRPC 接口供遠端調用,可以動态的修改請求和傳回的幾乎所有資料,這正是網關私密鍊路這種場景所需要的。gRPC 攔截器會将一個請求拆成 4 個 gRPC 串行調用
- ProcessingRequest_RequestHeaders,請求頭部處理。
- ProcessingRequest_RequestBody,請求 Body 處理。
- ProcessingRequest_ResponseHeaders,傳回頭部處理。
- ProcessingRequest_ResponseBody,傳回 Body 處理。
一個加密的請求到網關之後,首先會接收到 RequestHeaders 消息,對于 RequestHeaders 處理會比較簡單,判斷是否為探測 OPTIONS 或内部健康檢查,如果是則直接傳回 204;再根據請求的 Origin 動态的傳回跨域所需要的頭部即可。RequestBody 攜帶了業務的完整請求資訊,需要先解密再做 HTTP Parser 擷取業務請求行、請求頭部和消息體;然後将解析後的資訊,覆寫掉請求的頭部和消息體;改為單層網關後,Envoy 就充當了整個鍊路的第一跳,還需要把請求的 X-Forwarded-For 複寫為 Remote 位址來防止僞造的可能。
傳回的頭部和請求的頭部處理基本一樣,一個不同點就是 Set Cookie 支援多個字段,這裡需要合并處理。對于 ResponseBody 需要重新打包加密後再進行傳回;由于業務的狀态碼可能存在異常,而私密鍊路本身是應該正常傳回,是以這裡并不能業務的狀态碼複寫到私密鍊路,以免請求異常中斷。
3.2 攔截器的順序
使用 gRPC 攔截器解決了流量加解密的問題,不過多個 Filter 的協作仍然需要處理。Envoy 在請求的時候,執行的攔截器的順序是自上而下;傳回的處理恰好相反,自下而上。
是以,在請求的時候先經過 Lua Request 的預處理,私密鍊路的 gRPC 攔截器再進行解密,解密後的流量重新發到限頻/防水牆的時候,已經是業務的資料了。在傳回的時候同樣經過 Lua Reponse 的預處理,再使用私密鍊路的 gRPC 攔截器進行封裝,這樣整個鍊路就打通了。
04
總結
通常增加一層中間層可以解決業務所遇到的問題,不過增加一層映射也帶來新的問題;增加中間層同樣需要計算資源的支撐,為了高可用勢必需要增加多副本,這就降低了整個系統的 ROI。在網關這個場景下,通過合并加解密層到網關接入層,由雙層架構演變成單層架構,網關的架構進一步統一,日志、監控、告警也可以直接複用;在目前大背景下反而是一個更優解。
作者:李皓宇
來源-微信公衆号:騰訊雲開發者
出處:https://mp.weixin.qq.com/s/ghIQ7IX2WitzMdDSzwcq1w