在上一篇文章Golang 学习之 grpc 的使用中介绍了 gRPC 的使用,并使用官方 example 来举例解读
在这里先看一下对 gRPC 的传输抓包
可以看到 gRPC Client/Server 都是明文加密的;在真实场景中,就会有可能被第三方恶意篡改或伪造 “非法” 数据,因此我们需要对 gRPC 进行加密传输处理
Golang 提供了两种加密示例 ATLS 和 TLS,笔者使用的是双向 TLS (mTLS),客户端和服务端相互验证
TLS 传输抓包
ATLS
ATLS 目前需要 GCP 的特殊早期访问权限
ATLS 是谷歌的应用层传输安全,支持相互认证和传输加密;但目前 ATLS 之支持在 Google Cloud Platform 上运行;与 TLS 不同,ATLS 的证书/密钥管理对用户透明,更容易设置
TLS
TLS 是一种常用的加密协议,用于提供端到端的通信安全性。
在正常的 TLS 中,服务器只关心提供服务器证书供客户端验证;在双向 TLS 中,服务器还加载受信任的 CA 文件列表,用于验证客户端提供的证书
在普通的 TLS 中,客户端只关心使用一个或多个受信任的 CA 文件对服务器进行身份验证;在双向 TLS 中,客户端还将客户端证书提供给服务器进行身份验证
代码剖析
这里分析一下 mTLS 模式的 gRPC 代码,有关证书的生成可以参考 带入gRPC:基于 CA 的 TLS 证书认证 或者 官方的脚本生成 create.sh
Server
package main
import (
"context"
"crypto/tls"
"crypto/x509"
"flag"
"fmt"
"io/ioutil"
"log"
"net"
pb "go-grpc-CA/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
var port = flag.Int("port", 50051, "the port to serve on")
type ecServer struct {
pb.UnimplementedEchoServer
}
func (s *ecServer) UnaryEcho(ctx context.Context, req *pb.EchoRequest) (*pb.EchoResponse, error) {
return &pb.EchoResponse{Message: req.Message}, nil
}
func main() {
flag.Parse()
log.Printf("server starting on port %d...\n", *port)
cert, err := tls.LoadX509KeyPair("../conf/server_cert.pem", "../conf/server_key.pem")
if err != nil {
log.Fatalf("failed to load key pair: %s", err)
}
ca := x509.NewCertPool()
caFilePath := "../conf/client_ca_cert.pem"
caBytes, err := ioutil.ReadFile(caFilePath)
if err != nil {
log.Fatalf("failed to read ca cert %q: %v", caFilePath, err)
}
if ok := ca.AppendCertsFromPEM(caBytes); !ok {
log.Fatalf("failed to parse %q", caFilePath)
}
tlsConfig := &tls.Config{
ClientAuth: tls.RequireAndVerifyClientCert,
Certificates: []tls.Certificate{cert},
ClientCAs: ca,
}
s := grpc.NewServer(grpc.Creds(credentials.NewTLS(tlsConfig)))
pb.RegisterEchoServer(s, &ecServer{})
lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", *port))
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
Client
package main
import (
"context"
"crypto/tls"
"crypto/x509"
"flag"
"fmt"
"io/ioutil"
"log"
"time"
ecpb "go-grpc-CA/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
)
var addr = flag.String("addr", "localhost:50051", "the address to connect to")
func callUnaryEcho(client ecpb.EchoClient, message string) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
resp, err := client.UnaryEcho(ctx, &ecpb.EchoRequest{Message: message})
if err != nil {
log.Fatalf("client.UnaryEcho(_) = _, %v: ", err)
}
fmt.Println("UnaryEcho: ", resp.Message)
}
func main() {
flag.Parse()
cert, err := tls.LoadX509KeyPair("../conf/client_cert.pem", "../conf/client_key.pem")
if err != nil {
log.Fatalf("failed to load client cert: %v", err)
}
ca := x509.NewCertPool()
caFilePath := "../conf/ca_cert.pem"
caBytes, err := ioutil.ReadFile(caFilePath)
if err != nil {
log.Fatalf("failed to read ca cert %q: %v", caFilePath, err)
}
if ok := ca.AppendCertsFromPEM(caBytes); !ok {
log.Fatalf("failed to parse %q", caFilePath)
}
tlsConfig := &tls.Config{
ServerName: "x.test.example.com",
Certificates: []tls.Certificate{cert},
RootCAs: ca,
}
conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)))
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
callUnaryEcho(ecpb.NewEchoClient(conn), "hello world")
}
代码地址
https://github.com/MoGD2018/go-study/tree/main/go-grpc-CA