邊緣認證和與令牌無關的身份傳播

charlieroro發表於2021-02-17

邊緣認證和與令牌無關的身份傳播

翻譯自Edge Authentication and Token-Agnostic Identity Propagation。通過本文可以瞭解到Netflix是如何通過將認證轉移到邊緣裝置來降低系統內容內部的認證流程,以及如何使用統一的認證結構支援系統對身份資訊的需求。

正如大多數開發人員認為的那樣,對安全協議和身份令牌,以及使用者和裝置身份驗證的處理可能會充滿挑戰。假設有很多協議,令牌,200M+的使用者,以及上千個裝置,問題可能隨時會在範圍內爆發。幾年前,我們決定通過發起一個新的計劃,組建一個新的團隊來解決這種複雜性,將使用者和裝置身份驗證以及各種安全協議和令牌的複雜處理移至(由一組集中式服務和一個團隊管理的)邊緣網路上。在這個過程中,我們更改了身份在服務之間的傳播方式,轉而使用支援加密驗證且令牌無關的身份物件。

通過本文可以瞭解到:

  • 如何降低服務所有者的複雜度,服務所有者不需要再瞭解並負責終結安全協議,以及處理無數的安全令牌;
  • 通過將令牌管理委派給在該領域具有專業知識的服務和團隊來提高安全性;
  • 提高審計能力和取證分析。

我們是如何做到的

Netflix最初是一個允許會員管理其DVD佇列的網站。該網站後來提供了流內容,流裝置稍晚一些,但是這些初始裝置的功能受到了限制。隨著時間的推移,流裝置的功能越來越強大,曾經只能通過網站訪問的功能也擴充到了流裝置上。Netflix服務的擴充套件非常快速,目前已經支援多達2000種裝置型別。

支援這些功能的服務識別多種令牌以及安全協議(用於驗證使用者和裝置,並授權訪問這些功能)的負擔也越來越重。整個系統變得相當複雜,開發也變得脆弱。加上邊緣層的架構已經演化到PaaS模型,我們需要確定如何,以及在哪裡處理身份令牌。

複雜度:多個服務處理認證令牌

為了展示流的複雜度,下面描述了在架構修改前,使用者是如何登入的:

從最高層面看,此流程(大大簡化)涉及的步驟如下:

  1. 使用者輸入憑據,然後Netflix客戶端將憑據以及裝置的ESN傳輸到邊緣閘道器,即Zuul;
  2. Zuul將使用者呼叫重定向到API/登入終端;
  3. API服務編排後端系統,驗證使用者身份。
  4. 成功驗證請求提供的宣告後,API服務會返回cookie給上游,包括customerId 和ESN,以及一個到期指令;
  5. 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) …
}

UserInfoDeviceInfo 包含了請求的SourcePassportAuthenticationLevelSource是一個宣告型別列表,為使用的協議以及用於驗證宣告的服務。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作為身份之後,前面所述的登入流程演變為了以下內容:

邊緣認證和與令牌無關的身份傳播
  1. 使用者輸入憑據,Netflix客戶端將裝置ESN和憑據傳送到邊緣閘道器,即Zuul;
  2. Zuul上執行的身份過濾器會生成一個繫結裝置的Passport,然後將其傳送到API/登入終端;
  3. API服務將Passport傳播到負責認證使用者的中間層服務;
  4. 在成功認證提供的宣告之後,這些服務會建立併傳送一個Passport Action(伴隨原始Passport),同時將流備份到API和Zuul;
  5. 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)的體驗訪問控制提供了支援。

相關文章