Categories
程式開發

如何為1500個微服務建立網絡隔離?


Monzo安全團隊的目標之一是邁向完全可信的平台。這意味著,理論上,我們可以在平台上運行惡意代碼,而不會有任何風險。如果沒有安全團隊授予特殊的訪問權限,這些代碼將無法與任何危險的東西交互。

在我們致力於平台研發的過程中,花了一部分時間來研究網絡隔離,這意味著我們不希望可以控制Pot上鏡像的服務能夠與轉移資金的服務進行對話。服務應該明確定義並手動批准可以與之通信的列表,其他任何內容都應該被阻止。

當服務比較少的時候,我們可以手動維護這些列表。但是,我們已經有超過1500個服務,所以允許路徑的列表非常大,並且在不斷變化。實際上,有超過9300個惟一連接(例如,從service.emoji到service.ledger)。

服務和連接數量太多

大量的服務和連接使得這個項目非常具有挑戰性。我們必須找到一種方法,從代碼中生成允許的路徑,並用一種便於工程師管理和機器解釋的方式存儲規則,然後在不造成任何破壞的情況下執行它們。

這是我們在這個項目中詳細列出的網絡。每個服務都用點表示。每一條線都是一個強制的網絡規則,它表示允許兩個服務之間進行通信。

如何為1500個微服務建立網絡隔離? 1

下面這幅圖是同一幅圖,不過刪除了一些大眾化的節點,並著了色:

如何為1500個微服務建立網絡隔離? 2

如果你對我們為什麼決定構建這種系統感興趣,請查看我們在2016年寫的關於構建現代化後台的博文,或者是我們的一位工程師做的關於如何使用微服務的演講。

隔離一個服務

在決定隔離所有服務之前,我們決定隔離我們安全級別最高的其中一個服務service.ledger,這是客戶餘額和所有資金流動的事實來源。我們的一個工程原則是先發布項目,然後迭代,所以最好從小項目開始,特別是對於這樣一個複雜的系統。我們首先選擇如此重要的服務的原因是,財務團隊非常想要鎖定這項服務,並且非常願意在我們的試驗中幫助我們。

用工具分析代碼

首先,我們編寫了一個名為rpcmap的工具。它讀取平台中的所有Go代碼,並嘗試查找看起來像是在向另一個服務發出請求的代碼。在此過程中,工具繪製出這些服務和調用服務之間的連接。雖然並不完美,但它足以構建一個需要調用service.ledger的服務列表。然後我們手動檢查這個列表以確保它的準確性。

最終,我們得到了一個簡單的服務列表。接下來,我們希望強制要求只有列表中的服務才能向service.ledger發出請求。我們知道,可以使用Kubernetes提供的一個非常簡單的特性NetworkPolicy資源來實現這一點(Kubernetes是我們的服務編排平台)。該策略是分類賬配置的一部分,只列出了一組允許調用的服務。只允許來自具有正確標籤的源的流量,例如,我們允許標記為service.pot的服務。當工程師需要向分類賬添加新調用者時,他們將其添加到此列表並重新部署分類賬。我們將上述文件存儲在與分類賬代碼相同的地方,這樣當你修改它時,財務團隊必須對其進行審查。這使他們能夠跟踪誰在調用這樣一個關鍵的服務,這對確保我們對服務調用的掌控是很重要的。我們對新代碼進行自動檢查,提醒工程師在調用分類賬時將他們的調用服務加入白名單列表。

問題

對於分類賬服務,我們的方法是行之有效的,但我們知道無法將其擴展到所有的服務,有幾個幾個方面的原因:

  1. 在推出這些策略之前,我們沒有一個安全的方法來測試,而且檢查我們生成的策略需要大量的手動工作。我們需要能夠推出一種策略,明確地知道它是否能減少流量,但不是實際地減少流量。這將使我們自動生成策略,而不是手動檢查它們,然後只需等待幾天,看看它們是否正確。

  2. 雖然這種方法對於分類賬服務行之有效,但團隊並不總是想要審查每一個服務的新調用者。因此,將允許的服務列表作為接收服務的屬性並不理想。

  3. 工程師們必須編輯Kubernetes的配置文件才能做出這樣一份白名單。這通常是他們不習慣做的事情,因此很容易出錯。

  4. 回滾服務變得非常危險。如果你返回到服務的早期版本以糾正代碼更改,你也可能會回滾到服務調用者被加入白名單之前,從而突然阻塞其流量。這突出了這種方法的一個基本問題。 service.emoji調用service.ledger是emoji的一種屬性,應該這樣部署。但在上面我們使它成為ledger的一個屬性。白名單的服務成為共享狀態,可能會不同步,難以維護。我們希望遵循單一責任原則

因此,我們希望找出測試策略的方法,重新思考我們在哪裡定義規則。

測試策略

我們實際上是使用Calico來實現Kubernetes的網絡策略的。 Calico是一個網絡軟件,它可以讓我們的服務互相通信。我們與Calico社區討論了測試網絡策略的問題,發現我們可以使用Calico的一些特性來測試我們的策略,而這些特性是Kubernetes無法訪問的。

當我們將網絡策略應用到服務時,我們決定給它一個標籤。然後,我們使用一種Calico網絡策略,它有一些Kubernetes沒有提供給我們的額外特性,讓我們可以聲明以下內容:

  1. 進入服務的流量上是否有網絡策略;
  2. 該流量不在其策略允許範圍內;
  3. 記錄流量,然後允許該流量。

該策略就像下面這個樣子:

如何為1500個微服務建立網絡隔離? 3

本質上,由於處於高階域,這個策略會在Kubernetes網絡策略之後運行。因此,如果流量到達了這個策略,它一定沒有被Kubernetes的策略所允許。

有能力只選擇已經具有策略的服務至關重要。否則,像上面這樣的一個預演策略將在沒有自己的策略的情況下捕獲服務的所有流量,而且我們不知道,如果沒有預演策略,流量會降低多少。

觀察網絡

有了這個預演策略,流量不會有任何丟棄。而且,在流量丟棄時,我們還會得到一個日誌項。我們使用一個名為kube-iptables-tailer的工具可以很方便地查看這些日誌,這樣我們就可以創建一些圖表,顯示哪些服務的流量正在丟棄以及從哪裡丟棄。

我們有點擔心日誌記錄過多(來自會丟棄流量負載的不正確的策略)會在我們的平台上產生問題。所以我們決定,只有在我們證明了這個機率很低的情況下,才會啟用日誌功能。為此,我們編寫了一個新工具calico- accounting,它能夠計算給定服務將丟棄多少包,而不需要實際記錄它們。這個計數對於查看哪些服務具有糟糕的策略非常有用。但是在某些情況下,我們仍然需要日誌,因為日誌還顯示了“被丟棄”流量的源服務。

即使我們開始實施網絡策略以丟棄不允許的流量,我們仍然保持日誌功能,這樣我們就可以快速地提醒工程師有流量被丟棄,無論是來自Bug還是攻擊者。

重新設計表示服務連接的方法

我們考慮了很多假設,從service.emoji到service.ledger的流量應該表示為emoji的屬性,並且想出了不少點子。本質上,我們希望每個服務以某種方式指定它需要與其他哪些服務進行通信,然後進行匯總,從而明確service.ledger的入站服務。

一開始,我們想為每個鏈接寫一個網絡策略(emoji→ledger)。但是大量的策略會導致嚴重的性能問題。然後我們意識到,我們可以簡單地給服務貼上它們需要的標籤。如果你需要和service.ledger通信,你可以貼個標籤monzo.com/egress-s-ledger。然後,service.ledger的網絡策略可能是這樣的:

如何為1500個微服務建立網絡隔離? 4

這是一個突破性進展!現在,我們可以為所有服務編寫直觀、一致的策略,並且有一個非常簡單的方法來表示服務調用者的目的地服務。

編寫服務策略

我們知道,我們還必須覆蓋那些需要與大量服務進行對話的服務。例如,我們的監控服務幾乎可以與任何東西對話。我們不希望監控服務針對平台中的每個服務都有一個標籤,因為這是一個龐大且不斷變化的列表。

我們可以在每個服務的策略中添加一些東西來允許監視。但是,要添加調用所有內容的新服務,我們必須使用新策略部署所有服務。我們決定添加另一個標籤service-type,對可以調用它們的服務進行鬆散的分組。

例如,如果你有後端服務的類型標籤,你可以被一組服務調用。如果你有api標籤,你的調用組會稍微不同。我們通過一些“catch-all”策略來實現,比如:

如何為1500個微服務建立網絡隔離? 5

這些策略只選擇已經具有網絡策略的內容,這意味著它們只能允許新的流量,並且沒有阻塞任何流量的風險。

管理規則

以上是一種告訴計算機允許哪些路徑的很好的方法。但如果我們指望工程師自己更新這些標籤,那將是一場噩夢。我們不能要求人們考慮是否需要將服務列入白名單,然後將服務名稱轉換為標籤,然後找到正確的配置文件將那個標籤添加進去。相反,我們想要完全自動化這個過程。

  1. 首先,我們更新了rpcmap,這樣,如果我們在一個服務上運行它,它將掃描它所調用的任何內容,並生成“規則文件”。每個被調用的服務都有一個簡單的文件,它表示A調用B的事實。 service.emoji會有一個文件service.emoji/egress/service.ledger.rule。

  2. 我們將rpcmap設置為在每個人的代碼上運行,只要他們將代碼推送到GitHub。這可以提醒工程師保持規則的更新。它還在接受代碼之前檢查規則文件。當然,規則文件也需要人工查看。

  3. 如果一個團隊想要跟踪關鍵服務的調用者,他們可以指示GitHub要求他們對/service.ledger.rule進行審核。因此,即使規則文件位於另一個團隊的服務中,也會要求他們查看規則文件。

  4. 接下來,我們更新了部署管道,將上面的規則文件轉換為服務標籤。我們能夠保證目標服務的所有者可查看對服務的調用,並使服務之間的連接非常容易理解。這也使得找出什麼服務調用分類賬變得非常容易——你只需查找名為service.ledger.rule的文件。在我們的平台上強制執行規則的好處是,它為我們提供了可證明的信息——我們知道分類賬的調用者名單是詳盡無遺的。

推廣到1500個微服務

有了所有這些控制措施,儘管服務數量龐大,我們還是能夠相當迅速地推廣。首先,我們為所有服務生成規則文件,並開始強制維護它們以使代碼被接受。從那時起,我們有了一個相當精確的服務連接網絡。

接下來,我們將所有網絡策略部署到我們的測試環境中,這與實際運行Monzo的平台相同。在這個過程中,我們發現並修復了許多rpcmap無法推斷某個服務調用了另一個服務的情況,因為這些情況出現了流量丟棄。我們通常通過在代碼中添加一個特殊的註釋來修復這些情況,該註釋告訴rpcmap關於鏈接的信息:

如何為1500個微服務建立網絡隔離? 6

我們還必須手動覆蓋非Go服務(我們沒有很多)。在幾天內,我們修復了測試環境中的所有丟棄,所以我們改變了試運行策略,現在我們可以丟棄不允許的流量。

現在,我們可以更加確信工程師不會意外地交付沒有適當規則文件的代碼,因為這樣的代碼在測試環境中會失敗。

接下來,我們將網絡策略部署到我們的生產平台上,禁用了丟包和日誌記錄功能,但是如果有任何違反規則的情況,就會發出警報(這已經是安全方面的一大勝利)。由於潛在的巨大日誌量,我們還一直禁用日誌記錄,直到我們推出了所有允許流量的標籤。一旦我們使用calico- accounting證明了日誌量很小時,我們就可以通過記錄丟棄的流量來發現rpcmap不夠聰明的地方。這讓我們把丟棄數降為零。

我們決定將丟棄策略關閉一個月,以確保沒有任何很少使用的路徑是不被允許的。我們仍然可以從對違規的警告中獲得很多安全價值,但是當我們完全確定在正常的業務流程中不會丟失任何東西時,我們也會打開丟棄策略。

只調用其他六個服務

我們已經從每個服務可以調用其他1500個服務,發展到每個服務平均只能調用其他6個服務,並對每個新的配對進行了審查。解決圍繞管理這些規則的人類問題是一個特別有趣的挑戰。這就安全性而言是一場令人難以置信的勝利,也是邁向可信任平台的一大步。

英文原文:

We built network isolation for 1,500 services to make Monzo more secure