從gRPC安全設計理解雙向證書方案


  • 序言web

  • 安全需求算法

  • 安全方案後端

    • 敏感數據加密傳輸瀏覽器

    • 認證緩存

    • 鑑權安全

    • 數據完整性和一致性服務器

  • 證書的基本原理微信

    • 單向證書網絡

    • 雙向證書運維

  • gRPC安全機制

    • SSL/TLS認證

    • GoogleOAuth2.0

    • 自定義安全認證策略

序言

網絡安全領域在攻和防對抗規模羣體已經成熟,可是兩端從業者對於安全原理掌握程度良莠不齊,中間鴻溝般的差距構成了漏洞研究領域的主戰場。筆者「三省吾身」,在工做中會犯錯誤把一些加密、認證、鑑權的概念和實現方案搞混,尤爲是加解密涉及算法和公私鑰機制的概念不深刻細節。

最近的幾個影響頗大的安全漏洞,Apache Shiro 權限繞過漏洞、CVE-2020-14882weblogic 繞過登陸、微軟ZeroLogon,這些漏洞原理的共同點都是和基本的安全算法、認證鑑權方案缺陷有關。也許將來的漏洞攻防將轉移到安全基礎領域的對抗,從業人員除了要求推動安全方案的必要性,涉及安全建設的可用性更爲重要,因此特此開專欄系列,爲你們普及一些安全基本功。

本文主要經過介紹gRPC的雙向認證方案,理清證書領域的知識。

安全需求

RPC是一種技術思想,實現有阿里的 Dubbo/SOFA、Google gRPC、Facebook 的 Thrift,實現時的遠程通訊規範和協議能夠用RMI、Socket、SOAP(HTTP XML)、REST(HTTP JSON)。這種服務間通訊機制爲企業內部各系統、模塊之間的微服務和接口之間互相調用,RPC實現須要考慮安全性,RPC 調用安全主要涉及以下三點:

  1. 我的 / 企業敏感數據加密:例如針對我的的帳號、密碼、手機號等敏感信息進行加密傳輸,打印接口日誌時須要作數據模糊化處理等,不能明文打印;

  2. 對調用方的身份認證:調用來源是否合法,是否有訪問某個資源的權限,防止越權訪問;

  3. 數據防篡改和完整性:經過對請求參數、消息頭和消息體作簽名,防止請求消息在傳輸過程當中被非法篡改。

安全方案

常見的安全攻防重視rpc協議的反序列漏洞,可是若是業務方問道若是作以上的安全需求,SDL同窗就傻眼了,正確的作法是區分加密傳輸、認證、鑑權、數據完整性和一致性四個方向:

敏感數據加密傳輸

基於SSL/TLS的通道加密

當存在跨網絡邊界的 RPC 調用時,每每須要經過 TLS/SSL 對傳輸通道進行加密,以防止請求和響應消息中的敏感數據泄漏。跨網絡邊界調用場景主要有三種:

  1. 後端微服務直接開放給端側,例如手機 App、TV、多屏等,沒有統一的 API 網關/SLB 作安全接入和認證;
  2. 後端微服務直接開放給 DMZ 部署的管理或者運維類 Portal;
  3. 後端微服務直接開放給第三方合做夥伴 / 渠道。

除了跨網絡以外,對於一些安全等級要求比較高的業務場景,即使是內網通訊,只要跨主機 /VM/ 容器通訊,都強制要求對傳輸通道進行加密。在該場景下,即使只存在內網各模塊的 RPC 調用,仍然須要作 SSL/TLS。

使用 SSL/TLS 的典型場景以下所示:

通道加密的的實現技術難度稍大,對性能有損耗,定製化程度高,可是效果顯著,建設收益明顯

針對敏感數據的單獨加密

有些 RPC 調用並不涉及敏感數據的傳輸,或者敏感字段佔比較低,爲了最大程度的提高吞吐量,下降調用時延,一般會採用 HTTP/TCP + 敏感字段單獨加密的方式,既保障了敏感信息的傳輸安全,同時也下降了採用 SSL/TLS 加密通道帶來的性能損耗,對於 JDK 原生的 SSL 類庫,這種性能提高尤爲明顯。

它的工做原理以下所示:

敏感數據加密

一般使用 Handler 攔截機制,對請求和響應消息進行統一攔截,根據註解或者加解密標識對敏感字段進行加解密,這樣能夠避免侵入業務。

採用該方案的缺點主要有兩個:

  • 對敏感信息的識別可能存在誤差,容易遺漏或者過分保護,須要解讀數據和隱私保護方面的法律法規,並且不一樣國家對敏感數據的定義也不一樣,這會爲識別帶來不少困難;
  • 接口升級時容易遺漏,例如開發新增字段,忘記識別是否爲敏感數據。

認證

內部 RPC 調用的身份認證場景,主要有以下兩大類:

  • 防止對方知道服務提供者的地址以後,繞過註冊中心 / 服務路由策略直接訪問 RPC 服務提供端;
  • RPC 服務只想供內部模塊調用,不想開放給其它業務系統使用(雙方網絡是互通的)。

身份認證的方式較多,例如 HTTP Basic Authentication、OAuth2 等,比較簡單使用的是令牌認證(Token)機制,它的工做原理以下所示:

工做原理以下:

  • RPC 客戶端和服務端經過 HTTPS 與註冊中心鏈接,作雙向認證,以保證客戶端和服務端與註冊中心之間的安全;
  • 服務端生成 Token 並註冊到註冊中心,由註冊中心下發給訂閱者。經過訂閱 / 發佈機制,向 RPC 客戶端作 Token 受權;
  • 服務端開啓身份認證,對 RPC 調用進行 Token 校驗,認證經過以後才容許調用後端服務接口。

鑑權

身份認證能夠防止非法調用,若是須要對調用方進行更細粒度的權限管控,則須要作對 RPC 調用作鑑權。例如管理員能夠查看、修改和刪除某個後臺資源,而普通用戶只能查看資源,不能對資源作管理操做。

在 RPC 調用領域比較流行的是基於 OAuth2.0 的權限認證機制,它的工做原理以下:

OAuth2.0 的認證流程以下:

  • 客戶端向資源擁有者申請受權(例如攜帶用戶名 + 密碼等證實身份信息的憑證);
  • 資源擁有者對客戶端身份進行校驗,經過以後贊成受權;
  • 客戶端使用步驟 2 的受權憑證,向認證服務器申請資源訪問令牌(access token);
  • 認證服務器對受權憑證進行合法性校驗,經過以後,頒發 access token;
  • 客戶端攜帶 access token(一般在 HTTP Header 中)訪問後端資源,例如發起 RPC 調用;
  • 服務端對 access token 合法性進行校驗(是否合法、是否過時等),同時對 token 進行解析,獲取客戶端的身份信息以及對應的資源訪問權限列表,實現對資源訪問權限的細粒度管控;
  • access token 校驗經過,返回資源信息給客戶端。

步驟 2 的用戶受權,有四種方式:

  • 受權碼模式(authorization code)
  • 簡化模式(implicit)
  • 密碼模式(resource owner password credentials)
  • 客戶端模式(client credentials)

須要指出的是,OAuth 2.0 是一個規範,不一樣廠商即使遵循該規範,實現也可能會存在細微的差別。大部分廠商在採用 OAuth 2.0 的基礎之上,每每會衍生出本身特有的 OAuth 2.0 實現。

對於 access token,爲了提高性能,RPC 服務端每每會緩存,不須要每次調用都與 AS 服務器作交互。同時,access token 是有過時時間的,根據業務的差別,過時時間也會不一樣。客戶端在 token 過時以前,須要刷新 Token,或者申請一個新的 Token。

考慮到 access token 的安全,一般選擇 SSL/TLS 加密傳輸,或者對 access token 單獨作加密,防止 access token 泄漏。

關於oauth做爲安全基本功系列從此還會有專欄。

數據完整性和一致性

RPC 調用,除了數據的機密性和有效性以外,還有數據的完整性和一致性須要保證,即如何保證接收方收到的數據與發送方發出的數據是徹底相同的。

利用消息摘要能夠保障數據的完整性和一致性,它的特色以下:

  1. 單向 Hash 算法,從明文到密文的不可逆過程,即只能加密而不能解密;
  2. 不管消息大小,通過消息摘要算法加密以後獲得的密文長度都是固定的;
  3. 輸入相同,則輸出必定相同。

目前經常使用的消息摘要算法是 SHA-一、MD5 和 hmac,MD5 可產生一個 128 位的散列值。SHA-1 則是以 MD5 爲原型設計的安全散列算法,可產生一個 160 位的散列值,安全性更高一些。hmac 除了可以保證消息的完整性,還可以保證來源的真實性。

因爲 MD5 已被發現有許多漏洞,在實際應用中更多使用 SHA 和 hmac,並且每每會把數字簽名和消息摘要混合起來使用。微信支付、阿里雲調用是你們經常使用的簽名機制,注意消息摘要不是加密,不是加密,不是加密。

證書的基本原理

目前使用最廣的 SSL/TLS 工具 / 類庫就是 OpenSSL,它是爲網絡通訊提供安全及數據完整性的一種安全協議,囊括了主要的密碼算法、經常使用的密鑰和證書封裝管理功能以及 SSL 協議。注意SSL和TLS有不一樣的歷史和標準,HTTPS的意思是HTTP +SSL/ TLS,如今的安全方案通常是tls實現,SSL標準正被淘汰。只是由於沿襲歷史稱呼,因此常常混用兩次名詞, SSL被發現存在過 POODLE, DROWN協議算法自己的漏洞,注意區分大名鼎鼎的心臟滴血漏洞Heartbleed是OpenSSL的實現TLS和DTLS的心跳處理邏輯時有bug,而不是利用SSL/TLS協議自己的缺陷。

單向證書

https是你們最熟悉的單項證書方案,由瀏覽器、ca中心、服務端三方實現。單向認證的過程,客戶端從服務器端下載服務器端公鑰證書進行驗證,而後創建安全通訊通道。單向認證流程中,服務器端保存着公鑰證書和私鑰兩個文件,整個握手過程以下:

單向認證流程
  1. 客戶端發起創建HTTPS鏈接請求,將SSL協議版本的信息發送給服務器端;
  2. 服務器端將本機的公鑰證書(server.crt)發送給客戶端;
  3. 客戶端讀取公鑰證書(server.crt),取出了服務端公鑰;
  4. 客戶端生成一個隨機數(密鑰R),用剛纔獲得的服務器公鑰去加密這個隨機數造成密文,發送給服務端;
  5. 服務端用本身的私鑰(server.key)去解密這個密文,獲得了密鑰R
  6. 服務端和客戶端在後續通信過程當中就使用這個密鑰R進行通訊了。

雙向證書

雙向通訊流程,客戶端除了須要從服務器端下載服務器的公鑰證書進行驗證外,還須要把客戶端的公鑰證書上傳到服務器端給服務器端進行驗證,等雙方都認證經過了,纔開始創建安全通訊通道進行數據傳輸。

雙向認證流程
  1. 客戶端發起創建HTTPS鏈接請求,將SSL協議版本的信息發送給服務端;
  2. 服務器端將本機的公鑰證書(server.crt)發送給客戶端;
  3. 客戶端讀取公鑰證書(server.crt),取出了服務端公鑰;
  4. 客戶端將客戶端公鑰證書(client.crt)發送給服務器端;
  5. 服務器端解密客戶端公鑰證書,拿到客戶端公鑰;
  6. 客戶端發送本身支持的加密方案給服務器端;
  7. 服務器端根據本身和客戶端的能力,選擇一個雙方都能接受的加密方案,使用客戶端的公鑰加密後發送給客戶端;
  8. 客戶端使用本身的私鑰解密加密方案,生成一個隨機數R,使用服務器公鑰加密後傳給服務器端;
  9. 服務端用本身的私鑰去解密這個密文,獲得了密鑰R
  10. 服務端和客戶端在後續通信過程當中就使用這個密鑰R進行通訊了。

整個雙向認證的流程跑通,最終須要五個證書文件:

  • 服務器端公鑰證書:server.crt
  • 服務器端私鑰文件:server.key
  • 客戶端公鑰證書:client.crt
  • 客戶端私鑰文件:client.key
  • 客戶端集成證書(包括公鑰和私鑰,用於瀏覽器訪問場景):client.p12

生成這一些列證書以前,咱們須要先生成一個CA根證書,而後由這個CA根證書頒發服務器公鑰證書和客戶端公鑰證書。

證書生成

證書實現的核心是加密,可是也能夠被用來作認證,好比istio實現展現瞭如何用雙向證書解決身份、通信安全,:服務器身份(Server identities)被編碼在證書裏,但服務名稱(service names)經過服務發現或 DNS 被檢索。安全命名信息將服務器身份映射到服務名稱。身份 A 到服務名稱 B 的映射表示「受權 A 運行服務 B「。在雙向 TLS 握手期間,客戶端Envoy作了安全命名檢查,以驗證服務器證書中顯示的服務賬戶是否被受權運行目標服務。

gRPC安全機制

谷歌提供了可擴展的安全認證機制,以知足不一樣業務場景需求,它提供的受權機制主要有四類:

  • 通道憑證:默認提供了基於 HTTP/2 的 TLS,對客戶端和服務端交換的全部數據進行加密傳輸;
  • 調用憑證:被附加在每次 RPC 調用上,經過 Credentials 將認證信息附加到消息頭中,由服務端作受權認證;
  • 組合憑證:將一個頻道憑證和一個調用憑證關聯起來建立一個新的頻道憑證,在這個頻道上的每次調用會發送組合的調用憑證來做爲受權數據,最典型的場景就是使用 HTTP S 來傳輸 Access Token;
  • Google 的 OAuth 2.0:gRPC 內置的谷歌的 OAuth 2.0 認證機制,經過 gRPC 訪問 Google API 時,使用 Service Accounts 密鑰做爲憑證獲取受權令牌。

SSL/TLS認證

用go語言顯示下服務端和客戶端的調用過程:

服務端使用了證書文件

func main() {
    lis, err := net.Listen("tcp", port)
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    // create the TLS credentials from files
    creds, err := credentials.NewServerTLSFromFile("../cert/server.crt""../cert/server.key")
    if err != nil {
        log.Fatalf("could not load TLS keys: %s", err)
    }
    // create a gRPC option array with the credentials
    opts := []grpc.ServerOption{grpc.Creds(creds)}
    // create a gRPC server object with server options(opts)
    s := grpc.NewServer(opts...)
    pb.RegisterSimpleMathServer(s, &rpcimpl.SimpleMathServer{})
    reflection.Register(s)
    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

客戶端使用

func GreatCommonDivisor(first, second string) {
    // create the client TLS credentials
    creds, err := credentials.NewClientTLSFromFile("../cert/server.crt""")
    // initiate a connection with the server using creds
    conn, err := grpc.Dial(address, grpc.WithTransportCredentials(creds))
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
    c := pb.NewSimpleMathClient(conn)
    a, _ := strconv.ParseInt(first, 1032)
    b, _ := strconv.ParseInt(second, 1032)
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
    r, err := c.GreatCommonDivisor(ctx, &pb.GCDRequest{First: int32(a), Second: int32(b)})
    if err != nil {
        log.Fatalf("cound not compute: %v", err)
    }
    log.Printf("The Greatest Common Divisor of %d and %d is %d", a, b, r.Result)
}

GoogleOAuth2.0

gRPC 默認提供了多種 OAuth 2.0 認證機制,假如 gRPC 應用運行在 GCE 裏,能夠經過服務帳號的密鑰生成 Token 用於 RPC 調用的鑑權,密鑰能夠從環境變量 GOOGLE_APPLICATION_CREDENTIALS 對應的文件里加載。若是使用 GCE,能夠在虛擬機設置的時候爲其配置一個默認的服務帳號,運行時能夠與認證系統交互併爲 Channel 生成 RPC 調用時的 access Token。

自定義安全認證策略

參考 Google 內置的 Credentials 實現類,實現自定義的 Credentials,能夠擴展 gRPC 的鑑權策略。


本文分享自微信公衆號 - 安全樂觀主義(gh_d6239d0bb816)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索