grpc-go证书双向认证

grpc-go证书双向认证

grpc-go在使用中,可能会遇到一些缺少工具的问题,具体参看往期文章:https://www.madbull.site/?p=1453

官方示例

在grpc-go代码中,有一个示例,给出了服务端和客户端证书双向认证的实现。可以在这里 https://github.com/grpc/grpc-go 拉取代码,示例在 examples/features/encryption/mTLS 目录中。

本文基于官方给的 mTLS 示例,对 helloworld示例(也是官方的示例,具体参看官网文章:https://grpc.io/docs/languages/go/quickstart/)进行修改,实现客户端和服务端双向认证,并做了一些注释以作备忘。

第一步:准备证书

1.1、证书创建

双向认证需要4个证书:

  • 服务端CA证书:用来给服务端颁发证书的证书,此证书客户端必须信任,因为客户端需要用此证书来验证服务端证书的合法性。
  • 服务端证书:由服务端CA证书颁发,客户端连接时,服务端把此证书发送给客户端,由客户端来验证合法性。
  • 客户端CA证书:用来给客户端颁发证书的证书,此证书服务端必须信任,因为服务端需要用此证书来验证客户端证书的合法性。
  • 客户端证书:由客户端CA证书颁发,客户端连接时,客户端把此证书发送给服务端,由服务端来验证合法性。

以上4个证书,其中服务端CA证书客户端CA证书可以用同一个,所以,至少需要3个证书也可以。本文章示例就是用的3个证书做的示范。

关于证书颁发,参看往期文章:https://www.madbull.site/?p=1111。颁发客户端证书没有特殊要求,颁发服务端证书对DNS配置和IP配置有一些要求。配置文件详细内容如下:

[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = req_distinguished_name
req_extensions = v3_req

[req_distinguished_name]
countryName = CN
stateOrProvinceName = Shandong
localityName = Qingdao
organizationName = madbull
organizationalUnitName = IT Department
commonName = node9
emailAddress = xxx@xxx.com

[v3_req]
subjectAltName = @alt_names

[alt_names]
DNS.1 = node9
DNS.2 = localhost
IP.1 = 127.0.0.1
IP.2 = 192.168.1.57

1.2、证书存放位置:

由于本次示例在本地主机做的测试,所以客户端和服务端的密钥对放在一起来了,对于跨主机的操作,目录应该是下面这个样子的:

服务端:

客户端:

第二步:代码

2.1、目录结构

2.2、接口文件

helloworld/helloworld.proto文件:

syntax = "proto3";

option go_package = "google.golang.org/grpc/project/helloworld";
option java_multiple_files = true;
option java_package = "io.grpc.project";
option java_outer_classname = "HelloWorldProto";

package helloworld;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}

  rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}

}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

2.3、服务端代码

server/main.go文件:

// Package main implements a server for Greeter service.
package main

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

    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
    pb "google.golang.org/grpc/project/helloworld"
)

var (
    port = flag.Int("port", 50051, "The server port")
)

// server is used to implement helloworld.GreeterServer.
type server struct {
    pb.UnimplementedGreeterServer
}

// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(_ context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
    log.Printf("Received: %v", in.GetName())
    return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}

func (s * server) SayHelloAgain( ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
    return &pb.HelloReply{Message: "Hello again " + in.GetName() }, nil
}

// 生成服务端 tls 配置
func GetServTlsConfig() *tls.Config {
    // 1、加载服务的证书和私钥对
    certs, err := tls.LoadX509KeyPair("/opt/certs/public.crt", "/opt/certs/private.key")
    if err != nil {
        log.Fatalf("failed to load key pair: %q", err)
    }

    // 2、加载信任的根证书
    // 2.1、定义一个证书池
    ca :=  x509.NewCertPool()
    caFilePath := "/opt/certs/CAs/myCA.crt" 
    // 2.2、读取证书内容
    caBytes, err := os.ReadFile(caFilePath)
    if err != nil {
        log.Fatalf("failed to read ca cert %q, %v", caFilePath, err)
    }
    // 2.3、把证书添加到证书池
    if ok := ca.AppendCertsFromPEM(caBytes); !ok {
        log.Fatalf("failed to parse %q", caFilePath)
    }

    // 3、tls配置设置
    tlsConfig := &tls.Config{
        ClientAuth: tls.RequireAndVerifyClientCert,     // 验证策略:要求验证客户端证书。
        Certificates: []tls.Certificate {certs},        // 服务的证书和私钥对
        ClientCAs: ca,                                  // 用于验证客户端证书的信任的根证书
    }
    return tlsConfig
}


func main() {
    flag.Parse()

    tlsConfig := GetServTlsConfig() 

    // 4、生成grpc服务
    // 4.1、根据配置生成证书认证模块
    creds := credentials.NewTLS(tlsConfig) ;
    serv := grpc.NewServer(grpc.Creds(creds))
    // 4.2、用生成的 grpc 服务 serv 来 注册Greeter服务
    pb.RegisterGreeterServer(serv, &server{})

    // 5、设置tcp监听
    lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    // 5.1、服务绑定监听
    if err := serv.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

2.4、客户端代码

client/main.go文件:

// Package main implements a client for Greeter service.
package main

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

    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
    pb "google.golang.org/grpc/project/helloworld"
)

// 全局变量,默认值
const (
    defaultName = "world"
)

// 设置参数
var (
    addr = flag.String("addr", "localhost:50051", "the address to connect to")
    name = flag.String("name", defaultName, "Name to greet")
)

// 生成客户端 tls 配置
func GetCliTlsConfig() *tls.Config {

    // 1、加载客户端证书和私钥对
    certs, err :=  tls.LoadX509KeyPair("/opt/certs/clnt.crt", "/opt/certs/clnt.key")
    if err != nil {
        log.Fatalf("failed to load client cert: %v", err)
    }

    // 2、加载信任的根证书
    // 2.1、定义一个证书池
    ca :=  x509.NewCertPool()
    caFilePath := "/opt/certs/CAs/myCA.crt" 
    // 2.2、读取证书内容
    caBytes, err := os.ReadFile(caFilePath)
    if err != nil {
        log.Fatalf("failed to read ca cert %q, %v", caFilePath, err)
    }
    // 2.3、把证书添加到证书池
    if ok := ca.AppendCertsFromPEM(caBytes); !ok {
        log.Fatalf("failed to parse %q", caFilePath)
    }

    // 3、tls配置设置
    tlsConfig := &tls.Config{
        ServerName: "node9",                            // 服务端证书的DNS名称
        Certificates: []tls.Certificate {certs},        // 服务的证书和私钥对
        RootCAs: ca,                                    // 用于验证服务端的信任的根证书
    }
    return tlsConfig ;
}

func main() {
    flag.Parse()

    tlsConfig := GetCliTlsConfig()

    // 4、连接 rpc 远端服务
    // 4、tcp连接 和 认证
    creds := credentials.NewTLS(tlsConfig) ;
    conn, err := grpc.NewClient(*addr, grpc.WithTransportCredentials(creds))
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
    // 4.2、创建 Greeter 服务的客户端
    cli := pb.NewGreeterClient(conn)

    // 5、远程调用
    // 5.1、创建上下文,设置10秒超时
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()

    // 5.2、远程调用 Greeter 服务的函数: SayHello 和 SayHelloAgain
    resp, err := cli.SayHello(ctx, &pb.HelloRequest{Name: *name})
    if err != nil {
        log.Fatalf("could not greet: %v", err)
    }
    log.Printf("Greeting: %s", resp.GetMessage())

    resp, err = cli.SayHelloAgain(ctx, &pb.HelloRequest{Name: *name})
    if err != nil {
        log.Fatalf("could not greet: %v", err)
    }
    log.Printf("Greeting: %s", resp.GetMessage())
}

第三步:运行和测试

3.1、生成go接口文件

在 project 目录下,执行指令:protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative helloworld/helloworld.proto

生成 golang 的 接口文件,helloworld.pb.go 和 helloworld_grpc.pb.go

3.2、运行服务端

3.3、运行客户端

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注