第一篇入門說過 gRPC 底層是基于 HTTP/2 協議的,HTTP 本身不帶任何加密傳輸功能,基于 SSL 的 HTTPS 協議才是加密傳輸。gRPC 使用了 HTTP/2 協議但是并未使用 HTTPS,即少了加密傳輸的部分。
對于加密傳輸的部分 gRPC 將它抽出來作為一個組件,可以由用戶自由選擇。gRPC 內默認提供了兩種 內置的認證方式:
同時也提供了可擴展的用戶自定義認證方式。
gRPC 中的連接類型一共有以下 3 種:
我們之前的實例中都是使用 insecure connection:
Copyconn,?err?:=?grpc.Dial(":8972",?grpc.WithInsecure())
這種方式相當于裸奔的數據在網絡上行走,生產環境下這樣使用肯定是不行的。下面我們來說一下基于 TLS 認證方式加密操作。
服務端 TLS 具體包含以下幾個步驟:
CA 證書制作:
Copy#?生成.key??私鑰文件$?openssl?genrsa?-out?ca.key?2048#?生成.csr?證書簽名請求文件$?openssl?req?-new?-key?ca.key?-out?ca.csr??-subj?"/C=GB/L=China/O=rickiyang/CN=www.rickiyang.com"#?自簽名生成.crt?證書文件$?openssl?req?-new?-x509?-days?3650?-key?ca.key?-out?ca.crt??-subj?"/C=GB/L=China/O=rickiyang/CN=www.rickiyang.com"
服務端證書
和生成 CA證書類似,不過最后一步由 CA 證書進行簽名,而不是自簽名。
然后openssl 配置文件可能位置不同,需要自己修改一下。
首先找到你的 openssl 位置:
Copy$?find?/?-name?"openssl.cnf"
然后生成簽名證書:
Copy#?生成.key??私鑰文件$?openssl?genrsa?-out?server.key?2048#?生成.csr?證書簽名請求文件$?openssl?req?-new?-key?server.key?-out?server.csr?\ -subj?"/C=GB/L=China/O=rickiyang/CN=www.rickiyang.com"?\ -reqexts?SAN?\ -config?<(cat?/usr/local/etc/openssl@1.1/openssl.cnf?<(printf?"\n[SAN]\nsubjectAltName=DNS:*.rickiyang.com"))#?簽名生成.crt?證書文件$?openssl?x509?-req?-days?3650?\ ???-in?server.csr?-out?server.crt?\ ???-CA?ca.crt?-CAkey?ca.key?-CAcreateserial?\ ???-extensions?SAN?\ ???-extfile?<(cat?/usr/local/etc/openssl@1.1/openssl.cnf?<(printf?"\n[SAN]\nsubjectAltName=DNS:*.rickiyang.com"))
至此會生成如下文件:
Copy-rw-r--r--???1?rickiyang??staff??1119??6?30?10:32?ca.crt -rw-r--r--???1?rickiyang??staff???964??6?30?10:32?ca.csr -rw-r--r--???1?rickiyang??staff??1679??6?30?10:31?ca.key -rw-r--r--???1?rickiyang??staff????17??6?30?10:34?ca.srl -rw-r--r--???1?rickiyang??staff??1164??6?30?10:34?server.crt -rw-r--r--???1?rickiyang??staff??1017??6?30?10:33?server.csr -rw-r--r--???1?rickiyang??staff??1679??6?30?10:32?server.key
下面我們用到的會有這三個:
Copyca.crt server.key server.crt
下面來看一下如何將加密校驗邏輯加入到代碼中。相關代碼在 ?Github 上,自行下載查看。
服務端的修改點如下:
Copyfunc?TestGrpcServer(t?*testing.T)?{ //?監聽本地的8972端口 lis,?err?:=?net.Listen("tcp",?":8972") if?err?!=?nil?{ fmt.Printf("failed?to?listen:?%v",?err) return } //?TLS認證 creds,?err?:=?credentials.NewServerTLSFromFile("/Users/rickiyang/server.crt",?"/Users/rickiyang/server.key") if?err?!=?nil?{ grpclog.Fatalf("Failed?to?generate?credentials?%v",?err) } //開啟TLS認證,?注冊攔截器 s?:=?grpc.NewServer(grpc.Creds(creds),?grpc.UnaryInterceptor(LoggingInterceptor))?//?創建gRPC服務器 pb.RegisterGreeterServer(s,?&server{})?????????????????????????//?在gRPC服務端注冊服務 reflection.Register(s)?//在給定的gRPC服務器上注冊服務器反射服務 //?Serve方法在lis上接受傳入連接,為每個連接創建一個ServerTransport和server的goroutine。 //?該goroutine讀取gRPC請求,然后調用已注冊的處理程序來響應它們。 err?=?s.Serve(lis) if?err?!=?nil?{ fmt.Printf("failed?to?serve:?%v",?err) return } }
同樣服務端使用 TLS 連接,客戶端也需要使用對應的連接方式才能進行傳輸??蛻舳舜a主要修改點:
對應代碼邏輯如下:
Copyfunc?TestGrpcClient(t?*testing.T)?{ //?TLS連接 creds,?err?:=?credentials.NewClientTLSFromFile("/Users/rickiyang2/ca.crt",?"www.rickiyang.com") if?err?!=?nil?{ grpclog.Fatalf("Failed?to?create?TLS?credentials?%v",?err) } //?連接服務器 conn,?err?:=?grpc.Dial(":8972",?grpc.WithTransportCredentials(creds)) if?err?!=?nil?{ fmt.Printf("faild?to?connect:?%v",?err) } defer?conn.Close() c?:=?pb.NewGreeterClient(conn) //?調用服務端的SayHello r,?err?:=?c.SayHello(context.Background(),?&pb.HelloRequest{Name:?"CN"}) if?err?!=?nil?{ fmt.Printf("could?not?greet:?%v",?err) } fmt.Printf("Greeting:?%s?!\n",?r.Message) }
server-side TLS 中雖然服務端使用了證書,但是客戶端卻沒有使用證書,本章節會給客戶端也生成一個證書,并完成 mutual TLS。
生成客戶端證書和生成服務端證書沒有什么不同:
Copy#?生成.key??私鑰文件openssl?genrsa?-out?client.key?2048#?生成.csr?證書簽名請求文件openssl?req?-new?-key?client.key?-out?client.csr?\ -subj?"/C=GB/L=China/O=lixd/CN=www.rickiyang.com"?\ -reqexts?SAN?\ -config?<(cat?/usr/local/etc/openssl@1.1/openssl.cnf?<(printf?"\n[SAN]\nsubjectAltName=DNS:*.rickiyang.com"))#?簽名生成.crt?證書文件openssl?x509?-req?-days?3650?\ -in?client.csr?-out?client.crt?\ -CA?ca.crt?-CAkey?ca.key?-CAcreateserial?\ -extensions?SAN?\ -extfile?<(cat?/usr/local/etc/openssl@1.1/openssl.cnf?<(printf?"\n[SAN]\nsubjectAltName=DNS:*.rickiyang.com"))
執行上面的命令之后我們會得到這兩個重要的文件:
Copyclient.crt client.key
下面就是在代碼中去引用這些文件,mutual TLS 配置客戶端和服務端都需要修改,相關代碼點擊查看。
具體改動如下:
服務端:
Copyfunc?TestGrpcServer(t?*testing.T)?{ //?從證書相關文件中讀取和解析信息,得到證書公鑰、密鑰對 certificate,?err?:=?tls.LoadX509KeyPair(data.Path("/Users/rickiyang2/server.crt"),?data.Path("/Users/rickiyang2/server.key")) if?err?!=?nil?{ fmt.Errorf("err,?%v",?err) } //?創建CertPool,后續就用池里的證書來校驗客戶端證書有效性 //?所以如果有多個客戶端?可以給每個客戶端使用不同的?CA?證書,來實現分別校驗的目的 certPool?:=?x509.NewCertPool() ca,?err?:=?ioutil.ReadFile(data.Path("/Users/rickiyang2/ca.crt")) if?err?!=?nil?{ fmt.Errorf("err,?%v",?err) } if?ok?:=?certPool.AppendCertsFromPEM(ca);?!ok?{ fmt.Errorf("failed?to?append?certs") } //?構建基于?TLS?的?TransportCredentials creds?:=?credentials.NewTLS(&tls.Config{ //?設置證書鏈,允許包含一個或多個 Certificates:?[]tls.Certificate{certificate}, //?要求必須校驗客戶端的證書?可以根據實際情況選用其他參數 ClientAuth:?tls.RequireAndVerifyClientCert,?//?NOTE:?this?is?optional! //?設置根證書的集合,校驗方式使用?ClientAuth?中設定的模式 ClientCAs:?certPool, }) //開啟TLS認證,?注冊攔截器 s?:=?grpc.NewServer(grpc.Creds(creds),?grpc.UnaryInterceptor(LoggingInterceptor))?//?創建gRPC服務器 pb.RegisterGreeterServer(s,?&server{})?????????????????????????//?在gRPC服務端注冊服務 //?監聽本地的8972端口 lis,?err?:=?net.Listen("tcp",?":8972") if?err?!=?nil?{ fmt.Printf("failed?to?listen:?%v",?err) return } reflection.Register(s)?//在給定的gRPC服務器上注冊服務器反射服務 //?Serve方法在lis上接受傳入連接,為每個連接創建一個ServerTransport和server的goroutine。 //?該goroutine讀取gRPC請求,然后調用已注冊的處理程序來響應它們。 err?=?s.Serve(lis) if?err?!=?nil?{ fmt.Printf("failed?to?serve:?%v",?err) return } }
客戶端:
Copyfunc?TestGrpcClient(t?*testing.T)?{ //?加載客戶端證書 certificate,?err?:=?tls.LoadX509KeyPair(data.Path("/Users/rickiyang2/client.crt"),?data.Path("/Users/rickiyang2/client.key")) if?err?!=?nil?{ fmt.Errorf("err,?%v",?err) } //?構建CertPool以校驗服務端證書有效性 certPool?:=?x509.NewCertPool() ca,?err?:=?ioutil.ReadFile(data.Path("/Users/rickiyang2/ca.crt")) if?err?!=?nil?{ fmt.Errorf("err,?%v",?err) } if?ok?:=?certPool.AppendCertsFromPEM(ca);?!ok?{ fmt.Errorf("failed?to?append?ca?certs") } creds?:=?credentials.NewTLS(&tls.Config{ Certificates:?[]tls.Certificate{certificate}, ServerName:???"www.rickiyang.com",?//?NOTE:?this?is?required! RootCAs:??????certPool, }) //?連接服務器 conn,?err?:=?grpc.Dial(":8972",?grpc.WithTransportCredentials(creds)) if?err?!=?nil?{ fmt.Printf("faild?to?connect:?%v",?err) } defer?conn.Close() c?:=?pb.NewGreeterClient(conn) //?調用服務端的SayHello r,?err?:=?c.SayHello(context.Background(),?&pb.HelloRequest{Name:?"CN"}) if?err?!=?nil?{ fmt.Printf("could?not?greet:?%v",?err) } fmt.Printf("Greeting:?%s?!\n",?r.Message) }
本篇只介紹 SSL/TLS 認證相關的方式,生成證書相關操作本文是在 Mac 上操作,不同系統可能會有不一樣的環境問題, 如果出現問題按照相關提示排除錯誤。下一篇我們繼續介紹 Token 認證和自定義認證方式。
|