[grpc]双向tls加密认证

发布时间 2023-07-20 18:19:39作者: 花酒锄作田

前言

假设gRPC服务端的主机名为qw.er.com,需要为gRPC服务端和客户端之间的通信配置tls双向认证加密。

生成证书

  1. 生成ca根证书。生成过程会要求填写密码、CN、ON、OU等信息,记住密码。
openssl req -x509 -newkey rsa:4096 -keyout ca.key -out ca.crt -subj "/CN=hgzh.hse.com" -days 3650
  1. 新建并编辑文件openssl.cnf文件。req_distinguished_name中内容按需填写,DNS.1要替换成实际域名。
[req]
req_extensions = v3_req
distinguished_name = req_distinguished_name
prompt = no

[req_distinguished_name]
countryName = CN
stateOrProvinceName = Anhui
localityName = Hefei
organizationName = zhangsan
commonName = qw.er.com

[v3_req]
subjectAltName = @alt_names

[alt_names]
DNS.1 = qw.er.com
  1. 生成服务端证书
openssl req -newkey rsa:2048 -nodes -keyout server.key -out server.csr -subj "/CN=qw.er.com" -config openssl.cnf

# 提示输入ca私钥的密码
openssl x509 -req -in server.csr -out server.crt -CA ca.crt -CAkey ca.key -CAcreateserial -days 365 -extensions v3_req -extfile openssl.cnf
  1. 生成客户端证书
openssl req -newkey rsa:2048 -nodes -keyout client.key -out client.csr -subj "/CN=qw.er.com" -config openssl.cnf

# 提示输入ca私钥的密码
openssl x509 -req -in client.csr -out client.crt -CA ca.crt -CAkey ca.key -CAcreateserial -days 365 -extensions v3_req -extfile openssl.cnf

服务端代码示例

只包含了tls配置的部分代码。

package main

import (
	pb "qwer/proto"
	"context"
	"crypto/tls"
	"crypto/x509"
	"flag"
	"fmt"
	"log"
	"net"
	"os"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
)

var (
	port    = flag.Int("port", 8010, "the server port")
	crtFile = flag.String("crt", "server.crt", "the server crt file")
	keyFile = flag.String("key", "server.key", "the server key file")
	caFile  = flag.String("ca", "ca.crt", "the server ca file")
)

func main() {
	flag.Parse()

	// 通过服务端的证书和密钥直接创建X.509密钥对
	certificate, err := tls.LoadX509KeyPair(*crtFile, *keyFile)
	if err != nil {
		log.Fatalf("Failed to load key pair: %v", err)
	}

	// 通过CA创建证书池
	certPool := x509.NewCertPool()
	ca, err := os.ReadFile(*caFile)
	if err != nil {
		log.Fatalf("Failed to read ca: %v", err)
	}

	// 将来自CA的客户端证书附加到证书池
	if ok := certPool.AppendCertsFromPEM(ca); !ok {
		log.Fatalf("Failed to append ca certificate")
	}

	opts := []grpc.ServerOption{
		grpc.Creds( // 为所有传入的连接启用TLS
			credentials.NewTLS(&tls.Config{
				ClientAuth: tls.RequireAndVerifyClientCert,
				Certificates: []tls.Certificate{certificate},
				ClientCAs: certPool,
			},
		)),
	}

	listen, err := net.Listen("tcp", fmt.Sprintf("0.0.0.0:%d", *port))
	if err != nil {
		log.Fatalf("failed to listen %d port", *port)
	}
	// 通过传入的TLS服务器凭证创建新的gRPC服务实例
	s := grpc.NewServer(opts...)
	pb.RegisterQwerServer(s, &server{})
	log.Printf("server listening at %v", listen.Addr())
	if err := s.Serve(listen); err != nil {
		log.Fatalf("Failed to serve: %v", err)
	}

}

客户端代码示例

只包含了tls配置的部分代码。

package main

import (
	pb "qwerc/proto"
	"context"
	"crypto/tls"
	"crypto/x509"
	"flag"
	"log"
	"os"
	"time"

	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials"
)

var (
	addr     = flag.String("addr", "qw.er.com:8010", "server address")
	hostname = flag.String("host", "qw.er.com", "server hostname")
	crtFile  = flag.String("crt", "client.crt", "client crt file")
	keyFile  = flag.String("key", "client.key", "client key file")
	caFile   = flag.String("ca", "ca.crt", "ca file")
)

func main() {
	flag.Parse()

	certificate, err := tls.LoadX509KeyPair(*crtFile, *keyFile)
	if err != nil {
		log.Fatalf("Failed to load client key pair, %v", err)
	}

	certPool := x509.NewCertPool()
	ca, err := os.ReadFile(*caFile)
	if err != nil {
		log.Fatalf("Failed to read %s, error: %v", *caFile, err)
	}

	if ok := certPool.AppendCertsFromPEM(ca); !ok {
		log.Fatalf("Failed to append ca certs")
	}

	opts := []grpc.DialOption{
		grpc.WithTransportCredentials(credentials.NewTLS(
			&tls.Config{
				ServerName:   *hostname,
				Certificates: []tls.Certificate{certificate},
				RootCAs:      certPool,
			})),
	}

	// conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
	conn, err := grpc.Dial(*addr, opts...)
	if err != nil {
		log.Fatalf("Connect to %s failed", *addr)
	}
	defer conn.Close()

	client := pb.NewQwerClient(conn)
	// 创建带有超时时间的上下文, cancel可以取消上下文
	ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
	defer cancel()

	// 业务代码处理部分 ...
}