用gRPC实现一个系统(3):安全通信

栏目: 服务器 · 发布时间: 6年前

内容简介:在客户端和服务器通过gRPC相互通信,由于我们使用了protobuf来序列化和反序列化消息,因此消息的数据是二进制形式的。但是我们的通信是明文传输的,这在一些安全需求较高的场景中是不允许的,因此需要使用安全传输。幸好,在gRPC中,我们可以直接使用SSL/TLS,用来验证服务器,并对通信过程进行加密。首先,需要生成证书。

前一篇 文章中,我们使用gRPC从零开始定义了一个服务。在这篇文章中,我们更近一步,在这个服务中添加TLS特性。

1. 制作证书

客户端和服务器通过gRPC相互通信,由于我们使用了protobuf来序列化和反序列化消息,因此消息的数据是二进制形式的。但是我们的通信是明文传输的,这在一些安全需求较高的场景中是不允许的,因此需要使用安全传输。幸好,在gRPC中,我们可以直接使用SSL/TLS,用来验证服务器,并对通信过程进行加密。

首先,需要生成证书。

1.1 生成私钥

在我们的simplemath项目目录中新建目录 cert ,进入这个目录,执行下面的命令来生成私钥:

$ openssl genrsa -out server.key 2048

我们使用 openssl genrsa 命令来生成私钥,并用 -out 选项指定输出。最后一个参数 2048 表示的是生成密钥的位数,如果没有指定,那么默认就是512位。

1.2 根据私钥生成CSR

如果想从一个认证中心( Certificate Authority,CA )获取一个SSL证书,我们需要生成一个证书签名请求( Certificate Signing Reqeusts,CRSs )。一个CSR主要包含钥匙对中的公钥,以及其它一些重要的信息。

我们可以根据前面生成的私钥生成一个CSR:

$ openssl req -new -sha256 -key server.key -out server.csr

执行上面的命令后,需要完成一些信息的填写,主要有:

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) []:
Email Address []:

填写完这些信息后,就会生成一个证书签名请求( server.csr )。

1.3 生成证书

如果想使用一个SSL证书来对通信进行加密,但是不需要使用CA签字的证书,那么我们可以生成一个自签名的证书。

使用前面生成的私钥( server.key )以及证书签名请求( server.csr ),我们可以生成一个自签名的证书:

$ openssl x509 -req -sha256 -in server.csr -signkey server.key -out server.crt -days 3650

选项 -x509 指定 req 来生成一个自签名的证书。 -days 3650 指定了证书的有效期是3650天。 -signkey 指定了私钥,而 -in 指定了证书签名请求。

这样,就能生成一个自签名的证书( server.crt )。

此时,整个项目的目录结构如下:

$GOPATH                        // your $GOPATH folder
 |__src                         // the source folder
    |__simplemath               // the simplemath project folder
       |__cert                  // the certificate folder
       |  |__server.key         // the private key
       |  |__server.csr         // the certificate signing request
       |  |__server.crt         // the self-signed certificate
       |__client                // the client folder stores the client codes
       |  |__rpc                // the rpc folder stores the call function
       |  |  |__simplemath.go   // the logic code for remote call
       |  |__main.go            // client program goes from here
       |  |__client             // the executeable client file
       |__api                   // folder that stores .proto and .pb.go files
       |  |__simplemath.proto   // file defines the messages and services
       |  |__simplemath.pb.go   // file generated by protoc
       |__server                // the server folder stores the server codes
          |__rpcimpl            // the rpcimpl folder stores the logic codes
          |  |__simplemath.go   // the logic code related to simplemath
          |__main.go            // server program goes from here
          |__server             // the executable server file
    ...

证书生成后,就可以在我们的代码中加入TLS了。

2. Server+TLS

gRPC协议本身没什么变动,主要的变动就是在gRPC对象的生成,包括服务器和客户端。如果只改动一边的话是不成功的,需要两端都修改。

2.1 修改服务器端代码

在服务器端,需要修改 main.go ,代码如下:

package main

import (
    "google.golang.org/grpc"
    // import the credentials package
    "google.golang.org/grpc/credentials"
    "google.golang.org/grpc/reflection"
    "log"
    "net"
    pb "simplemath/api"
    "simplemath/server/rpcimpl"
)

const (
    port = ":50051"
)

func main() {
    lis, err := net.Listen("tcp", port)
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    // create the TLS credentials from files
    creds, err := credentials.NewServerTLSFromFile("../cert/server.crt", "../cert/server.key")
    if err != nil {
        log.Fatalf("could not load TLS keys: %s", err)
    }
    // create a gRPC option array with the credentials
    opts := []grpc.ServerOption{grpc.Creds(creds)}
    // create a gRPC server object with server options(opts)
    s := grpc.NewServer(opts...)
    pb.RegisterSimpleMathServer(s, &rpcimpl.SimpleMathServer{})
    reflection.Register(s)
    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

2.2 我们变了什么?

这个代码和之前的有什么变化呢?

首先,为了使用TLS,我们需要导入 google.golang.org/credentials 包。

然后,使用我们之前生成的私钥( server.key )以及自签名证书( server.crt )构建一个credentials对象 creds

由于这个TLS是在我们原来的服务器上增加的特性,所以需要创建一个 grpc.ServerOption ,参数就是我们刚才创建的 creds

最后,我们将创建的 grpc.ServerOption 传入生成grpc服务器的构造函数 grpc.NewServer() 中。

注意 grpc.NewServer() 函数是一个可变数量参数的函数,我们可以传入零个或多个参数( grpc.ServerOption ),以后我们会用到更多。

2.3 注意Common Name

有一个需要注意的问题是,我们在生成服务器时绑定的地主必须和在生成CSR时候填写的 Common Name 信息一致,不然会出现下面的错误:

2018/09/25 17:04:02 cound not compute: rpc error: code = Unavailable desc = all SubConns are in TransientFailure, latest connection error: connection error: desc = "transport: authentication handshake failed: x509: certificate is valid for example.com, not localhost"

就是说,我们在生成CSR是填写的信息是 example.com ,但是使用的时候却是 localhost ,那么这是认证不通过的。因此,我们在生成CSR的时候, Common Name 信息需要填成 localhost

2.4 The First Try

当我们只修改服务器端,而客户端使用的是之前的版本,那么会出现如下的错误:

2018/09/25 16:58:43 cound not compute: rpc error: code = Unavailable desc = transport is closing

连接失败。因为两端都需要修改。

3. Client+TLS

在客户端这一部分,我们需要使用相同的证书来创建一个grpc客户端。由于我们是在 client/rpc/simplemath.go 中创建grpc客户端并建立连接,因此需要修改这个文件:

ackage rpc

import (
    "golang.org/x/net/context"
    // import credentials package
    "google.golang.org/grpc/credentials"
    "google.golang.org/grpc"
    "log"
    pb "simplemath/api"
    "strconv"
    "time"
)

const (
    address = "localhost:50051"
)

func GreatCommonDivisor(first, second string) {
    // create the client TLS credentials
    creds, err := credentials.NewClientTLSFromFile("../cert/server.crt", "")
    // initiate a connection with the server using creds
    conn, err := grpc.Dial(address, grpc.WithTransportCredentials(creds))
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
    c := pb.NewSimpleMathClient(conn)
    a, _ := strconv.ParseInt(first, 10, 32)
    b, _ := strconv.ParseInt(second, 10, 32)
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
    r, err := c.GreatCommonDivisor(ctx, &pb.GCDRequest{First: int32(a), Second: int32(b)})
    if err != nil {
        log.Fatalf("cound not compute: %v", err)
    }
    log.Printf("The Greatest Common Divisor of %d and %d is %d", a, b, r.Result)
}

和服务器端类似,我们使用 credentials.NewClientTLSFromFile() 函数创建一个credentials对象 creds ,并用这个对象创建连接。这个函数也是可变数量参数的函数,可以用来指定多个参数,用来指导建立连接时的行为。

注意,在创建 creds 的时候我们没有用到私钥( server.key ),从名字可知,这个私钥是服务器的。

这样,客户端和服务器端都使用了credentials,因此可以通过加密的方式进行通信了。

和原来一样,我们编译并运行服务器和客户端的代码,结果如下:

2018/09/25 18:11:26 The Greatest Common Divisor of 12 and 15 is 3

对话成功。

To Be Continued~


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Web Scalability for Startup Engineers

Web Scalability for Startup Engineers

Artur Ejsmont / McGraw / 2015-6-23 / USD 34.81

Design and build scalable web applications quickly This is an invaluable roadmap for meeting the rapid demand to deliver scalable applications in a startup environment. With a focus on core concept......一起来看看 《Web Scalability for Startup Engineers》 这本书的介绍吧!

SHA 加密
SHA 加密

SHA 加密工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具