云原生應用安全的方法
?保護云原生應用程序需要正確理解微服務向各種消費者公開的接口(邊界)。需要在每個邊界上應用適當的工具和機制,以實現適當的安全級別。正確保護運行應用程序的基礎架構也非常重要。這包括保護容器映像、安全運行容器運行時以及正確配置和使用容器編排系統 (Kubernetes)。
微服務安全格局
在前微服務時代,大多數應用程序都遵循 MVC 架構。今天,我們將這些稱為單體應用程序。與此類應用程序相比,云原生應用程序是高度分布式的,如圖 1 所示。
圖 1:單體應用與云原生應用
單體應用程序通常有一個入口點。除此之外,除了數據庫調用或類似的交互之外,一切都發生在一個進程中。相比之下,云原生應用程序的曝光表面要高得多。如圖 1 所示,云原生應用程序通常具有多個通過網絡進行通信的組件(服務)。任何給定組件的每個入口點都需要適當保護。
保護應用程序邊界
讓我們進一步詳細說明應用程序邊界并了解我們需要擔心的實際邊界。
識別云原生應用程序中的通信邊界
典型的云原生應用程序的后端架構將包含多個業務域。每個業務領域都封裝了一組微服務。以零售系統為例;訂單處理和庫存管理可以是兩個擁有自己的微服務集合的業務領域。
業務領域內的微服務將能夠無邊界地安全地相互通信。這在圖 2 中顯示為“域內東西向流量”。可以使用雙向 TLS 實現該域內的安全通信。可以使用服務網格實現雙向 TLS 。一種更輕量級的方法可能是傳遞由其中一個網關頒發的授權令牌。我們將在本文后面討論這種方法。
圖 2:云原生應用架構
跨業務域的微服務不應該能夠自由地相互通信,除非它們被公開為 API 并明確允許通信。
這在圖 2 中顯示為“域間東西向流量”。該業務域邊界的概念在論文“基于單元的架構”中進一步解釋。
有一個清晰的邊界將所有微服務與客戶端應用程序(Web/移動應用程序)分開。這在圖 2 中顯示為“南北交通”。
API 和 API 網關的使用來保護云原生應用程序
接下來,讓我們確定我們定義為 API 和微服務的內容。任何需要暴露在給定邊界之外的微服務(或集合)都需要定義為 API。API 通常具有 OpenAPI、GraphQL、AsyncAPI 等規范。API 網關用于跨邊界公開 API。API網關的主要任務如下:
- 接受來自呼叫客戶端的消息。
- 確保客戶端擁有正確級別的身份驗證/授權。
- 將消息轉發到正確的目標(微服務)。
如圖 2 所示,API 網關保護南北通道以及域間東西通道的云原生應用程序。
OAuth2.0 在保護云原生應用程序中的作用
調用 API 的客戶端需要先從令牌服務獲取 OAuth2.0 訪問令牌,然后才能與 API 對話。API 網關在允許訪問目標之前驗證令牌并確保它是由受信任的機構頒發的。這如圖 3 所示。盡管獲得對 API 的訪問權很常見,但令牌的類型以及獲取它們的方式會根據這些令牌的用例而有所不同。
圖 3:獲取和使用 OAuth2.0 訪問令牌的工作流程
OAuth2.0 規范有一個稱為授予類型的概念,它定義了獲取訪問令牌的步驟。Prabath Siriwardena 所著的 Advanced API Security是一本了解 OAuth2.0 概念及其用例的好書。
云原生應用授權
擁有有效的訪問令牌是客戶端訪問 API 的主要要求。訪問令牌的主要好處之一是它不僅允許您調用 API,而且還可以指定您可以使用它執行的操作類型。
使用 OAuth2.0 范圍進行授權
想象一個產品目錄 API 有兩個操作:一個用于檢索產品列表(GET /product-list),另一個用于修改產品列表(PUT /product-list)。在零售商店應用程序中,所有用戶都應該能夠檢索產品列表,而只有選定的用戶應該能夠修改產品列表。
在 API 上對此進行建模的標準方法是說產品列表更新操作需要一個特殊的“范圍”。除非用于訪問此 API 的令牌具有此范圍,否則將不允許該請求。OpenAPI規范允許將范圍綁定到操作。
一旦客戶端知道它需要一個特殊的范圍來訪問一個操作,它就會請求令牌服務發出一個帶有所需范圍的令牌。當驗證請求用戶/客戶端被授權獲取請求的范圍時,令牌服務將相關范圍綁定到令牌。此工作流程如圖 4 所示。
圖 4:使用范圍訪問 API
我們可以看到身份驗證和授權在 API 網關處終止。但是在某些情況下,實際的微服務需要了解用戶/客戶端訪問服務以執行業務邏輯的詳細信息。此要求是通過 API 網關發出輔助 JWT 格式令牌(不是訪問令牌)并將其轉發到目標服務來完成的。這個輔助令牌可以在該域內的微服務中傳遞,并用于在該域內建立相互信任。
OPA 授權
除了權限之外,我們可能還需要在云原生應用程序中實現其他授權規則。考慮限制對工作日上午 8 點到下午 6 點之間可用的某些應用程序功能的訪問。雖然這些可以在微服務的源代碼中實現,但這不是一個好的做法。
這些是可以改變的組織策略。最佳實踐是將此類策略從代碼外部化。
Open Policy Agent (OPA) 是一個輕量級的通用策略引擎,不依賴于微服務。授權規則可以在Rego中實現并掛載到 OPA。
圖 5 說明了 OPA 可用于授權規則的模式。
圖 5:使用 OPA 進行授權
使用 Docker 保護容器
Docker 是最流行的打包和分發微服務的工具。Docker 容器封裝了微服務及其依賴項,并存儲在容器注冊表(私有或公共)中。
圖 6:Docker 構建和推送
外部化應用程序秘密
微服務通常依賴于數據庫、第三方 API、其他微服務等。要連接到這些類型的系統,微服務可能依賴于敏感信息(秘密),例如證書和密碼。在單體應用程序中,這些類型的信息存儲在服務器配置文件中。
只有特權用戶才能訪問服務器配置文件。但在微服務世界中,開發人員通常將此信息與微服務代碼一起存儲在屬性文件中。當開發人員構建這樣的容器并將其推送到容器注冊表時,任何可以訪問容器映像的人都可以使用此信息!
為了防止這種情況發生,我們需要將應用程序機密從代碼中外部化。讓我們看一下執行此操作的 Java 程序中的示例 Dockerfile:
FROM openjdk:17-jdk-alpine
ADD builds/sample-java-program.jar \
sample-java-program.jar
ENV CONFIG_FILE=/opt/configs/service.properties
ENTRYPOINT ["java", "-jar", "sample-java-program.jar"]
此 Dockerfile 中的第三行指示 Docker 創建一個名為的環境變量并將CONFIG_FILE其指向該/opt/configs/service.properties位置。與其在源代碼中硬編碼秘密或從固定文件位置讀取代碼,不如編寫微服務的代碼,以便它查找此環境變量的值以確定配置文件位置并將其內容加載到內存中。有了這個,我們成功地避免了代碼中的秘密。如果我們用這個文件構建一個 Docker 容器,它不會包含任何敏感信息。接下來,讓我們看看如何將我們需要的值外化。
在運行從上述 Dockerfile 構建的 Docker 映像之前,我們需要將其掛載到具有正確值的實際配置文件的位置。這可以通過以下 Dockerrun命令完成:
:\> docker run -p 8090:8090 --mount type=bind, \ source="/hostmachine/configs/service.properties" \
target="/opt/configs/service.properties"
該source部分包含容器主機上文件系統的路徑。該target部分包含容器文件系統上的路徑。該--mount命令指示 Docker 運行時將源掛載到目標上,這意味著service.properties文件現在可以安全地維護在主機的文件系統上,并在啟動容器之前掛載到容器運行時。這樣,我們將敏感信息從 Docker 上的微服務本身外部化。
Docker 內容信任
現代軟件由許多依賴項組成。軟件供應鏈是從應用程序代碼到 CI/CD 一直到生產的軟件依賴項的集合。由于惡意軟件通過其依賴鏈進入應用程序運行時,軟件供應鏈攻擊非常頻繁。
在 Docker 上運行的云原生應用程序依賴于從一個或多個存儲庫中提取的 Docker 映像。毫無戒心的開發人員可能會依賴惡意 Docker 映像,該映像隨后會危及他們的應用程序。為了防止這種情況,Docker 引入了一種稱為 Docker Content Trust (DCT) 的機制,該機制允許鏡像發布者使用加密密鑰對鏡像進行簽名,并且 Docker 鏡像的用戶可以在使用前驗證鏡像。在您的開發和 CI/CD 流程中使用DCT將確保您僅依賴于云原生應用程序中受信任且經過驗證的 Docker 映像。
開發人員需要設置一個名為的環境變量DOCKER_CONTENT_TRUST并將其值設置為 1,以在使用 Docker 的所有環境中強制執行 DCT。例如::\> export DOCKER_CONTENT_TRUST=1。設置此環境變量后,它將影響以下 Docker 命令:push、build、create、pull和run。這意味著如果您嘗試對docker run未經驗證的圖像發出命令,您的命令將失敗。
docker特權
任何操作系統都有一個稱為 root 的超級用戶。默認情況下,所有 Docker 容器都以 root 用戶身份運行。這不一定是壞事,這要歸功于 Linux 內核上的命名空間分區。但是,如果您在容器中使用文件掛載,則獲得容器運行時訪問權限的攻擊者可能非常有害。以 root 訪問權限運行容器的另一個問題是,它授予攻擊者訪問容器運行時的權限,以便將其他工具安裝到容器中。這些工具可能會以各種方式損害應用程序,例如掃描開放端口等。
Docker 提供了一種以非特權用戶身份運行容器的方法。Linux 中的 root 用戶 ID 為 0。Docker 允許我們通過傳入用戶 ID 和組 ID 來運行 Docker 容器。以下命令將在用戶 ID 900 和組 ID 300 下啟動 Docker 容器。由于這是一個非 root 用戶,因此它可以對容器執行的操作是有限的。
Docker run --name sample-program --user 900:300 nuwan/sample-program:v1
結論
正確保護云原生應用程序并非易事。API 網關和相互信任是確保我們的通信渠道安全且我們擁有零信任架構的關鍵。OAuth2.0、范圍和 OPA(或類似)是確保 API 得到正確身份驗證和授權的基礎。
超出這個范圍,我們還需要關注在 Kubernetes 上使用正確的安全最佳實踐、正確處理機密(密碼)、保護事件驅動的 API 等等。API、微服務和容器是云原生應用程序的基礎。每個開發人員都需要及時了解最新的安全進步和最佳實踐。?