如果使用者身分對您的應用程式很重要,您就需要一種方法來管理它。這通常意味著使用者帳戶,您會在有人註冊時建立它們。

這控制了誰可以登入,但他們能做什麼呢?

為此,您需要角色或群組,它們與使用者一樣,都來自組織的身分識別提供者——Entra、Google、Okta 等。群組構成了 Firezone 存取模型的基础。它們決定了誰可以存取什麼。

將使用者和群組導入您的應用程式的過程稱為目錄同步,而要做好這件事卻出奇地棘手。在這篇文章中,我們將介紹目錄同步的確切含義、實施它的主要標準,以及為什麼我們選擇完全放棄它,從頭開始建立自己的引擎。

組織的目錄是其中工作人員名單以及他們被允許存取的內容。它存在於身分識別提供者中,由三部分組成:

目錄同步是將該目錄複製到您的應用程式並保持更新的過程。身分識別提供者是真相的來源,因此您的應用程式持有的是一份副本。隨著時間的推移,該副本必須處理三種變更:新使用者和群組、名稱變更等更新,以及移除。當有人加入、離開或更換團隊時,理想情況是您的應用程式能快速得知。

值得澄清的是,目錄同步不是單一登入 (SSO)。這似乎很明顯,但許多人將兩者混淆。主要的驗證標準,如 OpenID Connect,對於如何將使用者導入應用程式沒有任何說明。在目錄同步中,我們指的是將使用者(和群組)導入的確切機制,而不是他們如何被驗證(有關此,請參閱這篇文章)。

假設您的組織有以下目錄:

在您的資料庫中,它可能看起來像這樣:

為了回答您應用程式中常見的存取問題,例如「Bob 是否是 Product 的成員?」,您必須查看 Product 的成員,然後是其中任何群組的成員,依此類推,直到找到 Bob。

為了避免這種情況,我們可以展平群組,為每個使用者提供一個屬於他們(直接或間接)的群組的列:

現在,同一個問題變成了一個簡單的查找。

好的,這就是資料模型。現在我們需要將它從身分識別提供者導入我們的資料庫,並保持更新。

當然,您不必自己發明這一切。有一個標準可以做到這個目錄同步的事情。它叫做 SCIM,以下是它的自我描述:

跨網域身分識別管理系統 (SCIM) 規格是一個基於 HTTP 的協定,透過標準化服務,讓跨網域場景中的身分識別管理更容易支援。

實際上,這意味著您需要在應用程式中建立一個 REST 端點列表,例如:

然後身分識別提供者會呼叫它們來保持目錄同步。

SCIM 的好處是身分識別提供者會將變更推送到您的應用程式,因此從提供者更新發生到它在您的應用程式中落地之間的延遲時間,可能比輪詢提供者排程的拉取式方法要低得多。

聽起來很棒。但這裡有什麼問題呢?

首先,您的應用程式必須幾乎一直處於運行狀態並準備好接收目錄更新。即使是幾秒鐘的停機或過載,也可能意味著您會錯過關鍵的目錄更新。無法從您的應用程式觸發完整同步,因此您會丟失這些更新,直到身分識別提供者決定再次告知您。

但 SCIM 更令人惱火的是,雖然它在標準化協定(即傳輸格式)方面做得很好,但它並沒有說明這些端點應該如何被呼叫,它們在提供者中映射到什麼生命週期事件,甚至每個請求包含什麼樣的數據。

身分識別提供者在實施 SCIM 的方式上差異很大。以下是一些範例:

當涉及到使用者停用時,這個關鍵操作是切斷離職員工的存取權,會出現更多的不一致:

這一切都歸結為現實,您最終會得到許多不同的、特定於提供者的 SCIM 程式碼路徑,而不是您希望編寫的那個一致的實現。

走 SCIM 路線的實現,可能比您單獨命中每個提供者的 API 的拉取式系統要複雜(且脆弱)得多。

透過拉取式同步,您來進行呼叫。您的伺服器會根據排程(或應使用者要求)命中身分識別提供者的 API,遍歷目錄,並將其與您的資料庫進行協調。每個提供者的 API 都略有不同,但形狀總是相同的,因此大部分工作可以共享。

身分識別提供者有列出使用者、群組以及群組成員的 API 端點。驗證後,一個簡單的同步演算法可以是:

足夠簡單。對於像我們範例這樣的小型目錄,您可以遍歷整個目錄,一次性收集所有數據,然後將其寫入資料庫。

較大的目錄需要更長的時間來遍歷。目錄遍歷的時間越長,在您將累積的狀態刷新到資料庫之前丟失它的風險就越大。在錯誤的時間部署,您就必須重新開始,延遲新目錄數據進入資料庫的時間。

您可以做一些事情來稍微緩解這個問題,那就是用一個時期 (epoch) 初始化每次同步,在過程中檢查點 (checkpoint) 數據,然後最終移除所有比時期更舊的記錄:

大小並不是讓這件事變得困難的唯一因素。隨著目錄的增長,還會出現其他一些問題:

您獲取的數據越少,同步就越快。大多數目錄中有很多與您的應用程式無關的內容,例如承包商、服務帳戶,以及用於郵件列表和辦公室地點的數百個群組。

在可能的情況下,讓提供者進行篩選。只請求分配給您應用程式的使用者和群組,而不是所有內容。這意味著需要閱讀的頁面更少,呼叫的次數也更少,這也有助於緩解速率限制的壓力。

您可以就此停止,並獲得一個相當可靠的目錄同步引擎。但如果您使用目錄來設定存取規則(就像我們在 Firezone 中所做的那樣),您就有可能在同步排程之間錯過重要的更新。

這個過程非常「最終一致」:最終您的資料庫將反映組織在身分識別提供者中的目錄。

上述過程是完整同步——遍歷目錄,獲取每個使用者、群組和成員,然後與您的資料庫進行協調——插入缺失的內容並移除已消失的內容。

根據提供者的速率限制和目錄的大小,此同步可能快至幾秒鐘,或有時超過一小時。在此期間,目錄很有可能已經發生了變化:新員工入職,另一位員工離職,還有其他人休了育嬰假。

延遲新員工入職可能會導致該員工無法登入。這肯定很令人惱火。但延遲員工終止卻有風險——在您導入該目錄變更之前,該員工仍然擁有存取權。他們會竊取公司機密嗎?

SCIM 本應有所幫助,但我們已經看到了結果。我們還能做些什麼來減少延遲?

減少延遲的一種方法是停止遍歷整個目錄。記住您上次停下的地方,下次只詢問提供者哪些內容發生了變化。有些提供者為此內建了功能(Entra 稱之為 delta query)。

我們仔細研究了這一點,並決定不採用。主要原因是我們之前展平的群組:

對我們來說,delta 同步解決了錯誤的問題。目標不是避免完整同步。而是使完整同步快速且可靠到足以信任,並透過其他方式解決延遲問題。

事實證明,身分識別提供者很樂意提供自己的即時 API 集,您可以使用它們來獲取推送式的目錄更新——無需 SCIM!(Google、Entra、Okta、JumpCloud)。此外,這些 API 通常比 SCIM 的更新速度更快。例如,Entra 的 SCIM 更新延遲時間長達 40 分鐘。

Firezone 採用了一種混合方法,將這些 API 與上述優化的完整同步方法相結合,為客戶提供兩全其美:定期協調整個目錄以確保不會錯過更新,並透過即時觸發器為關鍵變更提供近乎即時的更新。由於即時觸發器處理緊急變更,因此完整同步可以更少地運行,作為安全網,從而節省 API 配額。

最後一塊是確保兩者不會互相覆蓋。您的作業處理系統中的簡單序列化佇列可以很好地處理這個問題。將每個即時通知視為重新讀取提供者對象的信號,而不是盲目信任通知的內容,也有所幫助。

一旦您在實際組織中嘗試過,目錄同步就會有很多活動部件。大型目錄、不穩定的 API、對「已刪除」含義有不同看法的提供者,以及互相干擾的作業都需要處理。SCIM 將其中大部分留給您的應用程式,每個提供者一次,並剝奪了您在出現問題時詢問提供者真相的能力。

從提供者的 API 拉取數據讓您能夠控制何時查看、信任什麼以及在出現問題時該怎麼做。在頂部添加提供者的即時掛鉤,您就可以獲得 SCIM 所承諾的大部分功能,而負擔卻少得多。

這裡還有更多我們沒有涵蓋的邊緣案例。例如,您如何將包含一些相同使用者的兩個(或更多)目錄同步到同一個帳戶?當組織遷移到另一個身分識別提供者,或兩個公司合併時,就會發生這種情況。Firezone 支持這一點,其身分識別方面在 Alice 問題中有介紹。

目錄同步今天可在我們的 Enterprise 套餐中使用。如果這是您組織的重要功能,請預約時間仔細了解其工作原理並進行試用。

賦予您的組織應有的保護。

WireGuard 是 Jason A. Donenfeld 的註冊商標。

Firezone 是 Firezone, Inc. 的註冊商標。

無需 SCIM 的可靠目錄同步 | Firezone 部落格無需 SCIM 的可靠目錄同步 | Firezone 部落格無需 SCIM 的可靠目錄同步 | Firezone 部落格無需 SCIM 的可靠目錄同步 | Firezone 部落格無需 SCIM 的可靠目錄同步 | Firezone 部落格無需 SCIM 的可靠目錄同步 | Firezone 部落格