序言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 調用安全主要涉及以下三點:
-
我的 / 企業敏感數據加密:例如針對我的的帳號、密碼、手機號等敏感信息進行加密傳輸,打印接口日誌時須要作數據模糊化處理等,不能明文打印;
-
對調用方的身份認證:調用來源是否合法,是否有訪問某個資源的權限,防止越權訪問;
-
數據防篡改和完整性:經過對請求參數、消息頭和消息體作簽名,防止請求消息在傳輸過程當中被非法篡改。
安全方案
常見的安全攻防重視rpc協議的反序列漏洞,可是若是業務方問道若是作以上的安全需求,SDL同窗就傻眼了,正確的作法是區分加密傳輸、認證、鑑權、數據完整性和一致性四個方向:
敏感數據加密傳輸
基於SSL/TLS的通道加密
當存在跨網絡邊界的 RPC 調用時,每每須要經過 TLS/SSL 對傳輸通道進行加密,以防止請求和響應消息中的敏感數據泄漏。跨網絡邊界調用場景主要有三種:
-
後端微服務直接開放給端側,例如手機 App、TV、多屏等,沒有統一的 API 網關/SLB 作安全接入和認證; -
後端微服務直接開放給 DMZ 部署的管理或者運維類 Portal; -
後端微服務直接開放給第三方合做夥伴 / 渠道。
除了跨網絡以外,對於一些安全等級要求比較高的業務場景,即使是內網通訊,只要跨主機 /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 調用,除了數據的機密性和有效性以外,還有數據的完整性和一致性須要保證,即如何保證接收方收到的數據與發送方發出的數據是徹底相同的。
利用消息摘要能夠保障數據的完整性和一致性,它的特色以下:
-
單向 Hash 算法,從明文到密文的不可逆過程,即只能加密而不能解密; -
不管消息大小,通過消息摘要算法加密以後獲得的密文長度都是固定的; -
輸入相同,則輸出必定相同。
目前經常使用的消息摘要算法是 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中心、服務端三方實現。單向認證的過程,客戶端從服務器端下載服務器端公鑰證書進行驗證,而後創建安全通訊通道。單向認證流程中,服務器端保存着公鑰證書和私鑰兩個文件,整個握手過程以下:
-
客戶端發起創建HTTPS鏈接請求,將SSL協議版本的信息發送給服務器端; -
服務器端將本機的公鑰證書(server.crt)發送給客戶端; -
客戶端讀取公鑰證書(server.crt),取出了服務端公鑰; -
客戶端生成一個隨機數(密鑰R),用剛纔獲得的服務器公鑰去加密這個隨機數造成密文,發送給服務端; -
服務端用本身的私鑰(server.key)去解密這個密文,獲得了密鑰R -
服務端和客戶端在後續通信過程當中就使用這個密鑰R進行通訊了。
雙向證書
雙向通訊流程,客戶端除了須要從服務器端下載服務器的公鑰證書進行驗證外,還須要把客戶端的公鑰證書上傳到服務器端給服務器端進行驗證,等雙方都認證經過了,纔開始創建安全通訊通道進行數據傳輸。
-
客戶端發起創建HTTPS鏈接請求,將SSL協議版本的信息發送給服務端; -
服務器端將本機的公鑰證書(server.crt)發送給客戶端; -
客戶端讀取公鑰證書(server.crt),取出了服務端公鑰; -
客戶端將客戶端公鑰證書(client.crt)發送給服務器端; -
服務器端解密客戶端公鑰證書,拿到客戶端公鑰; -
客戶端發送本身支持的加密方案給服務器端; -
服務器端根據本身和客戶端的能力,選擇一個雙方都能接受的加密方案,使用客戶端的公鑰加密後發送給客戶端; -
客戶端使用本身的私鑰解密加密方案,生成一個隨機數R,使用服務器公鑰加密後傳給服務器端; -
服務端用本身的私鑰去解密這個密文,獲得了密鑰R -
服務端和客戶端在後續通信過程當中就使用這個密鑰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, 10, 32)
b, _ := strconv.ParseInt(second, 10, 32)
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源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。