• 跟我一起学Go系列:Go gRPC 安全认证机制-SSL/TLS认证


    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 "
    [SAN]
    subjectAltName=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 "
    [SAN]
    subjectAltName=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 !
    ", 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 "
    [SAN]
    subjectAltName=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 "
    [SAN]
    subjectAltName=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 !
    ", r.Message)
    }
    

    本篇只介绍 SSL/TLS 认证相关的方式,生成证书相关操作本文是在 Mac 上操作,不同系统可能会有不一样的环境问题, 如果出现问题按照相关提示排除错误。下一篇我们继续介绍 Token 认证和自定义认证方式。

  • 相关阅读:
    ansible4:playbook介绍及使用
    ansible3:模块介绍
    Rabin加密算法
    基础业务:图片懒加载
    基础业务:滚动到指定位置导航固定(CSS实现)
    数据库事务处理的并发控制技术(二):事务模型
    详解HTTP缓存
    数据库事务处理的并发控制技术(一):并发控制概述
    二叉树的深度优先遍历和广度优先遍历
    Virtual DOM的简单实现
  • 原文地址:https://www.cnblogs.com/rickiyang/p/14981374.html
Copyright © 2020-2023  润新知