gRPC實踐–加密通信
在gRPC的demo通信中,有服務端和客戶端,他們之間都是明文通信的,這樣會帶來安全隱患,
一般的,gRPC有幾種方法對server 和 client 之間的通信進行加密,即,身份驗證機制:

這里,介紹最常用的證書認證機制,通過使用證書認證,在TLS協議下,實作加密通信,
我們知道,gRPC建立在HTTP/2協議之上,所以對 TLS 提供了很好的支持,
之前的gRPC的服務都沒有提供證書支持,因此客戶端在鏈接服務器中通過
grpc.WithInsecure()選項跳過了對服務器證書的驗證,沒有啟用證書的gRPC服務在和客戶端進行的是明文通訊,資訊面臨被任何第三方監聽的風險,為了保障gRPC通信不被第三方監聽篡改或偽造,我們可以對服務器啟動TLS加密特性,? ---------《Go語言高級編程(Advanced Go Programming)》
證書
在go 版本在1.15以下時,參考博客:帶入gRPC:TLS 證書認證,我們可以輕松完成證書的加密通信,但是,在go v1.15以后,如果使用一般的證書通信,則會報錯:
2021/04/24 15:37:11 could not greet: rpc error: code = Unavailable desc = connection error: desc = "transport: authentication handshake failed: x509: certificate relies on legacy Common Name field, use SANs or temporarily enable Common Name matching with GODEBUG=x509ignoreCN=0"
總的來說,就是:
身份驗證失敗了,因為GO 1.15以后X509 不能用了,提示我們有兩個選擇:
1. 需要使用SAN 證書
2. 改變環境變數:GODEBUG=x509ignoreCN=0
這里,我們選擇使用SAN證書,進行通信
SAN and openssl
首先,我們看看什么是SAN證書:
SAN(Subject Alternative Name) 是 SSL 標準 x509 中定義的一個擴展,使用了 SAN 欄位的 SSL 證書,可以擴展此證書支持的域名,使得一個證書可以支持多個不同域名的決議,
要生成SAN證書,需要以下幾步:
1. 修改openssl.cfg
如果是Linux系統的話,應該修改的是openssl.cnf檔案,我這個是windos,所以就是openssl.cfg,
找到你安裝openssl 程式的目錄,我的為E:\OpenSSL-Win64\bin,如果沒有該程式,可以從這里官網下載,
將openssl.cfg檔案拷貝到需要生成證書的那個目錄,進行修改
- 打開copy_extensions 在CA_default節
[ CA_default ]
...
copy_extensions = copy # Extension copying option: use with caution.
...
- 打開req_extensions 在req中修改,
[ req ]
...
req_extensions = v3_req # The extensions to add to a certificate request
...
這段配置表示在生成 CSR 檔案時讀取名叫
v3_req的段落的配置資訊,因此我們再在此組態檔中加入一段名為v3_req的配置:
- 增加subjectAltName 在v3_req節
[ v3_req ]
...
subjectAltName = @alt_names
## 這段配置中最重要的是在最后匯入名為 alt_names 的配置段,
## 因此我們還需要添加一個名為 [ alt_names ] 的配置段,這可以定義多個服務
[alt_names]
DNS.1 = *.org.example.com
DNS.2 = *.example.com
參考:[使用openssl創建包含SAN的證書](使用openssl創建包含SAN的證書 - 簡書 (jianshu.com))
2. 生成根證書
現在,開始生成證書,首先是生成CA證書:
生成CA私鑰(.key)–>生成CA證書請求(.csr)–>自簽名得到根證書(.crt)(CA給自已頒發的證書),
生成根證書私鑰
openssl genrsa -out ca.key 2048
生成CA證書請求
openssl req -new -key ca.key -out ca.csr -subj "/C=cn/OU=myorg/O=mytest/CN=myname"
自簽名得到根證書
openssl x509 -req -days 3650 -in ca.csr -signkey ca.key -out ca.crt
其中:
- genrsa:使用RSA演算法產生私鑰
- -in:要輸入的csr檔案
- -out:輸出檔案的路徑
- -subj:證書相關的用戶資訊(subject的縮寫)
- -key:指定私鑰路徑
- -new:新證書簽發請求
- -req:輸入csr檔案
- -days:證書的有效期(天)
3. 生成SNA的服務端證書
生成服務端私鑰(serve.key)–>生成服務端證書請求(server.csr)–>CA對服務端請求檔案簽名,生成服務端證書(server.pem)
生成服務端證書私鑰
openssl genrsa -out server.key 2048
根據私鑰server.key生成證書請求檔案server.csr
$ openssl req -new -nodes -key server.key -out server.csr -subj "/C=cn/OU=myserver/O=servercomp/CN=servername" -config ./openssl.cfg -extensions v3_req
請求CA對證書請求檔案簽名,生成最終證書檔案
λ openssl x509 -req -days 365 -in server.csr -out server.pem -CA ca.crt -CAkey ca.key -CAcreateserial -extfile ./openssl.cfg -extensions v3_req
Signature ok
subject=C = cn, OU = myserver, O = servercomp, CN = servername
Getting CA Private Key
驗證:
λ openssl x509 -noout -text -in server.pem
Certificate:
Data:
......
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
X509v3 Key Usage:
Digital Signature, Non Repudiation, Key Encipherment
X509v3 Subject Alternative Name:
DNS:*.org.haha.com, DNS:*.haha.com
......
代碼實作
在實作代碼之前,我們注意到client.go 中的一個代碼:
// 連接 gRPC 服務器
conn, err := grpc.Dial(address, grpc.WithInsecure())
其中,grpc.WithInsecure() 函式,正如開頭參考中提到的,這個選項會使得程式跳過對服務器證書的驗證,這樣服務端和客戶端就成立明文通信,
事實上,WithInsecure()最侄訓通過讀取設定的值來禁用安全傳輸,具體說明,可以參考下面這篇博客:帶入gRPC:TLS 證書認證 - SegmentFault 思否
有了證書之后,我們就可以在啟動gRPC服務時傳入證書選項引數:

在server 和 client 上實作 TLS 證書認證的加密通信:
目錄結構
λ tree /F
D:.
│ go.mod
│ go.sum
├─cert
│ ca.crt
│ ca.csr
│ ca.key
│ openssl.cfg
│ server.csr
│ server.key
│ server.pem
├─client
│ client.go
├─proto
│ helloworld.pb.go
│ helloworld.proto
└─server
server.go
Server

其中:
credentials.NewServerTLSFromFile:根據服務端輸入的證書檔案和密鑰構造 TLS 憑證
func NewServerTLSFromFile(certFile, keyFile string) (TransportCredentials, error) {
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return nil, err
}
return NewTLS(&tls.Config{Certificates: []tls.Certificate{cert}}), nil
}
grpc.Creds():回傳一個 ServerOption,用于設定服務器連接的憑據,用于grpc.NewServer(opt ...ServerOption)為 gRPC Server 設定連接選項
func Creds(c credentials.TransportCredentials) ServerOption {
return func(o *options) {
o.creds = c
}
}
經過以上兩個簡單步驟,gRPC Server 就建立起需證書認證的服務啦,
Client

其中:
credentials.NewClientTLSFromFile():根據客戶端輸入的證書檔案和密鑰構造 TLS 憑證,serverNameOverride 為服務名稱
func NewClientTLSFromFile(certFile, serverNameOverride string) (TransportCredentials, error) {
b, err := ioutil.ReadFile(certFile)
if err != nil {
return nil, err
}
cp := x509.NewCertPool()
if !cp.AppendCertsFromPEM(b) {
return nil, fmt.Errorf("credentials: failed to append certificates")
}
return NewTLS(&tls.Config{ServerName: serverNameOverride, RootCAs: cp}), nil
}
grpc.WithTransportCredentials():回傳一個配置連接的 DialOption 選項,用于grpc.Dial(target string, opts ...DialOption)設定連接選項
func WithTransportCredentials(creds credentials.TransportCredentials) DialOption {
return newFuncDialOption(func(o *dialOptions) {
o.copts.TransportCredentials = creds
})
}
驗證

總結
本文講述了gRPC的證書通信的簡單示例,主要為服務端頒發了證書,之后服務端和客戶端都基于一個證書進行TLS的加密通信,
但,在實際生產環境中,以上這種方式,需要提前將服務器的證書告知客戶端,這樣客戶端在鏈接服務器時才能進行對服務器證書認證,在復雜的網路環境中,服務器證書的傳輸本身也是一個非常危險的問題,如果在中間某個環節,服務器證書被監聽或替換那么對服務器的認證也將不再可靠,
為了避免證書的傳遞程序中被篡改,可以通過一個安全可靠的根證書分別對服務器和客戶端的證書進行簽名,這樣客戶端或服務器在收到對方的證書后可以通過根證書進行驗證證書的有效性,
參考
- Go語言高級編程(Advanced Go Programming)
- 帶入gRPC:TLS 證書認證
- gRPC官網EN
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/279862.html
標籤:區塊鏈
