邊緣認證和與令牌無關的身份傳播
翻譯自Edge Authentication and Token-Agnostic Identity Propagation。通過本文可以了解到Netflix是如何通過将認證轉移到邊緣裝置來降低系統内容内部的認證流程,以及如何使用統一的認證結構支援系統對身份資訊的需求。
正如大多數開發人員認為的那樣,對安全協定和身份令牌,以及使用者和裝置身份驗證的處理可能會充滿挑戰。假設有很多協定,令牌,200M+的使用者,以及上千個裝置,問題可能随時會在範圍内爆發。幾年前,我們決定通過發起一個新的計劃,組建一個新的團隊來解決這種複雜性,将使用者和裝置身份驗證以及各種安全協定和令牌的複雜處理移至(由一組集中式服務和一個團隊管理的)邊緣網絡上。在這個過程中,我們更改了身份在服務之間的傳播方式,轉而使用支援加密驗證且令牌無關的身份對象。
通過本文可以了解到:
- 如何降低服務所有者的複雜度,服務所有者不需要再了解并負責終結安全協定,以及處理無數的安全令牌;
- 通過将令牌管理委派給在該領域具有專業知識的服務和團隊來提高安全性;
- 提高審計能力和驗證分析。
我們是如何做到的
Netflix最初是一個允許會員管理其DVD隊列的網站。該網站後來提供了流内容,流裝置稍晚一些,但是這些初始裝置的功能受到了限制。随着時間的推移,流裝置的功能越來越強大,曾經隻能通過網站通路的功能也拓展到了流裝置上。Netflix服務的擴充非常快速,目前已經支援多達2000種裝置類型。
支援這些功能的服務識别多種令牌以及安全協定(用于驗證使用者和裝置,并授權通路這些功能)的負擔也越來越重。整個系統變得相當複雜,開發也變得脆弱。加上邊緣層的架構已經演化到PaaS模型,我們需要确定如何,以及在哪裡處理身份令牌。
複雜度:多個服務處理認證令牌
為了展示流的複雜度,下面描述了在架構修改前,使用者是如何登入的:

從最高層面看,此流程(大大簡化)涉及的步驟如下:
- 使用者輸入憑據,然後Netflix用戶端将憑據以及裝置的ESN傳輸到邊緣網關,即Zuul;
- Zuul将使用者調用重定向到API/登入終端;
- API服務編排後端系統,驗證使用者身份。
- 成功驗證請求提供的聲明後,API服務會傳回cookie給上遊,包括customerId 和ESN,以及一個到期指令;
- Zuul發送Cookies到NetFlix用戶端。
該模型有一些問題,如:
- 外部有效的令牌被深深地嵌入到調用棧中,是以需要一直向上遊傳播,可能會導緻記錄不合理的日志或導緻潛在的管理問題。
- 上遊系統必須重新打開令牌來識别使用者登入,并可能管理多個并行的身份資料結構,很可能導緻資料不同步。
多協定&令牌
本例展示了處理一個協定(HTTP/S)以及一個令牌類型(Cookies)的流程。在Netflix的流産品中使用了一些協定和令牌,概括如下:
Netflix 的流生态系統會消費(有可能會更改)這些令牌,如:
更複雜的是,可以通過多種方法在系統之間傳輸這些令牌或令牌中包含的資料。在某些情況下會不斷打開令牌,從中抽取身份資料元素,作為API調用使用的簡單基元或字元串,或通過請求上下文首部或URL參數在系統間傳遞。整個過程中并不會檢查令牌或令牌中包含的資料的完整性。
Netflix 的規模
同時,Netflix 的規模在以指數增長。現在,Netflix 已經有200M+的訂閱,以及每月上百萬個活動的裝置。我們每秒要服務超過2.5百萬個請求,相當大一部分用于某種格式的認證。在老的架構中,每一個請求都會觸發一個API調用,用來驗證請求中聲明的内容,如下所示:
加入EdgePaaS
後續業務的變動使得情況變得更複雜,邊緣工程團隊正在将老的API服務架構遷移到一個新的基于PaaS的方式。在我們遷移到EdgePaaS的同時,前後端服務也從基于Java的API遷移到了BFF(backend for frontend),即NodeQuark:
該模型可以在不依賴核心API架構的前提下讓前後端工程擁有和操作各自的服務。但這也引入了另一層複雜性,即這些NodeQuark服務如何處理身份令牌?NodeQuark服務是用JavaScript編寫的,廢除了像MSL這樣複雜又浪費的協定,這些協定會複制所有令牌管理的邏輯。
做個總結,在大規模場景下,發現我們使用了一個複雜且低效的方案來處理認證和身份令牌。我們有多種身份令牌類型和資源,每種身份令牌又需要不同的處理,各個處理邏輯被複制到了多個系統中。關鍵身份資料以不一緻的方式在整個伺服器生态系統中傳播。
使用邊緣認證解決問題
我們意識到,為了解決這個問題,需要一個統一的身份模型,在上遊進一步處理身份驗證令牌(和協定)。我們通過将認證和協定終結轉移到邊緣網絡,然後建立一個新的完整性保護的且令牌無關的對象,使該對象在整個伺服器生态系統中傳播。
将認證轉移到邊緣
注意,我們的目标是提升安全性,并降低複雜度,進而提供更好的使用者體驗,我們就如何将裝置身份驗證操作以及使用者辨別和身份驗證令牌管理集中到服務邊緣制定了相應的政策。
從上層看,Zuul(雲網關)作為令牌檢查和載荷加密/解密的終結點。這種情況下,Zuul可以處理這些操作(一小部分),例如,如果沒有出現令牌,則需要更新,否則視為無效。Zuul會将這些操作委派給一組新的邊緣身份驗證服務,用來處理加密密鑰交換以及令牌的建立或更新。
邊緣認證服務
邊緣認證服務(EAS)是一個架構理念,包含将裝置和使用者的認證和身份驗證從棧轉移到雲邊緣,以及用于處理令牌類型而開發的服務套件。
EAS是運作在Zuul中的一系列過濾器,可能會調用外部服務來支援域(domain),如調用一個服務來處理MSL 令牌或Cookies的其他令牌。EAS涵蓋了為隻讀令牌建立"Passport"(稍後會涉及到)。
EAS處理請求的基本模式如下:
對于每個進入Netflix 服務的請求,Zuul中的EAS入站過濾器會檢查裝置用戶端提供的令牌,然後将請求轉發到"Passport"檢查過濾器(Passport Injection Filter),或某個認證服務進行處理。Passport Injection Filter會生成一個令牌無關的身份,然後使用該身份在剩餘的服務生态系統中傳播。在響應路徑上,在邊緣認證服務的協助下,EAS出站過濾器會生成需要發送到用戶端裝置的令牌。
現在系統架構的格式如下:
注意令牌永遠不會越過邊緣網關/EAS邊界。MSL安全協定會在邊緣網關上終結,且所有的令牌會在網關上打開,然後以一種令牌無關的方式在服務生态系統中傳播身份資料。
在新的處理路徑上,Zuul能夠處理大量有效且未過期的令牌,邊緣認證服務處理剩餘的請求。
EAS服務具有容錯性,例如在Zuul辨別Cookies有效但已過期,且對EAS的續約調用失敗或某些潛在的錯誤情況下:
這種失敗場景下,Zuul中的EAS過濾器将會容忍這種錯誤,并允許解析後的身份繼續傳播,并在下一次請求時重新排程續約調用。
令牌無關的身份(Passport)
使用簡單的可變身份結構是遠遠不夠的,因為這樣會導緻服務到服務間傳遞的身份缺少足夠的信任。此時需要令牌無關的身份結構。
我們引入了一個稱為"Passport"的身份結構,它允許以統一的方式傳播使用者和裝置身份資訊。Passport也是一種令牌,但相比使用外部令牌,使用内部結構能帶來很多好處。然而,下遊系統仍然需要通路使用者和裝置身份。
Passport 是一種由邊緣網關為每個請求建立的短生命的身份結構,即它的生存時間取決于請求的生命周期,且僅在Netflix生态系統内部有效。Passport由Zuul通過一組身份過濾器生成。一個Passport包含使用者&裝置身份,格式為protobuf,其完整性由HMAC保證。
Passport 結構
如上所述,Passport 模型為一個Protocol Buffer。從高層看,Passport 的定義如下:
message Passport { Header header = 1; UserInfo user_info = 2; DeviceInfo device_info = 3; Integrity user_integrity = 4; Integrity device_integrity = 5; }
Header元素傳達了建立Passport的服務的名稱。更有意義的是傳播的與使用者和裝置有關的内容。
使用者&裝置資訊
UserInfo 元素包含識别發起請求的使用者所需的所有資訊,DeviceInfo 元素包含使用者通路Netflix的裝置所需的所有資訊:
message UserInfo { Source source = 1; int64 created = 2; int64 expires = 3; Int64Wrapper customer_id = 4; … (some internal stuff) … PassportAuthenticationLevel authentication_level = 11; repeated UserAction actions = 12; } message DeviceInfo { Source source = 1; int64 created = 2; int64 expires = 3; StringValue esn = 4; Int32Value device_type = 5; repeated DeviceAction actions = 7; PassportAuthenticationLevel authentication_level = 8; … (some more internal stuff) … }
UserInfo
和
DeviceInfo
包含了請求的
Source
PassportAuthenticationLevel
。
Source
是一個聲明類型清單,為使用的協定以及用于驗證聲明的服務。
PassportAuthenticationLevel
為放到認證聲明中的信任的級别。
enum Source { NONE = 0; COOKIE = 1; COOKIE_INSECURE = 2; MSL = 3; PARTNER_TOKEN = 4; … } enum PassportAuthenticationLevel { LOW = 1; // untrusted transport HIGH = 2; // secure tokens over TLS HIGHEST = 3; // MSL or user credentials }
下遊應用可以使用這些值來進行授權或決定使用者體驗。
Passport 的完整性
Passport 的完整性由HMAC保證(基于哈希的消息認證碼),HMAC是一種特定類型的MAC,涉及密碼哈希函數和密鑰,可以同時用于校驗資料完整性和消息的真實性。
使用者和裝置的完整性定義如下:
message Integrity { int32 version = 1; string key_name = 2; bytes hmac = 3; }
當Integrity的version為1表示為HMAC使用SHA-256,編碼為ByteArray。未來Integrity的version可能使用一個不同的哈希函數或編碼。在version為1時,HMAC字段包含MacSpec.SHA_256中的256位。
完整性防護保證Passport 字段在Passport建立之後不會改變。用戶端應用可以在使用其中包含的任何值之前,通過Passport Introspector檢查Passport的完整性。
Passport Introspector
Passport對象本身是不透明的。用戶端可以使用Passport Introspector從首部抽取Passport,并檢索其中的内容。Passport Introspector是Passport二進制資料的包裝器。用戶端可以通過一個工廠建立一個Introspector,然後就通路基本的通路器方法:
public interface PassportIntrospector { Long getCustomerId(); Long getAccountOwnerId(); String getEsn(); Integer getDeviceTypeId(); String getPassportAsString(); … }
Passport Actions
下面定義了Passport 協定緩沖,以及Passport Actions 的定義:
message UserInfo { repeated UserAction actions = 12; … } message DeviceInfo { repeated DeviceAction actions = 7; … }
當對使用者或裝置身份進行更新時,下遊服務會顯示地發送一個Passport Actions。EAS 會使用該信号來建立或更新對應類型的令牌。
重新審視登入流程
讓我們總結一下所有這些解決方案一起工作的例子。
在将認證和協定終結轉移到邊緣,并引入Passports作為身份之後,前面所述的登入流程演變為了以下内容:
- 使用者輸入憑據,Netflix用戶端将裝置ESN和憑據傳送到邊緣網關,即Zuul;
- Zuul上運作的身份過濾器會生成一個綁定裝置的Passport,然後将其傳送到API/登入終端;
- API服務将Passport傳播到負責認證使用者的中間層服務;
- 在成功認證提供的聲明之後,這些服務會建立并發送一個Passport Action(伴随原始Passport),同時将流備份到API和Zuul;
- Zuul會調用Cookie服務來解析Passport和Passport Actions,然後将Cookies傳回Netflix用戶端。
主要的好處
簡化授權
存在外部令牌流入下遊系統的原因是,授權決策經常會依賴令牌中的認證聲明,且信任與各種令牌類型相關聯。在我們的Passport結構中為信任配置設定了不同的級别,意味着,需要授權決策的系統可以圍繞Passport編寫合理的規則,而無需在很多服務的代碼中重複信任規則。
顯示的,可擴充的身份模型
具有規範身份的結構非常有用。傳遞身份原始資料的方式比較脆弱且難以調試。如果在一個調用聲明中,使用者的身份從服務A切換到了服務D,那麼誰會發生改變?一旦身份結構通過所有關鍵系統,一種相對簡單的方式是添加一個新的外部令牌類型,新的信任級别,以及新的方式來表示該身份。
操作問題和可見性
擁有一個像Passport的結構,可以允許定義一個使用Passport定義的服務,并且可以被其他服務校驗。當傳播Passport且在日志中看到該Passport時,我們可以打開、校驗、了解其身份内容。也可以了解到Passport的來曆,并跟蹤到它是如何進入系統的。這使得調試異常身份問題變得更加容易。
降低下遊系統的複雜度&負載
傳遞一個統一的結構到下遊系統,意味着這些系統可以使用内省庫檢視裝置和使用者身份(由于使用了相同的結構,是以無需單獨處理各個類型的外部令牌)
通過将令牌處理從這些系統解除安裝到中央邊緣認證服務上,下遊系統在CPU、請求延遲、垃圾回收名額當方面獲得了可觀的收益,所有這些都可以幫助降低叢集占用空間以及雲支出。下面例子中的受益都來源于主要的API服務。
在前面的實作中,每個請求必須承擔兩次解密/終止開銷,因為我們需要在邊緣具有路由的能力,且需要在下遊服務中具有豐富的終止請求的能力。某些性能的提高歸因于這些功能的合并-現在僅需要處理一次MSL請求。
CPU的RPS占比
解除安裝令牌的處理使得每個請求的CPU開銷降低了30%,并降低了40%的平均負載。下圖展示了CPU的RPS比率,越低越好:
API響應時間
API服務的響應時間有了很大提升,降低了30%的平均延遲,并使99%的延遲降低20%:
垃圾回收
顯著降低了API服務的垃圾回收的壓力,以及GC等待值時間,下圖展示了垃圾回收的STW名額:
開發者速度
将微服務開發人員和身份驗證和身份相關的問題剝離開來,意味着他們可以專注于其核心領域。現在僅在一組專門的服務中完成一次對身份認證的更改即可,而無需将變更散布到多個服務中。
下一步
更強大的認證
我們目前正在擴充邊緣認證服務來通過一個新的服務"Resistor"支援多因子認證。基于機器學習模型選擇性地為可疑的連接配接引入第二個因素。随着加入了新的流程,我們引入了新的因素,例如使用一次性密碼(OTP)來發送郵件或電話,給移動裝置推送通知,以及使用第三方認證應用等。
我們還可能為希望在其帳戶上增加安全性的使用者引入可選擇的多重身份驗證。
靈活的授權
現在我們已經有一個系統層面的身份驗證流,在授權決策中我們可以使用該身份驗證流作為一個信号。去年,我們開始探索一個新的産品通路策列(PACS),現在正在将其引入Netflix流産品中。PACS最近為Streamfest( a weekend of free Netflix in India)的體驗通路控制提供了支援。