第一篇入门说过 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 连接,客户端也需要使用对应的连接方式才能进行传输。客户端代码主要修改点:
对应代码逻辑如下:
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 认证和自定义认证方式。
|