前言
假设gRPC服务端的主机名为qw.er.com
,需要为gRPC服务端和客户端之间的通信配置tls双向认证加密。
生成证书
- 生成ca根证书。生成过程会要求填写密码、CN、ON、OU等信息,记住密码。
openssl req -x509 -newkey rsa:4096 -keyout ca.key -out ca.crt -subj "/CN=qw.er.com" -days 365
- 新建并编辑文件
openssl.cnf
文件。req_distinguished_name中内容按需填写,DNS.1要替换成实际域名。
[req] req_extensions = v3_req distinguished_name = req_distinguished_name prompt = no [req_distinguished_name] countryName = CN stateOrProvinceName = Anhui localityName = Hefei organizationName = zhangsan commonName = qw.er.com [v3_req] subjectAltName = @alt_names [alt_names] DNS.1 = qw.er.com
- 生成服务端证书
openssl req -newkey rsa:2048 -nodes -keyout server.key -out server.csr -subj "/CN=qw.er.com" -config openssl.cnf # 提示输入ca私钥的密码 openssl x509 -req -in server.csr -out server.crt -CA ca.crt -CAkey ca.key -CAcreateserial -days 365 -extensions v3_req -extfile openssl.cnf
- 生成客户端证书
openssl req -newkey rsa:2048 -nodes -keyout client.key -out client.csr -subj "/CN=qw.er.com" -config openssl.cnf # 提示输入ca私钥的密码 openssl x509 -req -in client.csr -out client.crt -CA ca.crt -CAkey ca.key -CAcreateserial -days 365 -extensions v3_req -extfile openssl.cnf
proto示例
用的还是入门级的helloworld
syntax = "proto3"; // protocol buffers版本 option go_package = "./;proto"; // 生成的Go代码将被放在当前目录,并使用proto作为包名称 // 定义grpc服务的接口。服务就是一组可被远程调用的方法 service Greeter { // 定义远程调用方法 rpc SayHello (HelloRequest) returns (HelloReply); } // 定义消息格式和消息类型 message HelloRequest { string name = 1; // 1 是二进制格式中的字段编号, 应该唯一 } message HelloReply { string message = 1; }
生成go代码:
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative hello.proto
服务端代码示例
如果需要客户端和服务端直接通信,可以参考以下示例代码。
package main import ( pb "grpcs/proto" "context" "crypto/tls" "crypto/x509" "flag" "fmt" "log" "net" "os" "google.golang.org/grpc" "google.golang.org/grpc/credentials" ) var ( port = flag.Int("port", 8010, "the server port") crtFile = flag.String("crt", "server.crt", "the server crt file") keyFile = flag.String("key", "server.key", "the server key file") caFile = flag.String("ca", "ca.crt", "the server ca file") ) type server struct{ pb.UnimplementedGreeterServer } func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { log.Printf("Received: %v", in.GetName()) return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil } func main() { flag.Parse() // 通过服务端的证书和密钥直接创建X.509密钥对 certificate, err := tls.LoadX509KeyPair(*crtFile, *keyFile) if err != nil { log.Fatalf("Failed to load key pair: %v", err) } // 通过CA创建证书池 certPool := x509.NewCertPool() ca, err := os.ReadFile(*caFile) if err != nil { log.Fatalf("Failed to read ca: %v", err) } // 将来自CA的客户端证书附加到证书池 if ok := certPool.AppendCertsFromPEM(ca); !ok { log.Fatalf("Failed to append ca certificate") } opts := []grpc.ServerOption{ grpc.Creds( // 为所有传入的连接启用TLS credentials.NewTLS(&tls.Config{ ClientAuth: tls.RequireAndVerifyClientCert, Certificates: []tls.Certificate{certificate}, ClientCAs: certPool, }, )), } listen, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", *port)) if err != nil { log.Fatalf("failed to listen %d port", *port) } // 通过传入的TLS服务器凭证创建新的gRPC服务实例 s := grpc.NewServer(opts...) pb.RegisterGreeterServer(s, &server{}) log.Printf("server listening at %v", listen.Addr()) if err := s.Serve(listen); err != nil { log.Fatalf("Failed to serve: %v", err) } }
运行:
go build -o server.bin ./server.bin -ca ca.crt -crt server.crt -key server.key -port 8010
客户端代码示例
package main import ( pb "grpcc/proto" "context" "crypto/tls" "crypto/x509" "flag" "log" "os" "time" "google.golang.org/grpc" "google.golang.org/grpc/credentials" ) var ( addr = flag.String("addr", "qw.er.com:8010", "server address") hostname = flag.String("host", "qw.er.com", "server hostname") crtFile = flag.String("crt", "client.crt", "client crt file") keyFile = flag.String("key", "client.key", "client key file") caFile = flag.String("ca", "ca.crt", "ca file") name = flag.String("n", "zhangsan", "name") ) func main() { flag.Parse() certificate, err := tls.LoadX509KeyPair(*crtFile, *keyFile) if err != nil { log.Fatalf("Failed to load client key pair, %v", err) } certPool := x509.NewCertPool() ca, err := os.ReadFile(*caFile) if err != nil { log.Fatalf("Failed to read %s, error: %v", *caFile, err) } if ok := certPool.AppendCertsFromPEM(ca); !ok { log.Fatalf("Failed to append ca certs") } opts := []grpc.DialOption{ grpc.WithTransportCredentials(credentials.NewTLS( &tls.Config{ ServerName: *hostname, Certificates: []tls.Certificate{certificate}, RootCAs: certPool, })), } // conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(insecure.NewCredentials())) conn, err := grpc.Dial(*addr, opts...) if err != nil { log.Fatalf("Connect to %s failed", *addr) } defer conn.Close() client := pb.NewGreeterClient(conn) // 创建带有超时时间的上下文, cancel可以取消上下文 ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) defer cancel() // 业务代码处理部分 ... r, err := client.SayHello(ctx, &pb.HelloRequest{Name: *name}) if err != nil { log.Printf("Failed to greet, error: %v", err) } else { log.Printf("Greeting: %v",r.GetMessage()) } }
运行:
go build -o client.bin ./client.bin -addr qw.er.com:8010 -host qw.er.com -ca ca.crt -crt client.crt -key client.key -name 'lisi'
nginx代理
某些场景下服务端和客户端无法直接通信,需要在中间加个nginx反向代理服务端。目前个人方案是客户端与nginx之间为https双向加密通信,nginx与服务端之间为http普通通信。
客户端代码无需改动,服务端就是去掉tls相关配置,示例:
package main import ( "context" "flag" "fmt" "log" "net" pb "grpcs/proto" "google.golang.org/grpc" ) var ( port = flag.Int("port", 8010, "The server port") ) // server is used to implement hello.GreeterServer. type server struct { pb.UnimplementedGreeterServer } // SayHello 实现 proto 中的 service Greeter func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { log.Printf("Received: %v", in.GetName()) return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil } func main() { flag.Parse() lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port)) if err != nil { log.Fatalf("failed to listen: %v", err) } s := grpc.NewServer() pb.RegisterGreeterServer(s, &server{}) log.Printf("server listening at %v", lis.Addr()) if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } }
tls证书配到nginx:
server { listen 80 ssl http2; server_name qw.er.com; # 证书文件路径 ssl_certificate /home/admin/apps/openresty/nginx/certs/qwer/server.crt; ssl_certificate_key /home/admin/apps/openresty/nginx/certs/qwer/server.key; # 验证客户端证书 ssl_verify_client on; ssl_client_certificate /home/admin/apps/openresty/nginx/certs/qwer/ca.crt; # 反向代理服务端 location / { grpc_pass grpc://192.168.1.111:8010; } }