Go gRPC 系列:

跟我一起学Go系列:gRPC 拦截器使用

跟我一起学Go系列:gRPC 入门必备

第一篇入门说过 gRPC 底层是基于 HTTP/2 协议的,HTTP 本身不带任何加密传输功能,基于 SSL 的 HTTPS 协议才是加密传输。gRPC 使用了 HTTP/2 协议但是并未使用 HTTPS,即少了加密传输的部分。

对于加密传输的部分 gRPC 将它抽出来作为一个组件,可以由用户自由选择。gRPC 内默认提供了两种 内置的认证方式:

  1. 基于 CA 证书的 SSL/TLS 认证方式;
  2. 基于 Token 的认证方式。

同时也提供了可扩展的用户自定义认证方式。

gRPC 中的连接类型一共有以下 3 种:

  1. insecure connection:不使用 TLS 加密;
  2. server-side TLS:仅服务端 TLS 加密;
  3. mutual TLS:客户端、服务端都使用 TLS 加密。

我们之前的实例中都是使用 insecure connection:

conn, err := grpc.Dial(":8972", grpc.WithInsecure())

这种方式相当于裸奔的数据在网络上行走,生产环境下这样使用肯定是不行的。下面我们来说一下基于 TLS 认证方式加密操作。

server-side TLS

服务端 TLS 具体包含以下几个步骤:

  1. 制作证书,包含服务端证书和 CA 证书;
  2. 服务端启动时加载证书;
  3. 客户端连接时使用CA 证书校验服务端证书有效性。

CA 证书制作:

# 生成.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 位置:

$ find / -name "openssl.cnf"

然后生成签名证书:

# 生成.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"))

至此会生成如下文件:

-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

下面我们用到的会有这三个:

ca.crt
server.key
server.crt

下面来看一下如何将加密校验逻辑加入到代码中。相关代码在 Github 上,自行下载查看

服务端的修改点如下:

  • NewServerTLSFromFile 加载证书
  • NewServer 时指定 Creds。
func 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 连接,客户端也需要使用对应的连接方式才能进行传输。客户端代码主要修改点:

  • NewClientTLSFromFile 指定使用 CA 证书来校验服务端的证书有效性。注意:第二个参数域名就是前面生成服务端证书时指定的CN参数
  • 建立连接时 指定建立安全连接 WithTransportCredentials。

对应代码逻辑如下:

func 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)
}
mutual TLS

server-side TLS 中虽然服务端使用了证书,但是客户端却没有使用证书,本章节会给客户端也生成一个证书,并完成 mutual TLS。

生成客户端证书和生成服务端证书没有什么不同:

# 生成.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"))

执行上面的命令之后我们会得到这两个重要的文件:

client.crt
client.key

下面就是在代码中去引用这些文件,mutual TLS 配置客户端和服务端都需要修改,相关代码点击查看

具体改动如下:

服务端:

func 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
} }

客户端:

func 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 认证和自定义认证方式。

跟我一起学Go系列:Go gRPC 安全认证机制-SSL/TLS认证的更多相关文章

  1. 跟我一起学 Go 系列:gRPC 拦截器

    Go gRPC 学习系列: 跟我一起学Go系列:gRPC 入门必备 第一篇内容我们已经基本了解到 gRPC 如何使用 .对应的三种流模式.现在已经可以让服务端和客户端互相发送消息.本篇仍然讲解功能性的 ...

  2. .NET Core下使用gRpc公开服务(SSL/TLS)

    一.前言 前一阵子关于.NET的各大公众号都发表了关于gRpc的消息,而随之而来的就是一波关于.NET Core下如何使用的教程,但是在这众多的教程中基本都是泛泛而谈,难以实际在实际环境中使用,而该篇 ...

  3. 跟我一起学Go系列:gRPC 入门必备

    RPC 的定义这里就不再说,看文章的同学都是成熟的开发.gRPC 是 Google 开源的高性能跨语言的 RPC 方案,该框架的作者 Louis Ryan 阐述了设计这款框架的动机,有兴趣的同学可以看 ...

  4. 跟我一起学Go系列:gRPC 全局数据传输和超时处理

    gRPC 在多个 GoRoutine 之间传递数据使用的是 Go SDK 提供的 Context 包.关于 Context 的使用可以看我之前的一篇文章:Context 使用. 但是 Context ...

  5. 跟我一起学Go系列:Go gRPC 安全认证方式-Token和自定义认证

    Go gRPC 系列: 跟我一起学Go系列:gRPC安全认证机制-SSL/TLS认证 跟我一起学 Go 系列:gRPC 拦截器使用 跟我一起学 Go 系列:gRPC 入门必备 接上一篇继续讲 gRPC ...

  6. Go gRPC进阶-TLS认证+自定义方法认证(七)

    前言 前面篇章的gRPC都是明文传输的,容易被篡改数据.本章将介绍如何为gRPC添加安全机制,包括TLS证书认证和Token认证. TLS证书认证 什么是TLS TLS(Transport Layer ...

  7. 跟着鸟哥学Linux系列笔记3-第11章BASH学习

    跟着鸟哥学Linux系列笔记0-扫盲之概念 跟着鸟哥学Linux系列笔记0-如何解决问题 跟着鸟哥学Linux系列笔记1 跟着鸟哥学Linux系列笔记2-第10章VIM学习 认识与学习bash 1. ...

  8. 跟着鸟哥学Linux系列笔记2-第10章VIM学习

    跟着鸟哥学Linux系列笔记0-扫盲之概念 跟着鸟哥学Linux系列笔记0-如何解决问题 跟着鸟哥学Linux系列笔记1 常用的文本编辑器:Emacs, pico, nano, joe, vim VI ...

  9. 跟着鸟哥学Linux系列笔记0-如何解决问题

    跟着鸟哥学Linux系列笔记0-扫盲之概念 在发生问题怎么处理: 1.  在自己的主机.网络数据库上查询How-To或FAQ -Linux 自身的文件数据: /usr/share/doc -CLDP中 ...

随机推荐

  1. 系统区域设置 本地语言的支持依赖于 /etc/locale.conf,/etc/locale.conf 包含不少于此相关的环境变量

    https://linux.cn/lfs/LFS-BOOK-7.7-systemd/chapter07/locale.html 7.7. 系统区域设置 本地语言的支持依赖于 /etc/locale.c ...

  2. zabbix screen 图片以邮件形式发送

    zabbix screen 图片以邮件形式发送 #! /usr/bin/env python #coding=utf-8 # Andy_f import time,os import urllib i ...

  3. Centos7使用mail命令发送邮件

    以配置outlook邮箱(微软邮箱)为例 一.配置mail邮箱账号密码 [root@bogon log]# tail -n 8 /etc/mail.rc set from=bp**@outlook.c ...

  4. Java8 Period 类与 Duration 类 用法详解

    引言 Java 8 中引入了两个与日期相关的新类: Period :基于日期值 Duration:基于时间值 它们最大的作用就不需要你自己复杂的计算关于两个年月日之间的相差的时间或日期啦. Perio ...

  5. python 中的nonlocal

    python 中nonloal 关键字用来在函数或其他作用域中使用外层变量(非全局),也可使用global需要在函数外部

  6. HDFS的小文件问题

    HDFS 中任何一个文件,目录或者数据块在 NameNode 节点内存中均以一个对象形式表示(元数据),而这受到 NameNode 物理内存容量的限制.每个元数据对象约占 150 byte,所以如果有 ...

  7. .NET Worker Service 添加 Serilog 日志记录

    前面我们了解了 .NET Worker Service 的入门知识[1] 和 如何优雅退出 Worker Service [2],今天我们接着介绍一下如何为 Worker Service 添加 Ser ...

  8. thinkphp api接口 统一结果返回处理类

    20210602 修正 wqy的笔记:http://www.upwqy.com/details/216.html 返回结果处理,归根结底 主要是有两点 数据结构和返回的数据类型 1.数据类型 :一般情 ...

  9. Go语言基础包之net/http

    Go语言基础包之net/http Go语言内置的net/http包十分的优秀,提供了HTTP客户端和服务端的实现. net/http介绍 Go语言内置的net/http包提供了HTTP客户端和服务端的 ...

  10. Docker学习(14) Docker容器的数据管理

    Docker容器的数据管理 Docker容器的数据卷 重要: Docker的数据卷容器 Docker数据卷的备份和还原