原文地址:帶入gRPC:TLS 證書認證
項目地址:https://github.com/EDDYCJY/go...html
在前面的章節裏,咱們介紹了 gRPC 的四種 API 使用方式。是否是很簡單呢 😀git
此時存在一個安全問題,先前的例子中 gRPC Client/Server 都是明文傳輸的,會不會有被竊聽的風險呢?github
從結論上來說,是有的。在明文通信的狀況下,你的請求就是裸奔的,有可能被第三方惡意篡改或者僞造爲「非法」的數據golang
嗯,明文傳輸無誤。這是有問題的,接下將改造咱們的 gRPC,以便於解決這個問題 😤segmentfault
openssl ecparam -genkey -name secp384r1 -out server.key
openssl req -new -x509 -sha256 -key server.key -out server.pem -days 3650
Country Name (2 letter code) []: State or Province Name (full name) []: Locality Name (eg, city) []: Organization Name (eg, company) []: Organizational Unit Name (eg, section) []: Common Name (eg, fully qualified host name) []:go-grpc-example Email Address []:
生成證書結束後,將證書相關文件放到 conf/ 下,目錄結構:安全
$ tree go-grpc-example go-grpc-example ├── client ├── conf │ ├── server.key │ └── server.pem ├── proto └── server ├── simple_server └── stream_server
因爲本文偏向 gRPC,詳解可參見 《製做證書》。後續番外可能會展開細節描述 👌服務器
在 simple_server 中,爲何「啥事都沒幹」就能在不須要證書的狀況下運行呢?網絡
grpc.NewServer()
在服務端顯然沒有傳入任何 DialOptionsapp
conn, err := grpc.Dial(":"+PORT, grpc.WithInsecure())
在客戶端留意到 grpc.WithInsecure()
方法tcp
func WithInsecure() DialOption { return newFuncDialOption(func(o *dialOptions) { o.insecure = true }) }
在方法內能夠看到 WithInsecure
返回一個 DialOption
,而且它最終會經過讀取設置的值來禁用安全傳輸
那麼它「最終」又是在哪裏處理的呢,咱們把視線移到 grpc.Dial()
方法內
func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *ClientConn, err error) { ... for _, opt := range opts { opt.apply(&cc.dopts) } ... if !cc.dopts.insecure { if cc.dopts.copts.TransportCredentials == nil { return nil, errNoTransportSecurity } } else { if cc.dopts.copts.TransportCredentials != nil { return nil, errCredentialsConflict } for _, cd := range cc.dopts.copts.PerRPCCredentials { if cd.RequireTransportSecurity() { return nil, errTransportCredentialsMissing } } } ... creds := cc.dopts.copts.TransportCredentials if creds != nil && creds.Info().ServerName != "" { cc.authority = creds.Info().ServerName } else if cc.dopts.insecure && cc.dopts.authority != "" { cc.authority = cc.dopts.authority } else { // Use endpoint from "scheme://authority/endpoint" as the default // authority for ClientConn. cc.authority = cc.parsedTarget.Endpoint } ... }
接下來咱們將正式開始編碼,在 gRPC Client/Server 上實現 TLS 證書認證的支持 🤔
package main import ( "context" "log" "net" "google.golang.org/grpc" "google.golang.org/grpc/credentials" pb "github.com/EDDYCJY/go-grpc-example/proto" ) ... const PORT = "9001" func main() { c, err := credentials.NewServerTLSFromFile("../../conf/server.pem", "../../conf/server.key") if err != nil { log.Fatalf("credentials.NewServerTLSFromFile err: %v", err) } server := grpc.NewServer(grpc.Creds(c)) pb.RegisterSearchServiceServer(server, &SearchService{}) lis, err := net.Listen("tcp", ":"+PORT) if err != nil { log.Fatalf("net.Listen err: %v", err) } server.Serve(lis) }
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.NewServer(opt ...ServerOption)
爲 gRPC Server 設置鏈接選項func Creds(c credentials.TransportCredentials) ServerOption { return func(o *options) { o.creds = c } }
通過以上兩個簡單步驟,gRPC Server 就創建起需證書認證的服務啦 🤔
package main import ( "context" "log" "google.golang.org/grpc" "google.golang.org/grpc/credentials" pb "github.com/EDDYCJY/go-grpc-example/proto" ) const PORT = "9001" func main() { c, err := credentials.NewClientTLSFromFile("../../conf/server.pem", "go-grpc-example") if err != nil { log.Fatalf("credentials.NewClientTLSFromFile err: %v", err) } conn, err := grpc.Dial(":"+PORT, grpc.WithTransportCredentials(c)) if err != nil { log.Fatalf("grpc.Dial err: %v", err) } defer conn.Close() client := pb.NewSearchServiceClient(conn) resp, err := client.Search(context.Background(), &pb.SearchRequest{ Request: "gRPC", }) if err != nil { log.Fatalf("client.Search err: %v", err) } log.Printf("resp: %s", resp.GetResponse()) }
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.Dial(target string, opts ...DialOption)
設置鏈接選項func WithTransportCredentials(creds credentials.TransportCredentials) DialOption { return newFuncDialOption(func(o *dialOptions) { o.copts.TransportCredentials = creds }) }
從新啓動 server.go 和執行 client.go,獲得響應結果
$ go run client.go 2018/09/30 20:00:21 resp: gRPC Server
成功 👍
在本章節咱們實現了 gRPC TLS Client/Servert,你覺得大功告成了嗎?我不 😤
你仔細再看看,Client 是基於 Server 端的證書和服務名稱來創建請求的。這樣的話,你就須要將 Server 的證書經過各類手段給到 Client 端,不然是沒法完成這項任務的
問題也就來了,你沒法保證你的「各類手段」是安全的,畢竟如今的網絡環境是很危險的,萬一被...
咱們將在下一章節解決這個問題,保證其可靠性 🙂