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

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

内容简介:在客户端和服务器通过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~


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

查看所有标签

猜你喜欢:

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

大数据架构商业之路

大数据架构商业之路

黄申 / 机械工业出版社 / 2016-5-1 / 69.00元

目前大数据技术已经日趋成熟,但是业界发现与大数据相关的产品设计和研发仍然非常困难,技术、产品和商业的结合度还远远不够。这主要是因为大数据涉及范围广、技术含量高、更新换代快,门槛也比其他大多数IT行业更高。人们要么使用昂贵的商业解决方案,要么花费巨大的精力摸索。本书通过一个虚拟的互联网O2O创业故事,来逐步展开介绍创业各个阶段可能遇到的大数据课题、业务需求,以及相对应的技术方案,甚至是实践解析;让读......一起来看看 《大数据架构商业之路》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

MD5 加密
MD5 加密

MD5 加密工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器